Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server checker #451

Draft
wants to merge 34 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
78a33da
Tests: add a text identifier to each caldav server entry
tobixen Nov 15, 2024
232b60c
fixup! allow strings instead of tuples as input for sort_keys in search.
tobixen Nov 15, 2024
123fa48
work in progress
tobixen Nov 16, 2024
f587e65
fixup! work in progress
tobixen Nov 16, 2024
3887476
wip
tobixen Nov 17, 2024
fc856c7
woot ... the most important file not added to the repository?
tobixen Nov 17, 2024
dada1bc
work in progress
tobixen Nov 17, 2024
d44b6da
work in progress
tobixen Nov 17, 2024
b4f2de4
event check is done, I think
tobixen Nov 18, 2024
6fe61b9
bugfixing and xandikos compatibility matrix. xandikos now passes wit…
tobixen Nov 18, 2024
d11e95d
style fix
tobixen Nov 18, 2024
f5a4fd1
bugfix
tobixen Nov 18, 2024
7218161
bugfix
tobixen Nov 18, 2024
5da56d5
debug
tobixen Nov 18, 2024
c60160f
bufgfix
tobixen Nov 18, 2024
83d9741
bgufix
tobixen Nov 18, 2024
3b50b18
refactoring a bit
tobixen Nov 18, 2024
d822c9a
wip
tobixen Nov 19, 2024
d5825d1
debug abstracted away
tobixen Nov 19, 2024
3ef739a
no_todo_on_standard_calendar is out, plus misc other fixes
tobixen Nov 19, 2024
c496623
wip
tobixen Nov 20, 2024
c112d27
wip
tobixen Nov 21, 2024
65bb875
style fixup
tobixen Nov 21, 2024
401e88e
work in progress
tobixen Nov 30, 2024
347ff5a
bugfix
tobixen Nov 30, 2024
2f481d6
bugfix - exact match vs case sensitive
tobixen Nov 30, 2024
2a44b12
requires a newer caldav version (TODO: should use pyproject.toml)
tobixen Dec 1, 2024
5f6dc38
wip
tobixen Dec 5, 2024
5705632
throw user into python debugger only if an environmental variable is set
tobixen Dec 6, 2024
fa37f97
don't break if the calendar server denies telling details about events
tobixen Dec 6, 2024
d8970d2
if we cannot load an object using GET, try using REPORT and multiget
tobixen Dec 6, 2024
ac5f246
wip
tobixen Dec 6, 2024
138578d
inaccurate datesearch
tobixen Dec 7, 2024
77cbe66
inaccurate datesearch
tobixen Dec 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0

### Added

* 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/pull/449
* 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

## [1.4.0] - 2024-11-05

Expand Down
21 changes: 19 additions & 2 deletions caldav/davclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def _parse_response(self, response) -> Tuple[str, List[_Element], Optional[Any]]
status = None
href: Optional[str] = None
propstats: List[_Element] = []
check_404 = False ## special for purelymail
error.assert_(response.tag == dav.Response.tag)
for elem in response:
if elem.tag == dav.Status.tag:
Expand All @@ -223,13 +224,29 @@ def _parse_response(self, response) -> Tuple[str, List[_Element], Optional[Any]]
href = unquote(elem.text)
elif elem.tag == dav.PropStat.tag:
propstats.append(elem)
elif elem.tag == "{DAV:}error":
## This happens with purelymail on a 404.
## This code is mostly moot, but in debug
## mode I want to be sure we do not toss away any data
children = elem.getchildren()
error.assert_(len(children) == 1)
error.assert_(
children[0].tag == "{https://purelymail.com}does-not-exist"
)
check_404 = True
else:
error.assert_(False)
## i.e. purelymail may contain one more tag, <error>...</error>
## This is probably not a breach of the standard. It may
## probably be ignored. But it's something we may want to
## know.
error.weirdness("unexpected element found in response", elem)
error.assert_(href)
if check_404:
error.assert_("404" in status)
## TODO: is this safe/sane?
## Ref https://github.com/python-caldav/caldav/issues/435 the paths returned may be absolute URLs,
## but the caller expects them to be paths. Could we have issues when a server has same path
## but different URLs for different elements?
## but different URLs for different elements? Perhaps href should always be made into an URL-object?
if ":" in href:
href = unquote(URL(href).path)
return (cast(str, href), propstats, status)
Expand Down
2 changes: 2 additions & 0 deletions caldav/lib/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@


def xmlstring(root):
if isinstance(root, str):
return root
if hasattr(root, "xmlelement"):
root = root.xmlelement()
return etree.tostring(root, pretty_print=True).decode("utf-8")
Expand Down
14 changes: 13 additions & 1 deletion caldav/lib/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
## one of DEBUG_PDB, DEBUG, DEVELOPMENT, PRODUCTION
debugmode = os.environ["PYTHON_CALDAV_DEBUGMODE"]
except:
if "dev" in __version__:
if "dev" in __version__ or __version__ == "(unknown)":
debugmode = "DEVELOPMENT"
else:
debugmode = "PRODUCTION"
Expand All @@ -26,6 +26,18 @@
log.setLevel(logging.WARNING)


def weirdness(*reasons):
from caldav.lib.debug import xmlstring

reason = " : ".join([xmlstring(x) for x in reasons])
log.warning(f"Deviation from expectations found: {reason}")
if debugmode == "DEBUG_PDB":
log.error(f"Dropping into debugger due to {reason}")
import pdb

pdb.set_trace()


def assert_(condition: object) -> None:
try:
assert condition
Expand Down
2 changes: 2 additions & 0 deletions caldav/lib/vcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
I somehow feel this fits more into the icalendar library than here
"""
ical_fragment = to_normal_str(ical_fragment)
if "class_" in props:
props["class"] = props.pop("class_")
if not ical_fragment or not re.search("^BEGIN:V", ical_fragment, re.MULTILINE):
my_instance = icalendar.Calendar()
if objtype is None:
Expand Down
51 changes: 42 additions & 9 deletions caldav/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def _query(
expected_return_value is not None and ret.status != expected_return_value
) or ret.status >= 400:
## COMPATIBILITY HACK - see https://github.com/python-caldav/caldav/issues/309
## TODO: server quirks!
body = to_wire(body)
if (
ret.status == 500
Expand Down Expand Up @@ -791,7 +792,7 @@ def _create(
if name:
try:
self.set_properties([display_name])
except:
except Exception as e:
## TODO: investigate. Those asserts break.
error.assert_(False)
try:
Expand Down Expand Up @@ -946,7 +947,11 @@ def save(self):
self._create(id=self.id, name=self.name, **self.extra_init_options)
return self

def calendar_multiget(self, event_urls: Iterable[URL]) -> List["Event"]:
## TODO: this is missing test code.
## TODO: needs refactoring:
## Objects found may be Todo and Journal, not only Event.
## Replace the last lines with _request_report_build_resultlist method
def calendar_multiget(self, event_urls: Iterable[URL]) -> List[_CC]:
"""
get multiple events' data
@author [email protected]
Expand Down Expand Up @@ -1078,15 +1083,15 @@ def date_search(

return objects

## TODO: this logic has been partly duplicated in calendar_multiget, but
## the code there is much more readable and condensed than this.
## Can code below be refactored?
def _request_report_build_resultlist(
self, xml, comp_class=None, props=None, no_calendardata=False
):
"""
Takes some input XML, does a report query on a calendar object
and returns the resource objects found.

TODO: similar code is duplicated many places, we ought to do even more code
refactoring
"""
matches = []
if props is None:
Expand Down Expand Up @@ -1122,7 +1127,6 @@ def _request_report_build_resultlist(
props=pdata,
)
)

return (response, matches)

def search(
Expand Down Expand Up @@ -1163,7 +1167,7 @@ def search(
unless the next parameter is set ...
* include_completed - include completed tasks
* event - sets comp_class to event
* text attribute search parameters: category, uid, summary, omment,
* text attribute search parameters: category, uid, summary, comment,
description, location, status
* no-category, no-summary, etc ... search for objects that does not
have those attributes. TODO: WRITE TEST CODE!
Expand Down Expand Up @@ -1251,9 +1255,16 @@ def search(
)
raise

obj2 = []
for o in objects:
## This would not be needed if the servers would follow the standard ...
o.load(only_if_unloaded=True)
try:
o.load(only_if_unloaded=True)
obj2.append(o)
except:
logging.error("Server does not want to reveal details about the calendar object", exc_info=True)
pass
objects = obj2

## Google sometimes returns empty objects
objects = [o for o in objects if o.has_component()]
Expand Down Expand Up @@ -2338,6 +2349,8 @@ def copy(self, keep_uid: bool = False, new_parent: Optional[Any] = None) -> Self
obj.url = self.url
return obj

## TODO: move get-logics to a load_by_get method.
## The load method should deal with "server quirks".
def load(self, only_if_unloaded: bool = False) -> Self:
"""
(Re)load the object from the caldav server.
Expand All @@ -2351,7 +2364,10 @@ def load(self, only_if_unloaded: bool = False) -> Self:
if self.client is None:
raise ValueError("Unexpected value None for self.client")

r = self.client.request(str(self.url))
try:
r = self.client.request(str(self.url))
except:
return self.load_by_multiget()
if r.status == 404:
raise error.NotFoundError(errmsg(r))
self.data = vcal.fix(r.raw)
Expand All @@ -2361,6 +2377,23 @@ def load(self, only_if_unloaded: bool = False) -> Self:
self.props[cdav.ScheduleTag.tag] = r.headers["Schedule-Tag"]
return self

def load_by_multiget(self) -> Self:
"""
Some servers do not accept a GET, but we can still do a REPORT
with a multiget query
"""
error.assert_(self.url)
href = self.url.path
prop = dav.Prop() + cdav.CalendarData()
root = cdav.CalendarMultiGet() + prop + dav.Href(value=href)
response = self.parent._query(root, 1, "report")
results = response.expand_simple_props([cdav.CalendarData()])
error.assert_(len(results) == 1)
data = results[href][cdav.CalendarData.tag]
error.assert_(data)
self.data = data
return self

## TODO: self.id should either always be available or never
def _find_id_path(self, id=None, path=None) -> None:
"""
Expand Down
Loading
Loading