Skip to content

Commit 19afc58

Browse files
committed
Moving functionality from tests.conf
* Moved the server compatibility database from test directory to the caldav directory. This is needed for #402 and for my caldav-server-tester script. * Now that the caldav-server-tester script has been moved into a separate project, I still need it to access my test calendar configuration. Made a temp hack for this, will work more on it later. * While working at the above, I made #485 and started preparing a bit for it. * piggybacking in a fix for #465 (comment)
1 parent c670f47 commit 19afc58

10 files changed

Lines changed: 145 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ Python 3.7 is no longer tested - but it should work. Please file a bug report i
2121
### Changed
2222

2323
* Some exotic servers may return object URLs on search, but it does not work out to fetch the calendar data. Now it will log an error instead of raising an error in such cases.
24+
* The `tests/compatibility_issues.py` has been moved to `caldav/compatibility_hints.py`, this to make it available for a caldav-server-tester-tool that I'm splitting off to a separate project/repository, and also to make https://github.com/python-caldav/caldav/issues/402 possible.
2425

2526
#### Refactoring
2627

2728
* Minor code cleanups by github user @ArtemIsmagilov in https://github.com/python-caldav/caldav/pull/456
29+
* The very much overgrown `objects.py`-file has been split into three.
2830

2931
#### Test framework
3032

@@ -40,6 +42,7 @@ Python 3.7 is no longer tested - but it should work. Please file a bug report i
4042

4143
### Added
4244

45+
* Work in progress: `auto_conn`, `auto_calendar` and `auto_calendars` may read caldav connection and calendar configuration from a config file, environmental variables or other sources. Currently I've made the minimal possible work to be able to test the caldav-server-tester script.
4346
* By now `calendar.search(..., sort_keys=("DTSTART")` will work. Sort keys expects a list or a tuple, but it's easy to send an attribute by mistake. https://github.com/python-caldav/caldav/issues/448 https://github.com/python-caldav/caldav/pull/449
4447
* Compatibility workaround: If `event.load()` fails, it will retry the load by doing a multiget - https://github.com/python-caldav/caldav/pull/475 - https://github.com/python-caldav/caldav/issues/459
4548
* The `class_`-parameter now works when sending data to `save_event()` etc.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
# fmt: off
2+
"""This text was updated 2025-05-17. The plan is to reorganize this
3+
file a lot over the next few months, see
4+
https://github.com/python-caldav/caldav/issues/402
5+
6+
This file serves as a database of different compatibility issues we've
7+
encountered while working on the caldav library, and descriptions on
8+
how the well-known servers behave.
9+
10+
As for now, this is a list of binary "flags" that could be turned on
11+
or off. My experience is that there are often neuances, so the
12+
compatibility matrix will be changed from being a list of flags to a
13+
key=value store in the near future (at least, that's the plan).
14+
15+
The issues may be grouped together, maybe even organized
16+
hierarchically. I did consider organizing the compatibility issues in
17+
some more advanced way, but I don't want to overcomplicate things - I
18+
will try out the key-value-approach first.
19+
"""
220
## The lists below are specifying what tests should be skipped or
321
## modified to accept non-conforming resultsets from the different
422
## calendar servers. In addition there are some hacks in the library

caldav/davclient.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env python
22
import logging
3+
import os
34
import sys
45
from types import TracebackType
56
from typing import Any
@@ -44,6 +45,24 @@
4445
else:
4546
from typing import Self
4647

48+
## TODO: this is also declared in davclient.DAVClient.__init__(...)
49+
## TODO: it should be consolidated, duplication is a bad thing
50+
## TODO: and it's almost certain that we'll forget to update this list
51+
CONNKEYS = set(
52+
(
53+
"url",
54+
"proxy",
55+
"username",
56+
"password",
57+
"timeout",
58+
"headers",
59+
"huge_tree",
60+
"ssl_verify_cert",
61+
"ssl_cert",
62+
"auth",
63+
)
64+
)
65+
4766

4867
class DAVResponse:
4968
"""
@@ -75,9 +94,11 @@ def __init__(
7594

7695
content_type = self.headers.get("Content-Type", "")
7796
xml = ["text/xml", "application/xml"]
78-
no_xml = ["text/plain", "text/calendar"]
97+
no_xml = ["text/plain", "text/calendar", "application/octet-stream"]
7998
expect_xml = any((content_type.startswith(x) for x in xml))
8099
expect_no_xml = any((content_type.startswith(x) for x in no_xml))
100+
if content_type and not expect_xml and not expect_no_xml:
101+
error.weirdness(f"Unexpected content type: {content_type}")
81102
try:
82103
content_length = int(self.headers["Content-Length"])
83104
except:
@@ -818,3 +839,82 @@ def request(
818839
commlog.write(b"\n")
819840

820841
return response
842+
843+
844+
def auto_calendars(
845+
configfile: str = f"{os.environ.get('HOME')}/.config/calendar.conf",
846+
testconfig=False,
847+
environment: bool = True,
848+
config_data: dict = None,
849+
config_name: str = None,
850+
) -> Iterable["Calendar"]:
851+
"""
852+
This will replace plann.lib.findcalendars()
853+
"""
854+
raise NotImplementedError("auto_calendars not implemented yet")
855+
856+
857+
def auto_calendar(*largs, **kwargs) -> Iterable["Calendar"]:
858+
"""
859+
Alternative to auto_calendars - in most use cases, one calendar suffices
860+
"""
861+
return next(auto_calendars(*largs, **kwargs), None)
862+
863+
864+
def auto_conn(
865+
configfile: str = f"{os.environ.get('HOME')}/.config/calendar.conf",
866+
testconfig=False,
867+
environment: bool = True,
868+
config_data: dict = None,
869+
name: str = None,
870+
) -> "DAVClient":
871+
"""
872+
Normally you would like to look into auto_calendars or
873+
auto_calendar instead. However, in some cases it's needed
874+
with a DAVClient object rather than a Calendar object.
875+
876+
This function will yield a DAVClient object. It will not try to
877+
connect (see auto_calendars for that). It will read configuration
878+
from various sources, dependent on the parameters given, in this
879+
order:
880+
881+
* Data from the given dict
882+
* Environment variables prepended with "CALDAV_"
883+
* Data from `./tests/conf.py` or `./conf.py` (this includes the possibility to spin up a test server)
884+
* Configuration file. Documented in the plann project as for now. (TODO - move it)
885+
886+
"""
887+
if config_data:
888+
return DAVClient(**config_data)
889+
890+
if testconfig:
891+
sys.path.insert(0, "tests")
892+
sys.path.insert(1, ".")
893+
## TODO: move the code from client into here
894+
try:
895+
from conf import client
896+
897+
try:
898+
idx = int(name)
899+
except ValueError:
900+
idx = None
901+
try:
902+
conn = client(idx, name, **config_data)
903+
if conn:
904+
return conn
905+
except:
906+
error.weirdness("traceback from client()")
907+
except ImportError:
908+
pass
909+
finally:
910+
sys.path = sys.path[2:]
911+
912+
if environment:
913+
raise NotImplementedError(
914+
"Not possible to configure the caldav server through environmental variables yet"
915+
)
916+
917+
if configfile:
918+
raise NotImplementedError(
919+
"Support for configuration file not made yet (TODO: copy the code from the plann tool)"
920+
)

caldav/davobject.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ def get_properties(
263263
* {proptag: value, ...}
264264
265265
"""
266-
from .collection import Principal ## late import to avoid cyclic dependencies
266+
from .collection import Principal ## late import to avoid cyclic dependencies
267+
267268
rc = None
268269
response = self._query_properties(props, depth)
269270
if not parse_response_xml:

caldav/lib/debug.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ def xmlstring(root):
66
return root
77
if hasattr(root, "xmlelement"):
88
root = root.xmlelement()
9-
return etree.tostring(root, pretty_print=True).decode("utf-8")
9+
try:
10+
return etree.tostring(root, pretty_print=True).decode("utf-8")
11+
except:
12+
return root
1013

1114

1215
def printxml(root) -> None:

caldav/lib/error.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
try:
1111
import os
1212

13+
## Environmental variables prepended with "PYTHON_CALDAV" are used for debug purposes,
14+
## environmental variables prepended with "CALDAV_" are for connection parameters
1315
debug_dump_communication = os.environ.get("PYTHON_CALDAV_COMMDUMP", False)
1416
## one of DEBUG_PDB, DEBUG, DEVELOPMENT, PRODUCTION
1517
debugmode = os.environ["PYTHON_CALDAV_DEBUGMODE"]

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ Compatibility
307307
tend to be a moving target, and I rarely recheck if things works in
308308
newer versions of the software after I find an incompatibility)
309309

310-
The test suite is regularly run against several calendar servers, see https://github.com/python-caldav/caldav/issues/45 for the latest updates. See ``tests/compatibility_issues.py`` for the most up-to-date list of compatibility issues. In early versions of this library test breakages was often an indication that the library did not conform well enough to the standards, but as of today it mostly indicates that the servers does not support the standard well enough. It may be an option to add tweaks to the library code to cover some of the missing functionality.
310+
The test suite is regularly run against several calendar servers, see https://github.com/python-caldav/caldav/issues/45 for the latest updates. See ``compatibility_hints.py`` for the most up-to-date list of compatibility issues. In early versions of this library test breakages was often an indication that the library did not conform well enough to the standards, but as of today it mostly indicates that the servers does not support the standard well enough. It may be an option to add tweaks to the library code to cover some of the missing functionality.
311311

312312
Here are some known issues:
313313

tests/conf.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
import requests
1212

13-
from . import compatibility_issues
13+
from caldav import compatibility_hints
14+
from caldav.davclient import CONNKEYS
1415
from caldav.davclient import DAVClient
1516

1617
####################################
@@ -140,7 +141,7 @@ def teardown_radicale(self):
140141
"username": "user1",
141142
"password": "",
142143
"backwards_compatibility_url": url + "user1",
143-
"incompatibilities": compatibility_issues.radicale,
144+
"incompatibilities": compatibility_hints.radicale,
144145
"setup": setup_radicale,
145146
"teardown": teardown_radicale,
146147
}
@@ -225,35 +226,16 @@ def silly_request():
225226
"name": "LocalXandikos",
226227
"url": url,
227228
"backwards_compatibility_url": url + "sometestuser",
228-
"incompatibilities": compatibility_issues.xandikos,
229+
"incompatibilities": compatibility_hints.xandikos,
229230
"setup": setup_xandikos,
230231
"teardown": teardown_xandikos,
231232
}
232233
)
233234

235+
234236
###################################################################
235237
# Convenience - get a DAVClient object from the caldav_servers list
236238
###################################################################
237-
## TODO: this is already declared in davclient.DAVClient.__init__(...)
238-
## TODO: is it possible to reuse the declaration here instead of
239-
## duplicating the list?
240-
## TODO: If not, it's needed to look through and ensure the list is uptodate
241-
CONNKEYS = set(
242-
(
243-
"url",
244-
"proxy",
245-
"username",
246-
"password",
247-
"timeout",
248-
"headers",
249-
"huge_tree",
250-
"ssl_verify_cert",
251-
"ssl_cert",
252-
"auth",
253-
)
254-
)
255-
256-
257239
def client(
258240
idx=None, name=None, setup=lambda conn: None, teardown=lambda conn: None, **kwargs
259241
):
@@ -266,8 +248,8 @@ def client(
266248
return client(**caldav_servers[idx])
267249
elif name is not None and no_args and caldav_servers:
268250
for s in caldav_servers:
269-
if caldav_servers["name"] == s:
270-
return s
251+
if s["name"] == name:
252+
return client(**s)
271253
return None
272254
elif no_args:
273255
return None

tests/conf_private.py.EXAMPLE

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from tests import compatibility_issues
1+
from caldav import compatibility_hints
22

33
## PRIVATE CALDAV SERVER(S) TO RUN TESTS TOWARDS
44
## Make a list of your own servers/accounts that you'd like to run the
@@ -29,8 +29,8 @@ caldav_servers = [
2929

3030
## incompatibilities is a list of flags that can be set for
3131
## skipping (parts) of certain tests. See
32-
## tests/compatibility_issues.py for premade lists
33-
#'incompatibilities': compatibility_issues.nextcloud
32+
## compatibility_hints.py for premade lists
33+
#'incompatibilities': compatibility_hints.nextcloud
3434
'incompatibilities': [],
3535

3636
## You may even add setup and teardown methods to set up

tests/test_caldav.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import vobject
2727
from requests.packages import urllib3
2828

29-
from . import compatibility_issues
3029
from .conf import caldav_servers
3130
from .conf import client
3231
from .conf import proxy
@@ -40,6 +39,7 @@
4039
from .conf import xandikos_port
4140
from .proxy import NonThreadingHTTPServer
4241
from .proxy import ProxyHandler
42+
from caldav import compatibility_hints
4343
from caldav.davclient import DAVClient
4444
from caldav.davclient import DAVResponse
4545
from caldav.elements import cdav
@@ -583,12 +583,12 @@ class RepeatedFunctionalTestsBaseClass:
583583

584584
def check_compatibility_flag(self, flag):
585585
## yield an assertion error if checking for the wrong thig
586-
assert flag in compatibility_issues.incompatibility_description
586+
assert flag in compatibility_hints.incompatibility_description
587587
return flag in self.incompatibilities
588588

589589
def skip_on_compatibility_flag(self, flag):
590590
if self.check_compatibility_flag(flag):
591-
msg = compatibility_issues.incompatibility_description[flag]
591+
msg = compatibility_hints.incompatibility_description[flag]
592592
pytest.skip("Test skipped due to server incompatibility issue: " + msg)
593593

594594
def setup_method(self):
@@ -598,7 +598,7 @@ def setup_method(self):
598598
self.calendars_used = []
599599

600600
for flag in self.server_params.get("incompatibilities", []):
601-
assert flag in compatibility_issues.incompatibility_description
601+
assert flag in compatibility_hints.incompatibility_description
602602
self.incompatibilities.add(flag)
603603

604604
if self.check_compatibility_flag("unique_calendar_ids"):

0 commit comments

Comments
 (0)