--- name: dav_make_calendar kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def dav_make_calendar(base_url: str, username: str, password: str, calendar_home: str, slug: str, display_name: str = \"\", color: str = \"\", description: str = \"\", *, timeout_s: float = 20.0, verify_tls: bool = True) -> dict" description: "Crea una nueva coleccion de calendario CalDAV (una agenda nueva) bajo el calendar-home de un principal via MKCALENDAR, fijando el displayname en el cuerpo, y opcionalmente fija color (Apple calendar-color) y descripcion (CalDAV calendar-description) con un PROPPATCH posterior. La coleccion se crea en /. El slug se sanea a [a-z0-9_-] (minusculas, espacios->guion); si queda vacio devuelve error de validacion. Idempotente: 201 Created es exito; 405/301 (ya existe) devuelve {status:'ok', existed:True}. Escapa display_name/description para XML. Construye Authorization: Basic base64(user:pass) a mano. Maneja errores sin lanzar (salvo validacion de args). Solo stdlib (urllib, base64, re, ssl, xml.sax.saxutils). Probado contra Xandikos." tags: [dav, caldav, calendar, mkcalendar, proppatch, create, collection, color, http, infra] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [base64, re, ssl, urllib.error, urllib.request, xml.sax.saxutils] params: - name: base_url desc: "URL base del servidor DAV sin barra final (p.ej. 'https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com')." - name: username desc: "usuario para HTTP Basic auth (p.ej. 'enmanuel')." - name: password desc: "contrasena para HTTP Basic auth. Resolver desde pass con pass_get_secret, nunca hardcodear." - name: calendar_home desc: "ruta del calendar-home del principal con barra final (p.ej. '/enmanuel/calendars/'). La nueva coleccion cuelga de el." - name: slug desc: "segmento de path de la coleccion en la URL (p.ej. 'trabajo'); se sanea a [a-z0-9_-]. La coleccion se crea en /. Si queda vacio tras sanear, devuelve error de validacion." - name: display_name desc: "nombre visible de la coleccion (DAV:displayname). Si vacio, usa el slug saneado." - name: color desc: "color de la coleccion como hex '#rrggbb' (propiedad calendar-color de Apple, http://apple.com/ns/ical/). Opcional; '' lo omite." - name: description desc: "descripcion de la coleccion (calendar-description de CalDAV). Opcional; '' lo omite." - name: timeout_s desc: "timeout de cada peticion HTTP en segundos. Default 20.0." - name: verify_tls desc: "si True (default) verifica el certificado TLS. No desactivar salvo entorno de prueba." output: "dict. En exito: {status:'ok', http_status:int, href:str} y, si la coleccion ya existia, ademas existed:True. En error (sin lanzar): {status:'error', http_status:int|None, href:str, error:str}. href es la ruta de la coleccion (calendar_home + slug saneado + '/')." tested: true tests: - "test_sanitize_slug_minusculas" - "test_sanitize_slug_espacios_a_guion" - "test_sanitize_slug_elimina_caracteres_raros" - "test_sanitize_slug_colapsa_guiones_y_recorta" - "test_sanitize_slug_vacio" - "test_join_url_compone_la_coleccion" - "test_mkcalendar_xml_incluye_displayname" - "test_mkcalendar_xml_escapa_displayname" - "test_proppatch_xml_color_y_descripcion" - "test_proppatch_xml_solo_color" - "test_proppatch_xml_escapa_descripcion" test_file_path: "python/functions/infra/dav_make_calendar_test.py" file_path: "python/functions/infra/dav_make_calendar.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from infra.pass_get_secret import pass_get_secret from infra.dav_make_calendar import dav_make_calendar pw = pass_get_secret("dav/xandikos-enmanuel")["value"] # NO logear res = dav_make_calendar( base_url="https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com", username="enmanuel", password=pw, calendar_home="/enmanuel/calendars/", slug="trabajo", display_name="Trabajo", color="#e8590c", ) print(res) # {'status': 'ok', 'http_status': 201, 'href': '/enmanuel/calendars/trabajo/'} # Volver a llamar con el mismo slug: # {'status': 'ok', 'http_status': 405, 'href': '/enmanuel/calendars/trabajo/', 'existed': True} ``` ## Cuando usarla Cuando el usuario quiere anadir una agenda/calendario nuevo ademas del principal: una coleccion CalDAV separada ("Trabajo", "Personal", "Cumpleanos") con su propio nombre visible y color, bajo el calendar-home del principal. El `href` devuelto es lo que luego pasas como `collection_path` a `caldav_put_event` para crear eventos en esa agenda, o a `dav_list_calendars` para verla en el selector. ## Gotchas - Impura: requiere red + Basic auth contra el servidor DAV. El password viene de `pass`, no se logea ni se hardcodea. - Idempotente: si la coleccion ya existe en ese path el servidor responde 405 (Method Not Allowed) o 301; ambos se traducen a `{status:'ok', existed:True}` en vez de error, asi que es seguro reintentar. - El PROPPATCH de color usa el `calendar-color` de Apple (`http://apple.com/ns/ical/`). Servidores que no lo soporten pueden ignorarlo: el fallo del PROPPATCH NO es fatal (el calendario ya quedo creado) y se ignora silenciosamente; el color simplemente no se aplica. Si necesitas confirmar el color, leelo despues con `dav_list_calendars`. - El `slug` se sanea a `[a-z0-9_-]` (minusculas, espacios->guion, resto fuera). Un slug que queda vacio tras sanear (p.ej. solo simbolos) devuelve error de validacion sin tocar la red. El `display_name` y la `description` se escapan para XML, pero el `slug` que va en la URL ya esta restringido al charset seguro.