Skip to content

Commit

Permalink
work on #232. Preparing for release 0.11.0
Browse files Browse the repository at this point in the history
  • Loading branch information
tobixen committed Nov 21, 2022
1 parent bff2e13 commit a55be14
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 37 deletions.
4 changes: 2 additions & 2 deletions caldav/lib/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
## The default debugmode should be PRODUCTION in official releases,
## and DEVELOPMENT when doing beta testing.
## TODO: find some way to automate this.
debugmode = "DEVELOPMENT"
# debugmode = "PRODUCTION"
# debugmode = "DEVELOPMENT"
debugmode = "PRODUCTION"

log = logging.getLogger("caldav")
if debugmode.startswith("DEBUG"):
Expand Down
5 changes: 1 addition & 4 deletions caldav/lib/vcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import uuid

import icalendar
from caldav.lib.python_utilities import to_normal_str

## Fixups to the icalendar data to work around compatbility issues.
Expand Down Expand Up @@ -83,10 +84,6 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
"""
I somehow feel this fits more into the icalendar library than here
"""
## late import, icalendar is not an explicit requirement for v0.x of the caldav library.
## (perhaps I should change my position on that soon)
import icalendar

ical_fragment = to_normal_str(ical_fragment)
if not ical_fragment or not re.search("^BEGIN:V", ical_fragment, re.MULTILINE):
my_instance = icalendar.Calendar()
Expand Down
77 changes: 51 additions & 26 deletions caldav/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,9 +799,9 @@ def calendar_multiget(self, event_urls):
return rv

## TODO: Upgrade the warning to an error (and perhaps critical) in future
## releases, and then finally remove this method alltogether.
## releases, and then finally remove this method completely.
def build_date_search_query(
self, start, end=None, compfilter="VEVENT", expand=False
self, start, end=None, compfilter="VEVENT", expand="maybe"
):
## This is dead code. It has no tests. It was made for usage
## by the date_search method, but I've decided not to use it
Expand All @@ -813,6 +813,9 @@ def build_date_search_query(
logging.warning(
"DEPRECATION WARNING: The calendar.build_date_search_query method will be removed in caldav library from version 1.0 or perhaps earlier. Use calendar.build_search_xml_query instead."
)
if expand == "maybe":
expand = end

if compfilter == "VEVENT":
comp_class = Event
elif compfilter == "VTODO":
Expand Down Expand Up @@ -1380,7 +1383,7 @@ def object_by_uid(self, uid, comp_filter=None, comp_class=None):
## but at one point it broke due to an extra CR in the data.
## Usage of the icalendar library increases readability and
## reliability
item_uid = item.icalendar_object().get("UID", None)
item_uid = item.icalendar_component.get("UID", None)
if item_uid and item_uid == uid:
items_found2.append(item)
if not items_found2:
Expand Down Expand Up @@ -1632,8 +1635,8 @@ def __init__(
if data is not None:
self.data = data
if id:
old_id = self.icalendar_object().pop("UID", None)
self.icalendar_object().add("UID", id)
old_id = self.icalendar_component.pop("UID", None)
self.icalendar_component.add("UID", id)

def add_organizer(self):
"""
Expand All @@ -1643,7 +1646,7 @@ def add_organizer(self):
principal = self.client.principal()
## TODO: remove Organizer-field, if exists
## TODO: what if walk returns more than one vevent?
self.icalendar_object().add("organizer", principal.get_vcal_address())
self.icalendar_component.add("organizer", principal.get_vcal_address())

def split_expanded(self):
i = self.icalendar_instance.subcomponents
Expand Down Expand Up @@ -1722,15 +1725,15 @@ def set_relation(
if other.id:
uid = other.id
else:
uid = other.icalendar_object()["uid"]
uid = other.icalendar_component["uid"]
else:
uid = other
if set_reverse:
other = self.parent.object_by_uid(uid)
if set_reverse:
other.set_relation(other=self, reltype=reltype_reverse, set_reverse=False)

existing_relation = self.icalendar_object().get("related-to", None)
existing_relation = self.icalendar_component.get("related-to", None)
existing_relations = (
existing_relation
if isinstance(existing_relation, list)
Expand All @@ -1740,11 +1743,15 @@ def set_relation(
if rel == uid:
return

self.icalendar_object().add("related-to", uid, parameters={"rel-type": reltype})
self.icalendar_component.add(
"related-to", uid, parameters={"rel-type": reltype}
)

def icalendar_object(self, assert_one=True):
def _get_icalendar_component(self, assert_one=True):
"""Returns the icalendar subcomponent - which should be an
Event, Journal, Todo or FreeBusy from the icalendar class
See also https://github.com/python-caldav/caldav/issues/232
"""
ret = [
x
Expand All @@ -1763,6 +1770,24 @@ def icalendar_object(self, assert_one=True):
return x
error.assert_(False)

def _set_icalendar_component(self, value):
s = self.icalendar_instance.subcomponents
i = [i for i in range(0, len(s)) if not isinstance(s[i], icalendar.Timezone)]
if len(i) == 1:
self.icalendar_instance.subcomponents[i[0]] = value
else:
my_instance = icalendar.Calendar()
my_instance.add("prodid", "-//python-caldav//caldav//" + language)
my_instance.add("version", "2.0")
my_instance.add_component(value)
self.icalendar_instance = my_instance

icalendar_component = property(
_get_icalendar_component,
_set_icalendar_component,
doc="icalendar component - cannot be used with recurrence sets",
)

def add_attendee(self, attendee, no_default_parameters=False, **parameters):
"""
For the current (event/todo/journal), add an attendee.
Expand Down Expand Up @@ -1825,7 +1850,7 @@ def add_attendee(self, attendee, no_default_parameters=False, **parameters):
else:
params[new_key] = parameters[key]
attendee_obj.params.update(params)
ievent = self.icalendar_object()
ievent = self.icalendar_component
ievent.add("attendee", attendee_obj)

def is_invite_request(self):
Expand Down Expand Up @@ -1917,7 +1942,7 @@ def _find_id_path(self, id=None, path=None):
random number and a domain)
4) if no path is given, generate the URL from the ID
"""
i = self.icalendar_object(assert_one=False)
i = self._get_icalendar_component(assert_one=False)
if not id and getattr(self, "id", None):
id = self.id
if not id:
Expand Down Expand Up @@ -1972,7 +1997,7 @@ def generate_url(self):
## TODO: should try to wrap my head around issues that arises when id contains weird characters. maybe it's
## better to generate a new uuid here, particularly if id is in some unexpected format.
if not self.id:
self.id = self.icalendar_object(assert_one=False)["UID"]
self.id = self._get_icalendar_component(assert_one=False)["UID"]
return self.parent.url.join(quote(self.id.replace("/", "%2F")) + ".ics")

def change_attendee_status(self, attendee=None, **kwargs):
Expand All @@ -1996,7 +2021,7 @@ def change_attendee_status(self, attendee=None, **kwargs):
error.assert_(cnt == 1)
return

ical_obj = self.icalendar_object()
ical_obj = self.icalendar_component
attendee_lines = ical_obj["attendee"]
if isinstance(attendee_lines, str):
attendee_lines = [attendee_lines]
Expand Down Expand Up @@ -2089,9 +2114,9 @@ def save(
)

if increase_seqno and b"SEQUENCE" in to_wire(self.data):
seqno = self.icalendar_object().pop("SEQUENCE", None)
seqno = self.icalendar_component.pop("SEQUENCE", None)
if seqno is not None:
self.icalendar_object().add("SEQUENCE", seqno + 1)
self.icalendar_component.add("SEQUENCE", seqno + 1)

self._create(id=self.id, path=path)
return self
Expand Down Expand Up @@ -2327,7 +2352,7 @@ def _next(self, ts=None, i=None, dtstart=None, rrule=None, by=None, no_count=Tru
"""
if not i:
i = self.icalendar_object()
i = self.icalendar_component
if not rrule:
rrule = i["RRULE"]
if not dtstart:
Expand All @@ -2351,7 +2376,7 @@ def _next(self, ts=None, i=None, dtstart=None, rrule=None, by=None, no_count=Tru

def _reduce_count(self, i=None):
if not i:
i = self.icalendar_object()
i = self.icalendar_component
if "COUNT" in i["RRULE"]:
if i["RRULE"]["COUNT"][0] == 1:
return False
Expand All @@ -2374,12 +2399,12 @@ def _complete_recurring_safe(self, completion_timestamp):

completed = self.copy()
completed.url = self.parent.url.join(completed.id + ".ics")
completed.icalendar_object().pop("RRULE")
completed.icalendar_component.pop("RRULE")
completed.save()
completed.complete()

duration = self.get_duration()
i = self.icalendar_object()
i = self.icalendar_component
i.pop("DTSTART", None)
i.add("DTSTART", next_dtstart)
self.set_duration(duration, movable_attr="DUE")
Expand Down Expand Up @@ -2507,15 +2532,15 @@ def _complete_ical(self, i=None, completion_timestamp=None):
## my idea was to let self.complete call this one ... but self.complete
## should use vobject and not icalendar library due to backward compatibility.
if i is None:
i = self.icalendar_object()
i = self.icalendar_component
assert self._is_pending(i)
status = i.pop("STATUS", None)
i.add("STATUS", "COMPLETED")
i.add("COMPLETED", completion_timestamp)

def _is_pending(self, i=None):
if i is None:
i = self.icalendar_object()
i = self.icalendar_component
if i.get("COMPLETED", None) is not None:
return False
if i.get("STATUS", None) in ("NEEDS-ACTION", "IN-PROCESS"):
Expand Down Expand Up @@ -2559,7 +2584,7 @@ def get_duration(self):
is that DTEND is used rather than DUE) and possibly also for
Journal (defaults to one day, probably?)
"""
i = self.icalendar_object()
i = self.icalendar_component
return self._get_duration(i)

def _get_duration(self, i):
Expand All @@ -2576,7 +2601,7 @@ def set_duration(self, duration, movable_attr="DTSTART"):
TODO: can this be written in a better/shorter way?
"""
i = self.icalendar_object()
i = self.icalendar_component
return self._set_duration(i, duration, movable_attr)

def _set_duration(self, i, duration, movable_attr="DTSTART"):
Expand All @@ -2601,7 +2626,7 @@ def get_due(self):
"""
A VTODO may have due or duration set. Return or calculate due.
"""
i = self.icalendar_object()
i = self.icalendar_component
if "DUE" in i:
return i["DUE"].dt
elif "DURATION" in i and "DTSTART" in i:
Expand All @@ -2614,7 +2639,7 @@ def set_due(self, due, move_dtstart=False):
duration, so when setting due, the duration field must be
evicted
"""
i = self.icalendar_object()
i = self.icalendar_component
duration = self.get_duration()
i.pop("DURATION", None)
i.pop("DUE", None)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## ATTENTION! when doing releases, the default debugmode in lib/error.py should be set to PRODUCTION.
## (TODO: any nicer ways than doing this manually? Make a "releases" branch, maybe?)
version = "0.10.1.dev0"
version = "0.11.0"

if __name__ == "__main__":
## For python 2.7 and 3.5 we depend on pytz and tzlocal. For 3.6 and up, batteries are included. Same with mock. (But unfortunately the icalendar library only support pytz timezones, so we'll keep pytz around for a bit longer).
Expand Down
32 changes: 28 additions & 4 deletions tests/test_caldav_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ def testOne(self):
start=datetime(1998, 10, 10), end=datetime(1998, 12, 12)
)
assert len(self.yearly.icalendar_instance.subcomponents) == 1
assert not "RRULE" in self.yearly.icalendar_object()
assert "UID" in self.yearly.icalendar_object()
assert "RECURRENCE-ID" in self.yearly.icalendar_object()
assert not "RRULE" in self.yearly.icalendar_component
assert "UID" in self.yearly.icalendar_component
assert "RECURRENCE-ID" in self.yearly.icalendar_component

def testThree(self):
self.yearly.expand_rrule(
Expand Down Expand Up @@ -232,7 +232,8 @@ def testSplit(self):
assert len(events) == 3
assert len(events[0].icalendar_instance.subcomponents) == 1
assert (
events[1].icalendar_object()["UID"] == "19970901T130000Z-123403@example.com"
events[1].icalendar_component["UID"]
== "19970901T130000Z-123403@example.com"
)


Expand Down Expand Up @@ -947,6 +948,29 @@ def testInstance(self):
lines_orig.sort()
assert lines_now == lines_orig

def testComponent(self):
cal_url = "http://me:hunter2@calendar.example:80/"
client = DAVClient(url=cal_url)
my_event = Event(client, data=ev1)
icalcomp = my_event.icalendar_component
icalcomp["SUMMARY"] = "yet another summary"
assert my_event.vobject_instance.vevent.summary.value == "yet another summary"
## will the string still match the original?
lines_now = my_event.data.strip().split("\n")
lines_orig = (
ev1.replace("Bastille Day Party", "yet another summary").strip().split("\n")
)
lines_now.sort()
lines_orig.sort()
assert lines_now == lines_orig
## Can we replace the component? (One shouldn't do things like this in normal circumstances though ... both because the uid changes and because the component type changes - we're putting a vtodo into an Event class ...)
icalendar_component = icalendar.Todo.from_ical(todo).subcomponents[0]
my_event.icalendar_component = icalendar_component
assert (
my_event.vobject_instance.vtodo.summary.value
== "Submit Quebec Income Tax Return for 2006"
)

def testTodoDuration(self):
cal_url = "http://me:hunter2@calendar.example:80/"
client = DAVClient(url=cal_url)
Expand Down

0 comments on commit a55be14

Please sign in to comment.