Last Updated: January 2026 Status: Phase 1-3 Complete
The Sans-I/O refactoring has been significantly completed. Here's the current state:
┌─────────────────────────────────────────────────────┐
│ High-Level Objects (Calendar, Principal, etc.) │
│ → Use DAVResponse.results (parsed protocol types) │
├─────────────────────────────────────────────────────┤
│ DAVClient (sync) / AsyncDAVClient (async) │
│ → Handle HTTP via niquests │
│ → Use protocol layer for XML building/parsing │
│ → ~65% code duplication (problem!) │
├─────────────────────────────────────────────────────┤
│ Protocol Layer (caldav/protocol/) │
│ → xml_builders.py: Pure functions for XML bodies │
│ → xml_parsers.py: Pure functions for parsing │
│ → types.py: DAVRequest, DAVResponse, result types │
│ → NO I/O - just data transformations │
└─────────────────────────────────────────────────────┘
-
Protocol Layer (
caldav/protocol/):xml_builders.py- All XML request body buildingxml_parsers.py- All response parsingtypes.py- DAVRequest, DAVResponse, PropfindResult, etc.- Used by both sync and async clients
-
Response Parsing:
DAVResponse.resultsprovides parsed protocol typesfind_objects_and_props()deprecated but still works
-
Both Clients Work:
DAVClient- Full sync API with backward compatibilityAsyncDAVClient- Async API (not yet released)
After Phase 2-3 refactoring, duplication has been significantly reduced:
| Component | Status |
|---|---|
extract_auth_types() |
✅ Extracted to caldav/lib/auth.py |
select_auth_type() |
✅ Extracted to caldav/lib/auth.py |
CONNKEYS |
✅ Single source in caldav/config.py |
| Response initialization | ✅ Consolidated in BaseDAVResponse._init_from_response() |
| HTTP method wrappers | ~95% similar (acceptable - sync/async signatures differ) |
| Constructor logic | ~85% similar (acceptable - client setup differs) |
The original plan proposed an io/ layer abstraction. This was abandoned because:
- Added complexity without clear benefit
- Both clients use niquests which handles sync/async natively
- The protocol layer already provides the "Sans-I/O" separation
New approach: Extract identical/similar code to shared modules.
The protocol layer is working:
caldav/protocol/xml_builders.py- XML request body constructioncaldav/protocol/xml_parsers.py- Response parsingcaldav/protocol/types.py- Type definitions
Goal: Reduce duplication without architectural changes.
Completed:
caldav/lib/auth.pycreated with:extract_auth_types()- Parse WWW-Authenticate headersselect_auth_type()- Choose best auth method from options
CONNKEYSuses single source incaldav/config.py- Both clients import and use these shared utilities
Goal: Move common response logic to BaseDAVResponse.
Completed:
BaseDAVResponse._init_from_response()now contains all shared initialization:- Headers and status extraction
- XML parsing with etree
- Content-type validation
- CRLF normalization
- Error handling
BaseDAVResponse.rawproperty moved from subclassesDAVResponse.__init__reduced to single delegation callAsyncDAVResponse.__init__reduced to single delegation call- Eliminated ~150 lines of duplicated code
Status: Deferred - evaluate after Phase 2-3.
A BaseDAVClient could reduce duplication further, but:
- Sync/async method signatures differ fundamentally
- May not be worth the complexity
- Evaluate after simpler refactoring is done
| File | Changes |
|---|---|
caldav/lib/auth.py |
✅ NEW: Shared auth utilities |
caldav/config.py |
✅ CONNKEYS single source |
caldav/davclient.py |
✅ Uses shared utilities, simplified DAVResponse |
caldav/async_davclient.py |
✅ Uses shared utilities, simplified AsyncDAVResponse |
caldav/response.py |
✅ BaseDAVResponse with _init_from_response() and raw property |
These were from the abandoned io/ layer approach:
| File | Reason Removed |
|---|---|
caldav/io/ |
Never integrated, io/ abstraction abandoned |
caldav/protocol_client.py |
Redundant with protocol layer |
caldav/protocol/operations.py |
CalDAVProtocol class never used |
- ✅ Protocol layer is single source of truth for XML
- ✅ No duplicate utility functions between clients (auth.py)
- ✅ Shared constants accessible to both clients (config.py)
- ✅ Common response logic in BaseDAVResponse
- ✅ All existing tests pass
- ✅ Backward compatibility maintained for sync API
- Phase 1: ✅ Complete
- Phase 2: ✅ Complete
- Phase 3: ✅ Complete
- Phase 4: Evaluate if further refactoring is needed