Skip to content

Commit 8ea2757

Browse files
tobixenclaude
andcommitted
fix: Restore communication dump to sync and async request paths
The debug_dump_communication / PYTHON_CALDAV_COMMDUMP feature was lost during the v3.0 refactor. Extract it into a shared _dump_communication() helper in caldav.lib.error so the logic is not duplicated between the sync (_sync_request) and async (_async_request) code paths. Fixes #638 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 20c1640 commit 8ea2757

5 files changed

Lines changed: 79 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
1414

1515
## [Unreleased]
1616

17+
### Fixed
18+
19+
* Communication dump (`PYTHON_CALDAV_COMMDUMP` / `debug_dump_communication`) was accidentally dropped during the v3.0 refactor. Restored, with the dump logic extracted into a shared helper so both the sync and async code paths benefit. Fixes https://github.com/python-caldav/caldav/issues/638
20+
1721
### Documentation
1822

1923
I've decided to try to stick to the conventionalcommits standard. This is documented in CONTRIBUTING.md. We'll see how many days it takes before I forget about it ...

caldav/async_davclient.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ async def _async_request(
527527
if response.status in (401, 403):
528528
self._raise_authorization_error(str(url_obj), response)
529529

530+
if error.debug_dump_communication:
531+
error._dump_communication(method, url, combined_headers, body, response)
532+
530533
return response
531534

532535
# ==================== HTTP Method Wrappers ====================

caldav/davclient.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,10 @@ def _sync_request(
936936
self._raise_authorization_error(str(url_obj), r)
937937

938938
response = DAVResponse(r, self)
939+
940+
if error.debug_dump_communication:
941+
error._dump_communication(method, url, combined_headers, body, response)
942+
939943
return response
940944

941945

caldav/lib/error.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ def errmsg(r) -> str:
3434
return "%s %s\n\n%s" % (r.status, r.reason, r.raw)
3535

3636

37+
def _dump_communication(method: str, url: str, combined_headers: dict, body, response) -> None:
38+
"""Write a request/response exchange to a uniquely-named temp file.
39+
40+
Called when ``debug_dump_communication`` is truthy. Works for both the
41+
sync and async code paths because it only depends on the attributes that
42+
``BaseDAVResponse`` exposes: ``.status``, ``.reason``, ``.headers``,
43+
``.tree``, and ``._raw``.
44+
"""
45+
import datetime
46+
from tempfile import NamedTemporaryFile
47+
48+
from lxml import etree
49+
50+
from caldav.lib.python_utilities import to_wire
51+
52+
with NamedTemporaryFile(prefix="caldavcomm", delete=False) as commlog:
53+
commlog.write(b"=" * 80 + b"\n")
54+
commlog.write(f"{datetime.datetime.now():%FT%H:%M:%S}".encode())
55+
commlog.write(b"\n====>\n")
56+
commlog.write(f"{method} {url}\n".encode())
57+
commlog.write(b"\n".join(to_wire(f"{k}: {v}") for k, v in combined_headers.items()))
58+
commlog.write(b"\n\n")
59+
commlog.write(to_wire(body) or b"")
60+
commlog.write(b"\n<====\n")
61+
commlog.write(f"{response.status} {response.reason}\n".encode())
62+
commlog.write(b"\n".join(to_wire(f"{k}: {v}") for k, v in response.headers.items()))
63+
commlog.write(b"\n\n")
64+
if response.tree is not None:
65+
commlog.write(to_wire(etree.tostring(response.tree, pretty_print=True)))
66+
else:
67+
commlog.write(to_wire(response._raw) or b"")
68+
commlog.write(b"\n")
69+
70+
3771
def weirdness(*reasons):
3872
from caldav.lib.debug import xmlstring
3973

tests/test_caldav_unit.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,40 @@ def testNonValidXMLNoContentLength(self, mocked):
415415
with pytest.raises(lxml.etree.XMLSyntaxError):
416416
client.request("/")
417417

418+
@mock.patch("caldav.davclient.requests.Session.request")
419+
def testCommunicationDump(self, mocked):
420+
"""
421+
ref https://github.com/python-caldav/caldav/issues/638
422+
When PYTHON_CALDAV_COMMDUMP (or debug_dump_communication) is set,
423+
request/response data should be written to a temp file.
424+
"""
425+
import glob
426+
import os
427+
import tempfile
428+
429+
mocked().status_code = 200
430+
mocked().headers = {"Content-Type": "text/plain"}
431+
mocked().content = b""
432+
mocked().reason = "OK"
433+
mocked().reason_phrase = None
434+
435+
from caldav.lib import error as caldav_error
436+
437+
old_value = caldav_error.debug_dump_communication
438+
caldav_error.debug_dump_communication = True
439+
try:
440+
client = DAVClient(url="http://test.example.com/")
441+
before = set(glob.glob(os.path.join(tempfile.gettempdir(), "caldavcomm*")))
442+
client.request("/")
443+
after = set(glob.glob(os.path.join(tempfile.gettempdir(), "caldavcomm*")))
444+
new_files = after - before
445+
assert len(new_files) == 1
446+
content = open(list(new_files)[0], "rb").read()
447+
assert b"GET /" in content
448+
assert b"200 OK" in content
449+
finally:
450+
caldav_error.debug_dump_communication = old_value
451+
418452
def testPathWithEscapedCharacters(self):
419453
xml = b"""<D:multistatus xmlns:D="DAV:" xmlns:caldav="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:ical="http://apple.com/ns/ical/">
420454
<D:response xmlns:carddav="urn:ietf:params:xml:ns:carddav" xmlns:cm="http://cal.me.com/_namespace/" xmlns:md="urn:mobileme:davservices">

0 commit comments

Comments
 (0)