Skip to content

Commit e224f39

Browse files
committed
refactor: move response-related logic from protocol/ into response.py
- Moved all result dataclasses (PropfindResult, CalendarQueryResult, SyncCollectionResult, MultistatusResponse) and XML parse functions (_parse_multistatus, _parse_propfind_response, etc.) from protocol/types.py and protocol/xml_parsers.py into response.py, which is the natural home for response-parsing logic. - Deleted protocol/types.py and protocol/xml_parsers.py entirely. protocol/__init__.py now re-exports the result types from response.py. - Added parse_propfind(), parse_calendar_query(), parse_sync_collection() as instance methods on BaseDAVResponse so callers can simply do `response.parse_propfind()` instead of passing raw bytes, status code and huge_tree as arguments to a standalone function. - Dropped MultiGetResult dataclass — it was an exact structural copy of CalendarQueryResult with no distinct semantics and was never imported in production code. - Updated async_davclient.py and davclient.py to use the new instance methods (eliminating the raw-bytes boilerplate at each call site). - Fixed unit tests that were mocking _async_get_vcal_address directly instead of the dual-mode get_vcal_address; and removed the TestDAVTypes class that tested now-deleted Sans-IO types (DAVRequest, DAVResponse). prompt: All response-related logic in the protocol directory should be moved back to the response class. followup-prompt: Make sure there is no duplicated code or logic. I feel more comfortable having things organized as methods under the Response class than having them as pure functions, consider if any of the functions moved over would fit as methods. Those new dataclasses, make sure they are either used consequently whenever it makes sense, or that they are dropped completely. Make me an overview of places where those new functions and classes are used, when not being called from the Response class. AI Prompts: claude-sonnet-4-6: All response-related logic in the protocol directory should be moved back to the response class.
1 parent 7785774 commit e224f39

8 files changed

Lines changed: 357 additions & 845 deletions

File tree

caldav/async_davclient.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,14 @@ def auth_flow(self, request):
7171
from caldav.lib import error
7272
from caldav.lib.python_utilities import to_wire
7373
from caldav.lib.url import URL
74-
from caldav.protocol.types import (
75-
CalendarQueryResult,
76-
PropfindResult,
77-
)
7874
from caldav.protocol.xml_builders import (
7975
_build_calendar_multiget_body,
8076
_build_calendar_query_body,
8177
_build_propfind_body,
8278
_build_sync_collection_body,
8379
)
84-
from caldav.protocol.xml_parsers import (
85-
_parse_calendar_query_response,
86-
_parse_propfind_response,
87-
_parse_sync_collection_response,
88-
)
8980
from caldav.requests import HTTPBearerAuth
90-
from caldav.response import BaseDAVResponse
81+
from caldav.response import BaseDAVResponse, CalendarQueryResult, PropfindResult
9182

9283
log = logging.getLogger("caldav")
9384

@@ -562,14 +553,8 @@ async def propfind(
562553
final_headers = self._build_method_headers("PROPFIND", depth, headers)
563554
response = await self.request(url or str(self.url), "PROPFIND", body, final_headers)
564555

565-
# Parse response using protocol layer
566556
if response.status in (200, 207) and response._raw:
567-
raw_bytes = (
568-
response._raw if isinstance(response._raw, bytes) else response._raw.encode("utf-8")
569-
)
570-
response.results = _parse_propfind_response(
571-
raw_bytes, response.status, response.huge_tree
572-
)
557+
response.results = response.parse_propfind()
573558

574559
return response
575560

@@ -779,14 +764,8 @@ async def calendar_query(
779764
url or str(self.url), "REPORT", body.decode("utf-8"), final_headers
780765
)
781766

782-
# Parse response using protocol layer
783767
if response.status in (200, 207) and response._raw:
784-
raw_bytes = (
785-
response._raw if isinstance(response._raw, bytes) else response._raw.encode("utf-8")
786-
)
787-
response.results = _parse_calendar_query_response(
788-
raw_bytes, response.status, response.huge_tree
789-
)
768+
response.results = response.parse_calendar_query()
790769

791770
return response
792771

@@ -816,14 +795,8 @@ async def calendar_multiget(
816795
url or str(self.url), "REPORT", body.decode("utf-8"), final_headers
817796
)
818797

819-
# Parse response using protocol layer
820798
if response.status in (200, 207) and response._raw:
821-
raw_bytes = (
822-
response._raw if isinstance(response._raw, bytes) else response._raw.encode("utf-8")
823-
)
824-
response.results = _parse_calendar_query_response(
825-
raw_bytes, response.status, response.huge_tree
826-
)
799+
response.results = response.parse_calendar_query()
827800

828801
return response
829802

@@ -855,14 +828,8 @@ async def sync_collection(
855828
url or str(self.url), "REPORT", body.decode("utf-8"), final_headers
856829
)
857830

858-
# Parse response using protocol layer
859831
if response.status in (200, 207) and response._raw:
860-
raw_bytes = (
861-
response._raw if isinstance(response._raw, bytes) else response._raw.encode("utf-8")
862-
)
863-
sync_result = _parse_sync_collection_response(
864-
raw_bytes, response.status, response.huge_tree
865-
)
832+
sync_result = response.parse_sync_collection()
866833
response.results = sync_result.changed
867834
response.sync_token = sync_result.sync_token
868835

caldav/davclient.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -717,16 +717,8 @@ def propfind(
717717
headers = {"Depth": str(depth)}
718718
response = self.request(url or str(self.url), "PROPFIND", body, headers)
719719

720-
# Parse response using protocol layer
721720
if response.status in (200, 207) and response._raw:
722-
from caldav.protocol.xml_parsers import _parse_propfind_response
723-
724-
raw_bytes = (
725-
response._raw if isinstance(response._raw, bytes) else response._raw.encode("utf-8")
726-
)
727-
response.results = _parse_propfind_response(
728-
raw_bytes, response.status, response.huge_tree
729-
)
721+
response.results = response.parse_propfind()
730722
return response
731723

732724
def proppatch(self, url: str, body: str, dummy: None = None) -> DAVResponse:

caldav/protocol/__init__.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
"""
2-
Sans-I/O CalDAV protocol implementation.
2+
CalDAV protocol layer — XML builders and (legacy) re-exports.
33
4-
This module provides protocol-level operations without any I/O.
5-
It builds requests and parses responses as pure data transformations.
6-
7-
The protocol layer is organized into:
8-
- types: Core data structures (DAVRequest, DAVResponse, result types)
9-
- xml_builders: Internal functions to build XML request bodies
10-
- xml_parsers: Internal functions to parse XML response bodies
11-
12-
Both DAVClient (sync) and AsyncDAVClient (async) use these shared
13-
functions for XML building and parsing, ensuring consistent behavior.
14-
15-
Note: The xml_builders and xml_parsers functions are internal implementation
16-
details and should not be used directly. Use the client methods instead.
4+
The xml_builders submodule provides functions to build CalDAV request XML.
5+
Result dataclasses previously defined here have been moved to caldav.response.
176
"""
187

19-
from .types import (
8+
from caldav.response import (
209
CalendarQueryResult,
21-
DAVMethod,
22-
DAVRequest,
23-
DAVResponse,
24-
MultiGetResult,
2510
MultistatusResponse,
26-
PrincipalInfo,
2711
PropfindResult,
2812
SyncCollectionResult,
2913
)
3014

3115
__all__ = [
32-
# Enums
33-
"DAVMethod",
34-
# Request/Response
35-
"DAVRequest",
36-
"DAVResponse",
37-
# Result types
3816
"CalendarQueryResult",
39-
"MultiGetResult",
4017
"MultistatusResponse",
41-
"PrincipalInfo",
4218
"PropfindResult",
4319
"SyncCollectionResult",
4420
]

caldav/protocol/types.py

Lines changed: 0 additions & 221 deletions
This file was deleted.

0 commit comments

Comments
 (0)