Skip to content

Tutorial work#641

Draft
tobixen wants to merge 12 commits intomasterfrom
test-fix
Draft

Tutorial work#641
tobixen wants to merge 12 commits intomasterfrom
test-fix

Conversation

@tobixen
Copy link
Member

@tobixen tobixen commented Mar 15, 2026

I'm redoing the tutorial. While working on it, I found quite many issues. I'm tossing both the tutorial work and the fixes needed to get the tutorial to work into one pull request. Also, piggybacking in a fix for a test breakage for Stalwart. Somehow that commit fell out while I was doing the v3.0.2-release.

tobixen and others added 10 commits March 15, 2026 22:34
Stalwart (and RFC-compliant servers in general) may return the same
iCal object with different line folding depending on the fetch method
(get_journal_by_uid vs get_journals).  The old test relied on a side
effect of property access on icalendar_instance to force re-serialization
through the icalendar library (which normalizes folding), then compared
.data.  After the ruff B018 cleanup replaced that with the explicit
get_icalendar_instance() call (which has no side effects on .data),
the raw differently-folded bytes were compared and the assertion failed.

Fix: compare to_ical() output directly, which is both side-effect-free
and normalizes line folding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When PYTHON_CALDAV_USE_TEST_SERVER=1 is set (or testconfig=True), and no
testing_allowed config-file section is found, get_davclient() now falls
back to spinning up the first enabled server from the test-server registry.

Registry changes:
- Xandikos is now discovered before Radicale in _discover_embedded_servers(),
  giving it higher default priority
- all_servers() and enabled_servers() now return servers sorted by priority
  (lower number = first); defaults: embedded=10, docker=20, external=30
- Per-server priority can be overridden via priority: <int> in either
  caldav_test_servers.yaml or a calendar config file section
- Three new env vars filter which server types are included:
    PYTHON_CALDAV_TEST_EMBEDDED (default: enabled)
    PYTHON_CALDAV_TEST_DOCKER   (default: enabled)
    PYTHON_CALDAV_TEST_EXTERNAL (default: enabled)
  Set any to 0/false/no/off to exclude that category.

caldav/config.py changes:
- _get_test_server_config() falls back to _try_start_registry_server()
  when no testing_allowed config section is found
- _try_start_registry_server() iterates enabled_servers() in priority
  order, starts the first one that succeeds, and returns conn params

Tests added:
- testAutoEmbeddedServer: verifies that testconfig=True auto-starts
  xandikos when no config file server is found (skipped if xandikos absent)
- test_get_davclient_returns_none_without_env_or_config: verifies that
  get_davclient() returns None when no env var and no config file are present

- Add caldav/testing.py (shipped with package): EmbeddedServer base class,
  XandikosServer, RadicaleServer — so pip-installed users can use
  PYTHON_CALDAV_USE_TEST_SERVER=1 without the source tree.

- Rewrite caldav/config.py _get_test_server_config() to use the registry as
  the primary authority (not a fallback): priority-ordered servers win over
  config-file entries unless those entries have an explicit better priority.
  Adds _collect_test_servers(), _get_pip_test_servers(), _ConfiguredServer,
  _test_server_to_params(). Removes _try_start_registry_server() and
  _registry_server_to_params() which duplicated client_context() logic.

- Rewrite tests/test_servers/helpers.py client_context(): no longer writes a
  temporary config file; just starts the server directly and sets
  PYTHON_CALDAV_USE_TEST_SERVER=1 for the duration.

- Rewrite tests/test_servers/embedded.py: XandikosTestServer and
  RadicaleTestServer now delegate to caldav.testing.XandikosServer /
  RadicaleServer, eliminating duplicated startup/teardown code.

- tests/test_caldav.py: add testAutoEmbeddedServer and
  test_get_davclient_returns_none_without_env_or_config; fix
  caldav_servers[-1] → caldav_servers[0] (embedded servers now sort first).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndar_name/url filtering

Two bugs fixed, one new feature:

Bug 1: _extract_conn_params_from_section silently dropped calendar_name and
calendar_url keys from config sections, so get_calendars(config_section="foo")
where foo had calendar_name: "My Cal" would return all calendars instead of
filtering.  Fixed by extracting those keys alongside the caldav_* ones.

Bug 2: expand_config_section was never called when reading the config file, so
meta-sections ({"contains": ["work", "personal"]}) had no effect.

New feature: get_calendars() now uses expand_config_section on the config file
path, expanding a single config_section value ("*", "all", "work_*", ...) to
multiple leaf sections.  Each expanded section gets its own DAVClient; all
calendars are aggregated into one CalendarCollection.  CalendarCollection now
holds a list of clients and closes all of them on exit.

The single-server path (explicit url=, env vars, testconfig=True) is unchanged.
Per-section calendar_name / calendar_url are used as filters; function-level
arguments override them.

New helper _fetch_calendars_for_client() eliminates the duplicate calendar-
fetching logic that was inline in get_calendars().

New config.py export: get_all_file_connection_params(config_file, section).

Tests added in test_caldav_unit.py (TestExpandConfigSection,
TestGetAllFileConnectionParams) and test_caldav.py (TestGetCalendarsConfig).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_icalendar_component() returns a deepcopy of the inner
VEVENT/VTODO/VJOURNAL subcomponent for read-only access, consistent
with the get_icalendar_instance() naming convention.

edit_icalendar_component() is a context manager that yields the inner
component directly for editing, delegating to edit_icalendar_instance()
so all borrow/state/save machinery is reused.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arning

RFC 4791 §9.9 requires UTC datetime values in time-range queries.  When
plain date objects were passed to calendar.search() or calendar.searcher(),
they flowed into icalendar_searcher.Searcher unchanged, which emitted a
logging.warning("Date-range searches not well supported yet; use datetime
rather than dates") for every object checked during client-side filtering.

Fix: extract _populate_searcher() helper (eliminating the duplicated loop
between searcher() and search()), and coerce any date to
datetime(Y, M, D, tzinfo=utc) before setting start/end/alarm_start/alarm_end
on the searcher.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ble()

A bodyless PROPFIND is treated as allprop by RFC 4918 §9.1.  Xandikos
handles allprop on the RootPage by iterating all registered properties,
several of which (owner, creationdate, comment) are not implemented on
RootPage and raise NotImplementedError, producing spurious ERROR log
lines on every liveness poll during test-server startup.

Send a minimal PROPFIND body that asks only for {DAV:}resourcetype.  This
verifies the server is a real WebDAV endpoint (no false positives from
non-DAV HTTP servers) without triggering allprop enumeration.  The
Content-Type: application/xml header is required; without it Xandikos
rejects the request with 415 Unsupported Media Type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nsion

- Fully document all config-file features implemented in caldav/config.py:
  calendar_name/calendar_url per-section, inherits (section inheritance),
  contains (meta-sections), glob patterns, * wildcard, disable flag,
  ${VAR} / ${VAR:-default} env-var expansion, features key, CALDAV_*
  env vars, and CALDAV_CONFIG_FILE / CALDAV_CONFIG_SECTION.
- Add configfile to the toctree (index.rst).
- Add a :doc:`configfile` cross-reference from the tutorial's
  "Real Configuration" section.
- Add TestConfigSectionInheritance (3 tests) covering the inherits key.
- Add TestExpandEnvVars (5 tests) covering expand_env_vars and its
  end-to-end effect through get_all_file_connection_params.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_davclient, get_calendar, get_calendars are defined in
caldav.davclient, not in the top-level caldav namespace from Sphinx's
perspective.  Update all six :func: roles to use the canonical
caldav.davclient.* paths so they render as actual hyperlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tobixen tobixen marked this pull request as draft March 18, 2026 10:06
@tobixen tobixen marked this pull request as draft March 18, 2026 10:06
@tobixen tobixen changed the title Test fixup for Stalwart Tutorial work Mar 18, 2026
tobixen and others added 2 commits March 18, 2026 11:55
- examples/basic_usage_examples.py used the deprecated `c.name` property,
  causing test_examples to fail with DeprecationWarning
- aiohttp is a transitive dep used only inside XandikosServer.start() in
  caldav/testing.py; add it to deptry DEP003 ignore to silence false positive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant