Skip to content

Commit e3dc280

Browse files
tobixenclaude
andcommitted
Update documentation, examples, and changelog
- AI-POLICY.md: expand policy with guidance on AI-assisted code review, vibe-coding caveats, and disclosure requirements - CHANGELOG.md: update unreleased section with all v3.0-dev changes - MANIFEST.in: include caldav_test_servers.yaml.example in sdist - RELEASE-HOWTO.md: minor update - docs/design/V3_CODE_REVIEW.md: add niquests and urllib3-future code reviews - docs/design/niquests-code-review.md: new — review of niquests HTTP library - docs/design/urllib3-future-code-review.md: new — review of urllib3-future - docs/design/TODO.md: update outstanding tasks - docs/source/tutorial.rst: revise for v3.0 async API and new patterns - docs/source/about.rst, conf.py: minor fixes - examples/scheduling_examples.py: remove accidentally committed breakpoints Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c4e8efe commit e3dc280

12 files changed

Lines changed: 867 additions & 355 deletions

AI-POLICY.md

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,59 @@
11
# Policy on usage of Artifical Intelligence and other tools
22

3+
## Read this first
4+
5+
The most important rule: Inform about it!
6+
7+
If you've spent hours, perhaps a full day of your time writing up a
8+
pull request, then I sort of owe you something. I should spend some
9+
of my time looking through the submission carefully, and if nothing
10+
else, I owe to be polite, respectful and guide you in the right
11+
direction or give a good explanation for why I think your pull request
12+
is pulling the project in the wrong direction. A human being have
13+
feelings, I should be careful not to hurt your feelings.
14+
15+
At the other hand, perhaps you've spent 30 seconds either doing `ruff
16+
check --fix ; gh pr create` or telling Claude to check what went wrong
17+
in the logs and submit a bugfix upstream. Do I still owe
18+
you to spend time looking through the submission carefully and
19+
spending time being polite and caring about your feelings?
20+
21+
Perhaps your pull request is just one out of many such "drive-by pull
22+
requests". It doesn't scale for a maintainer to spent lots of time on
23+
each such pull request. I should just accept or decline such requests
24+
rapidly with minimum effort.
25+
26+
So it all boils down to this: Be honest about tool usage!
27+
328
## Background
429

5-
From time to time I do get pull requests where the author has done
6-
little else than running some tool on the code and submitting it as a
7-
pull request. Those pull requests may have value to the project, but
8-
it's dishonest to not be transparent about it; teaching me how to run
9-
the tool and integrating it into the CI workflow may have a bigger
10-
value than the changes provided by the tool. Recently I've also
11-
started receiving pull requests with code changes generated by AI (and
12-
I've seen people posting screenshots of simple questions and answers
13-
from ChatGPT in forum discussions, without contributing anything else).
30+
The "30 second effort pull request" mentioned above may have value to
31+
the project, but it's dishonest to not be transparent about it.
32+
Sometimes, teaching me how to run the tool and integrating it into the
33+
CI workflow may have a bigger value than the changes provided by the
34+
tool.
1435

1536
Starting in 2025-11, I've spent quite some time testing Claude. I'm
1637
positively surprised, it's doing a much better job than what I had
1738
expected. The AI may do things a lot faster, smarter and better than
1839
a good coder. Sometimes. Other times it may spend a lot of "tokens"
19-
and a long time coming up with sub-optimal solutions, or even
20-
solutions that doesn't work at all. Perhaps at some time in the near
21-
future the AI will do the developer profession completely obsoleted -
22-
but as of 2026-02, my experiences is that the AI performs best when
23-
being "supervised" and "guided" by a good coder knowing the project.
40+
and a long time coming up with sub-optimal or really bad solutions.
41+
42+
Perhaps at some time in the near future the AI will do the developer
43+
profession completely obsoleted - but as of 2026-02, my experiences is
44+
that the AI performs best when being "supervised" and "guided" by a
45+
good coder knowing the project.
46+
47+
## Bugfixes are (most often) welcome
48+
49+
Over the past month, playing with a "max" subscription with Claude,
50+
I've made it into a rule that when I stumble upon some weird bug in
51+
some software or libraries I'm using or dependent on, I always ask
52+
Claude to analyze the bug, check the outstanding issues in the
53+
project, either create a new issue or consider if there is anything of
54+
value to add to an existing issue, and come up with a pull-request. Being a bit aware of the
2455

25-
## The rules
56+
## General rules
2657

2758
* Do **respect the maintainers time**. If/when the maintainer gets
2859
overwhelmed by pull requests of questionable quality or pull

CHANGELOG.md

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
1818

1919
### Highlights
2020

21-
Version 3.0 should be fully backward-compatible with version 2.x - but there are massive code changes in version 3.0:
21+
There shouldn't be many breaking changes in version 3.0, but there are massive code changes in version 3.0:
2222

23-
* "Black style" has been replaced with ruff. This causes quite some changes in the code.
24-
* Version 3.0 introduces **full async support** using a Sans-I/O architecture. The same domain objects (Calendar, Event, Todo, etc.) now work with both synchronous and asynchronous clients. The async client uses niquests by default; httpx is also supported for projects that already have it as a dependency.
25-
* Quite some refactoring work has been done
26-
* Some work has been put down ensuring better consistency in the method names. Version 3.0 should be backward-compatible with version 2.0, so the old methods still work, but are deprecated.
23+
* **Full async support** using a Sans-I/O architecture. The same domain objects (Calendar, Event, Todo, etc.) now work with both synchronous and asynchronous clients. The async client uses niquests by default; httpx is also supported for projects that already have it as a dependency.
24+
* **Sans-I/O architecture** -- internal refactoring separates protocol logic (XML building/parsing) from I/O into a layered architecture: protocol layer (`caldav/protocol/`), operations layer (`caldav/operations/`), and response handling (`caldav/response.py`). This enables code reuse between sync and async implementations and improves testability.
25+
* **Lazy imports** -- `import caldav` is now significantly faster due to PEP 562 lazy loading. Heavy dependencies (lxml, niquests, icalendar) are deferred until first use. (https://github.com/python-caldav/caldav/issue/621)
26+
* **API naming consistency** -- methods have been renamed for consistency. Server-fetching methods use `get_` prefix, capability checks use `supports_*()`. Old method names still work but are deprecated.
27+
* **Ruff replaces Black** -- code formatting now uses ruff instead of Black, causing cosmetic changes throughout the codebase.
28+
* **Expanded compatibility hints** -- server-specific workarounds added for Zimbra, Bedework, CCS (Apple CalendarServer), Davis, DAViCal, GMX, ecloud, Synology, Posteo, PurelyMail, and more.
29+
* Quite some other refactoring work has been done.
2730

2831
### Breaking Changes
2932

3033
(Be aware that some of the 2.x minor-versions also tagged some "Potentially Breaking Changes")
3134

3235
* **Minimum Python version**: Python 3.10+ is now required (was 3.8+).
3336
* **Test Server Configuration**: `tests/conf.py` has been removed and `conf_private.py` will be ignored. See the Test Framework section below.
37+
* **`object.py` has been removed** as well as the `from caldav.object import *` in `caldav/__init__.py`. Some classes etc may appear to be missing, but the most important ones should still exist directly in the `caldav.*` namespace.
38+
* **Config file parse errors now raise exceptions**: `caldav.config.read_config()` now raises `ValueError` on YAML/JSON parse errors instead of logging and returning an empty dict. This ensures config errors are detected early.
3439

3540
### Deprecated
3641

@@ -69,7 +74,7 @@ Additionally, direct `DAVClient()` instantiation should migrate to `get_davclien
6974

7075
### Added
7176

72-
* **Full async API** - New `AsyncDAVClient` and async-compatible domain objects:
77+
* **Full async API** -- New `AsyncDAVClient` and async-compatible domain objects:
7378
```python
7479
from caldav.async_davclient import get_davclient
7580

@@ -79,20 +84,25 @@ Additionally, direct `DAVClient()` instantiation should migrate to `get_davclien
7984
for cal in calendars:
8085
events = await cal.get_events()
8186
```
82-
* **Sans-I/O architecture** - Internal refactoring separates protocol logic from I/O:
83-
- Protocol layer (`caldav/protocol/`): Pure functions for XML building/parsing
84-
- Operations layer (`caldav/operations/`): High-level CalDAV operations
85-
- This enables code reuse between sync and async implementations
87+
* **Sans-I/O architecture** -- Internal refactoring separates protocol logic from I/O:
88+
- Protocol layer (`caldav/protocol/`): Pure functions for XML building/parsing with typed dataclasses (DAVRequest, DAVResponse, PropfindResult, CalendarQueryResult)
89+
- Operations layer (`caldav/operations/`): Sans-I/O business logic for CalDAV operations (properties, search, calendar management, principal discovery)
90+
- Response layer (`caldav/response.py`): Shared `BaseDAVResponse` for sync/async
91+
- Data state (`caldav/datastate.py`): Strategy pattern for managing data representations (raw string, icalendar, vobject) -- avoids unnecessary parse/serialize cycles
92+
* **Lazy imports (PEP 562)** -- `import caldav` is now fast. Heavy dependencies (lxml, niquests, icalendar) are deferred until first use. https://github.com/python-caldav/caldav/pull/621
93+
* **`DAVObject.name` deprecated** -- use `get_display_name()` instead. The old `.name` property now emits `DeprecationWarning`.
8694
* Added python-dateutil and PyYAML as explicit dependencies (were transitive)
8795
* Quite some methods have been renamed for consistency and to follow best current practices. See the deprecation section.
8896
* `Calendar` class now accepts a `name` parameter in its constructor, addressing a long-standing API inconsistency (https://github.com/python-caldav/caldav/issues/128)
89-
* **Data representation API** - New efficient data access via `CalendarObjectResource` properties (https://github.com/python-caldav/caldav/issues/613):
90-
- `.icalendar_instance` - parsed icalendar object (lazy loaded)
91-
- `.vobject_instance` - parsed vobject object (lazy loaded)
92-
- `.data` - raw iCalendar string
97+
* **Data representation API** -- New efficient data access via `CalendarObjectResource` properties (https://github.com/python-caldav/caldav/issues/613):
98+
- `.icalendar_instance` -- parsed icalendar object (lazy loaded)
99+
- `.vobject_instance` -- parsed vobject object (lazy loaded)
100+
- `.data` -- raw iCalendar string
101+
- Context managers `edit_icalendar_instance()` and `edit_vobject_instance()` for safe mutable access
102+
- `get_data()`, `get_icalendar_instance()`, `get_vobject_instance()` return copies for read-only access
93103
- Internal `DataState` class manages caching between formats
94-
* **CalendarObjectResource.id property** - Returns the UID of calendar objects (https://github.com/python-caldav/caldav/issues/515)
95-
* **calendar.searcher() API** - Factory method for advanced search queries (https://github.com/python-caldav/caldav/issues/590):
104+
* **CalendarObjectResource.id property** -- Returns the UID of calendar objects (https://github.com/python-caldav/caldav/issues/515)
105+
* **calendar.searcher() API** -- Factory method for advanced search queries (https://github.com/python-caldav/caldav/issues/590):
96106
```python
97107
searcher = calendar.searcher()
98108
searcher.add_filter(...)
@@ -120,22 +130,54 @@ Additionally, direct `DAVClient()` instantiation should migrate to `get_davclien
120130
### Fixed
121131

122132
* RFC 4791 compliance: Don't send Depth header for calendar-multiget REPORT (clients SHOULD NOT send it, but servers MUST ignore it per §7.9)
133+
* Fixed `ssl_verify_cert` not passed through in `get_sync_client` and `get_async_client`
134+
* Fixed `_derive_from_subfeatures` partial-config derivation bug
135+
* Fixed feature name parsing when names include `compatibility_hints.` prefix
136+
* Fixed recursive `_search_with_comptypes` when `search.comp-type` is broken
137+
* Fixed pending todo search on servers with broken comp-type filtering
138+
* Fixed URL path quoting when extracting calendars from PROPFIND results
139+
* Removed spurious warning on URL path mismatch, deduplicated `get_properties`
140+
* Fixed `create-calendar` feature incorrectly derived as unsupported
141+
* Fixed various async test issues (awaiting sync calls, missing feature checks, authorization error handling)
142+
* Fixed `search.category` features to use correct `search.text.category` names
123143

124144
### Changed
125145

126146
* Sync client (`DAVClient`) now shares common code with async client via `BaseDAVClient`
127147
* Response handling unified in `BaseDAVResponse` class
148+
* Search refactored to use generator-based Sans-I/O pattern -- `_search_impl` yields `(SearchAction, data)` tuples consumed by sync or async wrappers
128149
* Test configuration migrated from legacy `tests/conf.py` to new `tests/test_servers/` framework
150+
* Configuration system expanded: `get_connection_params()` provides unified config discovery with clear priority (explicit params > test server config > env vars > config file)
151+
* `${VAR}` and `${VAR:-default}` environment variable expansion in config values
152+
* Ruff replaces Black for code formatting
153+
* `caldav/objects.py` backward-compatibility shim removed (imports go directly to submodules)
129154

130155
### Test Framework
131156

132-
* Fixed Nextcloud Docker test server tmpfs permissions race condition
133-
* Added deptry for dependency verification in CI
134-
* The test server framework has been refactored with a new `tests/test_servers/` module. It provides **YAML-based server configuration**: see `tests/test_servers/__init__.py` for usage
157+
* **New `tests/test_servers/` module** -- Complete rewrite of test infrastructure:
158+
- `TestServer` base class hierarchy (EmbeddedTestServer, DockerTestServer, ExternalTestServer)
159+
- YAML-based server configuration (`tests/caldav_test_servers.yaml.example`)
160+
- `ServerRegistry` for server discovery and lifecycle management
161+
- `client_context()` and `has_test_servers()` helpers
162+
* **New Docker test servers**: CCS (Apple CalendarServer), DAViCal, Davis, Zimbra
163+
* **Updated Docker configs**: Baikal, Cyrus, Nextcloud, SOGo
135164
* Added pytest-asyncio for async test support
165+
* Added deptry for dependency verification in CI
166+
* Added lychee link-check workflow
167+
* Added `convert_conf_private.py` migration tool for old config format
168+
* Removed `tests/conf.py`, `tests/conf_baikal.py`, `tests/conf_private.py.EXAMPLE`
169+
* **New test suites**:
170+
- `test_async_davclient.py` (821 lines) -- Async client unit tests
171+
- `test_async_integration.py` (466 lines) -- Async integration tests
172+
- `test_operations_*.py` (6 files) -- Operations layer unit tests
173+
- `test_protocol.py` (319 lines) -- Protocol layer unit tests
174+
- `test_lazy_import.py` (141 lines) -- PEP 562 lazy import verification
175+
* Fixed Nextcloud Docker test server tmpfs permissions race condition
136176

137177
### GitHub Pull Requests Merged
138178

179+
* #621 - Lazy-load heavy dependencies to speed up import caldav
180+
* #622 - Fix overlong inline literal, replace hyphens with en-dashes
139181
* #607 - Add deptry for dependency verification
140182

141183
### GitHub Issues Closed
@@ -148,7 +190,25 @@ Additionally, direct `DAVClient()` instantiation should migrate to `get_davclien
148190

149191
### Security
150192

151-
Nothing to report.
193+
* UUID1 usage in UID generation (`calendarobject_ops.py`) may embed the host MAC address in calendar UIDs. Since calendar events are shared with third parties, this is a privacy concern. Planned fix: switch to UUID4.
194+
195+
### Compatibility Hints Expanded
196+
197+
Server-specific workarounds have been significantly expanded. Profiles added or updated for:
198+
199+
* **Zimbra** -- search.is-not-defined, delete-calendar, recurrences.count, case-sensitive search
200+
* **Bedework** -- save-load.journal, save-load.todo.recurrences.thisandfuture, search.recurrences.expanded.todo, search.time-range.alarm
201+
* **CCS (Apple CalendarServer)** -- save-load.journal unsupported, various search hints
202+
* **Davis** -- principal-search at parent level, mixed-calendar features
203+
* **GMX** -- rate-limit, basepath correction
204+
* **ecloud** -- create-calendar unsupported, search.is-not-defined, case-sensitive
205+
* **Synology** -- is-not-defined, wipe-calendar cleanup
206+
* **SOGo** -- save-load.journal ungraceful, case-insensitive, delete-calendar
207+
* **Posteo** -- search.combined-is-logical-and unsupported
208+
* **PurelyMail** -- search.time-range.todo ungraceful
209+
* **DAViCal** -- various search and sync hints
210+
* **Xandikos** -- freebusy-query now supported in v0.3.3
211+
* **Baikal/Radicale** -- case-sensitive search, principal-search features
152212

153213
## [2.2.6] - [2026-02-01]
154214

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ include COPYING.*
22
include *.md
33
recursive-include tests caldav
44
exclude tests/conf_private.py
5+
exclude tests/caldav_test_servers.yaml
6+
exclude tests/tmp_caldav_test_servers.yaml

RELEASE-HOWTO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ This is most likely not complete, but should explain some of the "silly" steps a
4545
* Forgetting to add new files to the git repo
4646
* Having checked out a branch or tag or something, and tagging that as the new release rather than the latest HEAD.
4747
* Forgetting to push to pypi, or pushing something else than the tagged revision to pypi
48-
* Pushing out junk files in the pypi-release (i.e. .pyc-files, log files, temp files, `tests/conf_private.py`, etc
48+
* Pushing out junk files in the pypi-release (i.e. .pyc-files, log files, temp files, `tests/conf_private.py`, `tests/caldav_test_servers.yaml`, etc
4949
* Not adding the release to the "github releases" (I don't care much about this feature, but apparently some people check there to find the latest release version)

0 commit comments

Comments
 (0)