feat(dav): dav_get_collection + dav_collection_ctag — bulk DAV en 1 request + ctag cache
dav_get_collection trae TODOS los recursos de una coleccion CardDAV/CalDAV en UNA peticion REPORT (addressbook-query / calendar-query) con el contenido vCard / VCALENDAR inline, evitando el patron N+1 (PROPFIND + un GET por recurso). Para 1064 contactos baja de ~9s a ~1s. dav_collection_ctag lee el ctag de la coleccion (PROPFIND Depth:0 barato) para validar caches sin descargar cuando nada cambio. Ambas: solo stdlib, basic auth, verify_tls, error-safe, tests que mockean el multistatus. Grupo dav, verificadas contra Xandikos real. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
---
|
||||
name: dav_get_collection
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def dav_get_collection(base_url: str, username: str, password: str, collection_path: str, content_type: str = 'vcard', *, timeout_s: float = 30.0, verify_tls: bool = True) -> dict"
|
||||
description: "Descarga TODOS los recursos de una coleccion DAV en UNA peticion HTTP REPORT con el contenido inline, evitando el patron N+1 (PROPFIND + un GET por recurso). Usa addressbook-query (CardDAV, content_type='vcard') o calendar-query (CalDAV, content_type='ical'); el servidor responde un multistatus con el vCard/VCALENDAR de cada recurso embebido. Construye el header Authorization: Basic base64(user:pass) a mano con stdlib, parsea el XML con regex simple y des-escapa las entidades XML del contenido. Para 1064 contactos baja de ~9s (N GETs) a ~1s (1 REPORT). verify_tls=True por defecto. Maneja errores sin lanzar. Solo stdlib (urllib, base64, re, ssl, html). Probado contra Xandikos."
|
||||
tags: [dav, carddav, caldav, report, multiget, addressbook-query, calendar-query, bulk, http, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [base64, html, re, ssl, urllib.error, urllib.request]
|
||||
params:
|
||||
- name: base_url
|
||||
desc: "URL base del servidor DAV (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: collection_path
|
||||
desc: "ruta de la coleccion (CardDAV '/enmanuel/contacts/addressbook/' o CalDAV '/enmanuel/calendars/calendar/')."
|
||||
- name: content_type
|
||||
desc: "tipo de la coleccion: 'vcard' (CardDAV, default) o 'ical' (CalDAV). Acepta sinonimos: 'carddav'/'contacts'/'addressbook' -> vcard; 'caldav'/'calendar'/'icalendar' -> ical."
|
||||
- name: timeout_s
|
||||
desc: "timeout de la peticion HTTP en segundos. Default 30.0 (la respuesta puede ser grande: ~600KB para 1000 contactos)."
|
||||
- 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, resources:[{href:str, etag:str|None, data:str}, ...]} con un elemento por recurso de la coleccion; data es el vCard / VCALENDAR completo ya des-escapado. En error (sin lanzar): {status:'error', error:str, http_status:int|None}."
|
||||
tested: true
|
||||
tests:
|
||||
- "test_vcard_construye_report_addressbook_query"
|
||||
- "test_ical_construye_report_calendar_query_con_filtro"
|
||||
- "test_basic_auth_header_correcto"
|
||||
- "test_parsea_resources_con_data_inline"
|
||||
- "test_desescapa_entidades_xml_del_data"
|
||||
- "test_ical_parsea_calendar_data"
|
||||
- "test_acepta_sinonimos_de_content_type"
|
||||
- "test_content_type_invalido_devuelve_error"
|
||||
- "test_httperror_devuelve_status_error"
|
||||
test_file_path: "python/functions/infra/dav_get_collection_test.py"
|
||||
file_path: "python/functions/infra/dav_get_collection.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, "python/functions")
|
||||
from infra.pass_get_secret import pass_get_secret
|
||||
from infra.dav_get_collection import dav_get_collection
|
||||
|
||||
pw = pass_get_secret("dav/xandikos-enmanuel")["value"] # NO logear
|
||||
|
||||
# Todos los contactos en UNA peticion (~1s para 1064 vCards):
|
||||
res = dav_get_collection(
|
||||
base_url="https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com",
|
||||
username="enmanuel",
|
||||
password=pw,
|
||||
collection_path="/enmanuel/contacts/addressbook/",
|
||||
content_type="vcard",
|
||||
)
|
||||
print(res["status"], len(res["resources"])) # ok 1064
|
||||
print(res["resources"][0]["data"][:40]) # BEGIN:VCARD\nVERSION:3.0\nFN:...
|
||||
|
||||
# Todos los eventos del calendario en UNA peticion:
|
||||
cal = dav_get_collection(
|
||||
base_url="https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com",
|
||||
username="enmanuel",
|
||||
password=pw,
|
||||
collection_path="/enmanuel/calendars/calendar/",
|
||||
content_type="ical",
|
||||
)
|
||||
print(cal["status"], len(cal["resources"])) # ok 98
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando necesitas el contenido de TODOS los recursos de una coleccion CardDAV o
|
||||
CalDAV (renderizar la agenda completa, listar todos los eventos, sincronizar en
|
||||
bloque) y no solo sus hrefs. Sustituye a `dav_list_resources` + un
|
||||
`dav_get_resource` por recurso: una sola ida y vuelta en lugar de N+1, lo que
|
||||
para colecciones de cientos/miles de recursos es la diferencia entre ~9s y ~1s.
|
||||
Si solo necesitas los hrefs/etags (sin contenido), usa `dav_list_resources`; si
|
||||
necesitas un unico recurso, usa `dav_get_resource`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Usa los REPORT `addressbook-query` / `calendar-query` (RFC 6352 / 4791) con
|
||||
Depth:1, NO `addressbook-multiget` (que en Xandikos exige Depth:0 + una lista
|
||||
explicita de hrefs en el cuerpo). El query no necesita conocer los hrefs de
|
||||
antemano: una sola peticion trae todo.
|
||||
- El namespace CardDAV/CalDAV es el "legacy" `urn:ietf:params:xml:ns:carddav`
|
||||
(con `:ns:`), que es el que Xandikos anuncia en su `supported-report-set`. El
|
||||
namespace sin `:ns:` (`urn:ietf:params:xml:carddav`) provoca un 403
|
||||
"Unknown report" en Xandikos.
|
||||
- El contenido inline viene XML-escapado en el multistatus (`<`, `>`,
|
||||
`&`); la funcion lo des-escapa con `html.unescape` antes de devolverlo.
|
||||
El `data` resultante es el vCard / VCALENDAR tal cual lo guardo el servidor.
|
||||
- El parseo es regex simple sobre el multistatus (KISS, sin parser XML): robusto
|
||||
para la salida estandar de Xandikos, podria fallar con XML muy exotico.
|
||||
- La respuesta puede ser grande (~600KB para 1000 contactos): el timeout default
|
||||
es 30s, mayor que el de `dav_list_resources` por eso.
|
||||
- Lectura remota real sobre TLS; password de `pass`, no se logea.
|
||||
Reference in New Issue
Block a user