Skip to content

Commit 57200c5

Browse files
committed
brushing up examples, documentation, changelog, killing some deprecation warnings from the test code, and covering the basic_usage_examples by the test code
1 parent 949df4f commit 57200c5

9 files changed

Lines changed: 59 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
1919
* `event.component` is now an alias for `event.icalendar_component`.
2020
* `get_davclient` (earlier called `auto_conn`) is more complete now - it could already read from test config, now it can read from environment (including environment variable for reading from test config and for locating the config file). While the `auto_conn` itself is tested in the functional tests, the code for reading the config file (and all the corner cases) is not tested. It's allowable with a yaml config file, but the yaml module is not included in the dependencies yet ... so late imports as for now. - https://github.com/python-caldav/caldav/pull/502 - https://github.com/python-caldav/caldav/issues/485
2121

22+
### Fixes
23+
24+
* Support for Lark/Feishu got broken in the 1.6-release. Issue found and fixed by Hongbin Yang (github user @zealseeker) in https://github.com/python-caldav/caldav/issues/505 and https://github.com/python-caldav/caldav/pull/506
25+
2226
### Changed
2327

2428
* The request library has been in a feature freeze for ages and may seem like a dead end. There exists a fork of the project niquests, we're migrating to that one. This means nothing except for one additional dependency. (httpx was also considered, but it's not a drop-in replacement for the requests library, and it's a risk that such a change will break compatibility with various other servers - see https://github.com/python-caldav/caldav/issues/457 for details). Work by @ArtemIsmagilov, https://github.com/python-caldav/caldav/pull/455.

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Contents
1515
about
1616
tutorial
1717
reference
18+
examples
1819

1920
====================
2021
Indices and tables

docs/source/tutorial.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ Tutorial
55
In this tutorial you should learn basic usage of the python CalDAV
66
client library. You are encouraged to copy the code examples into a
77
file and add a ``breakpoint()`` inside the with-block so you can
8-
inspect the return objects you get from the library calls.
8+
inspect the return objects you get from the library calls. Do not
9+
name your file `caldav.py` or `calendar.py`, this may break some
10+
imports.
911

1012
To follow this tutorial as intended, each code block should be run
1113
towards a clean-slate Radicale server. To do this, you need:

examples/basic_usage_examples.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
sys.path.insert(0, ".")
99

1010
import caldav
11+
from caldav.davclient import get_davclient
1112

1213
## DO NOT name your file calendar.py or caldav.py! We've had several
1314
## issues filed, things break because the wrong files are imported.
@@ -29,13 +30,10 @@ def run_examples():
2930
## The client object stores http session information, username, password, etc.
3031
## As of 1.0, Initiating the client object will not cause any server communication,
3132
## so the credentials aren't validated.
33+
## get_davclient will try to read credentials and url from environment variables
34+
## and config file.
3235
## The client object can be used as a context manager, like this:
33-
with caldav.DAVClient(
34-
url=caldav_url,
35-
username=username,
36-
password=password,
37-
headers=headers, # Optional parameter to set HTTP headers on each request if needed
38-
) as client:
36+
with get_davclient() as client:
3937
## Typically the next step is to fetch a principal object.
4038
## This will cause communication with the server.
4139
my_principal = client.principal()
@@ -132,20 +130,20 @@ def read_modify_event_demo(event):
132130
## event.icalendar_instance gives an icalendar instance - which
133131
## normally would be one icalendar calendar object containing one
134132
## subcomponent. Quite often the fourth property,
135-
## icalendar_component is preferable - it gives us the component -
136-
## but be aware that if the server returns a recurring events with
137-
## exceptions, event.icalendar_component will ignore all the
138-
## exceptions.
139-
uid = event.icalendar_component["uid"]
133+
## icalendar_component (now available just as .component) is
134+
## preferable - it gives us the component - but be aware that if
135+
## the server returns a recurring events with exceptions,
136+
## event.icalendar_component will ignore all the exceptions.
137+
uid = event.component["uid"]
140138

141139
## Let's correct that typo using the icalendar library.
142-
event.icalendar_component["summary"] = event.icalendar_component["summary"].replace(
140+
event.component["summary"] = event.component["summary"].replace(
143141
"celebratiuns", "celebrations"
144142
)
145143

146144
## timestamps (DTSTAMP, DTSTART, DTEND for events, DUE for tasks,
147145
## etc) can be fetched using the icalendar library like this:
148-
dtstart = event.icalendar_component.get("dtstart")
146+
dtstart = event.component.get("dtstart")
149147

150148
## but, dtstart is not a python datetime - it's a vDatetime from
151149
## the icalendar package. If you want it as a python datetime,
@@ -156,13 +154,13 @@ def read_modify_event_demo(event):
156154

157155
## We can modify it:
158156
if dtstart:
159-
event.icalendar_component["dtstart"].dt = dtstart.dt + timedelta(seconds=3600)
157+
event.component["dtstart"].dt = dtstart.dt + timedelta(seconds=3600)
160158

161159
## And finally, get the casing correct
162160
event.data = event.data.replace("norwegian", "Norwegian")
163161

164162
## Note that this is not quite thread-safe:
165-
icalendar_component = event.icalendar_component
163+
icalendar_component = event.component
166164
## accessing the data (and setting it) will "disconnect" the
167165
## icalendar_component from the event
168166
event.data = event.data
@@ -184,7 +182,7 @@ def read_modify_event_demo(event):
184182
calendar = event.parent
185183
same_event = calendar.event_by_uid(uid)
186184
assert (
187-
same_event.icalendar_component["summary"]
185+
same_event.component["summary"]
188186
== "Norwegian national day celebrations"
189187
)
190188

examples/get_events_example.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
from caldav.davclient import get_davclient
55

6-
## Code contributed by Крылов Александр. Minor changes and quite some
7-
## comments by Tobias Brox.
6+
## Code contributed by Крылов Александр.
7+
## Minor changes by Tobias Brox.
8+
## All comments by Tobias Brox.
89
## Set CALDAV_USERNAME, CALDAV_URL and CALDAV_PASSWORD through
910
## environment variables before running this example
1011

@@ -37,6 +38,9 @@ def fill_event(component, calendar) -> dict[str, str]:
3738
cur["calendar"] = f"{calendar}"
3839
cur["summary"] = component.get("summary")
3940
cur["description"] = component.get("description")
41+
## month/day/year time? Never ever do that!
42+
## It's one of the most confusing date formats ever!
43+
## Use year-month-day time instead ... https://xkcd.com/1179/
4044
cur["start"] = component.start.strftime("%m/%d/%Y %H:%M")
4145
endDate = component.end
4246
if endDate:

examples/scheduling_examples.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## NOTE! This is currently NOT tested. It may and may not work.
2+
## Please reach out if you need help with scheduling ... by https://xkcd.com/1179/
3+
## or scheduling-help@plann.no
4+
15
import sys
26
import uuid
37
from datetime import datetime

examples/sync_examples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## similar code in the tests/test_caldav.py file. Raise a github
44
## issue or reach out by email or write a pull request or send a patch
55
## if there are mistakes in this code) ...
6+
67
## USE CASE #1: we'll have a local copy of all calendar contents in a
78
## running python process, and later we'd like to synchronize the
89
## local contents. (In case of a reboot, all contents will be

tests/test_caldav.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,15 +1412,15 @@ def testCopyEvent(self):
14121412

14131413
## what will happen with the event in c1 if we modify the event in c2,
14141414
## which shares the id with the event in c1?
1415-
e1_in_c2.instance.vevent.summary.value = "asdf"
1415+
e1_in_c2.vobject_instance.vevent.summary.value = "asdf"
14161416
e1_in_c2.save()
14171417
e1.load()
14181418
## should e1.summary be 'asdf' or 'Bastille Day Party'? I do
14191419
## not know, but all implementations I've tested will treat
14201420
## the copy in the other calendar as a distinct entity, even
14211421
## if the uid is the same.
1422-
assert e1.instance.vevent.summary.value == "Bastille Day Party"
1423-
assert c2.events()[0].instance.vevent.uid == e1.instance.vevent.uid
1422+
assert e1.vobject_instance.vevent.summary.value == "Bastille Day Party"
1423+
assert c2.events()[0].vobject_instance.vevent.uid == e1.vobject_instance.vevent.uid
14241424

14251425
## Duplicate the event in the same calendar, with same uid -
14261426
## this makes no sense, there won't be any duplication
@@ -1602,17 +1602,17 @@ def testSearchEvent(self):
16021602
## Even sorting should work out
16031603
all_events = c.search(sort_keys=("summary", "dtstamp"))
16041604
assert len(all_events) == 3
1605-
assert all_events[0].instance.vevent.summary.value == "Bastille Day Jitsi Party"
1605+
assert all_events[0].vobject_instance.vevent.summary.value == "Bastille Day Jitsi Party"
16061606

16071607
## Sorting by upper case should also wor
16081608
all_events = c.search(sort_keys=("SUMMARY", "DTSTAMP"))
16091609
assert len(all_events) == 3
1610-
assert all_events[0].instance.vevent.summary.value == "Bastille Day Jitsi Party"
1610+
assert all_events[0].vobject_instance.vevent.summary.value == "Bastille Day Jitsi Party"
16111611

16121612
## Sorting in reverse order should work also
16131613
all_events = c.search(sort_keys=("SUMMARY", "DTSTAMP"), sort_reverse=True)
16141614
assert len(all_events) == 3
1615-
assert all_events[0].instance.vevent.summary.value == "Our Blissful Anniversary"
1615+
assert all_events[0].vobject_instance.vevent.summary.value == "Our Blissful Anniversary"
16161616

16171617
## A more robust check for the sort key
16181618
all_events = c.search(sort_keys=("DTSTART",))
@@ -2146,7 +2146,7 @@ def testTodos(self):
21462146
assert len(todos) == 3
21472147

21482148
def uids(lst):
2149-
return [x.instance.vtodo.uid for x in lst]
2149+
return [x.vobject_instance.vtodo.uid for x in lst]
21502150

21512151
## Default sort order is (due, priority).
21522152
assert uids(todos) == uids([t2, t1, t4])
@@ -2158,9 +2158,9 @@ def uids(lst):
21582158

21592159
def pri(lst):
21602160
return [
2161-
x.instance.vtodo.priority.value
2161+
x.vobject_instance.vtodo.priority.value
21622162
for x in lst
2163-
if hasattr(x.instance.vtodo, "priority")
2163+
if hasattr(x.vobject_instance.vtodo, "priority")
21642164
]
21652165

21662166
assert pri(todos) == pri([t4, t2])
@@ -2372,9 +2372,9 @@ def testTodoCompletion(self):
23722372
assert len(todos) == 3
23732373
if not self.check_compatibility_flag("object_by_uid_is_broken"):
23742374
t3_ = c.todo_by_uid(t3.id)
2375-
assert t3_.instance.vtodo.summary == t3.instance.vtodo.summary
2376-
assert t3_.instance.vtodo.uid == t3.instance.vtodo.uid
2377-
assert t3_.instance.vtodo.dtstart == t3.instance.vtodo.dtstart
2375+
assert t3_.vobject_instance.vtodo.summary == t3.vobject_instance.vtodo.summary
2376+
assert t3_.vobject_instance.vtodo.uid == t3.vobject_instance.vtodo.uid
2377+
assert t3_.vobject_instance.vtodo.dtstart == t3.vobject_instance.vtodo.dtstart
23782378

23792379
t2.delete()
23802380

@@ -2603,19 +2603,19 @@ def testLookupEvent(self):
26032603
# Verify that we can look it up, both by URL and by ID
26042604
if not self.check_compatibility_flag("event_by_url_is_broken"):
26052605
e2 = c.event_by_url(e1.url)
2606-
assert e2.instance.vevent.uid == e1.instance.vevent.uid
2606+
assert e2.vobject_instance.vevent.uid == e1.vobject_instance.vevent.uid
26072607
assert e2.url == e1.url
26082608
if not self.check_compatibility_flag("object_by_uid_is_broken"):
26092609
e3 = c.event_by_uid("20010712T182145Z-123401@example.com")
2610-
assert e3.instance.vevent.uid == e1.instance.vevent.uid
2610+
assert e3.vobject_instance.vevent.uid == e1.vobject_instance.vevent.uid
26112611
assert e3.url == e1.url
26122612

26132613
# Knowing the URL of an event, we should be able to get to it
26142614
# without going through a calendar object
26152615
if not self.check_compatibility_flag("event_by_url_is_broken"):
26162616
e4 = Event(client=self.caldav, url=e1.url)
26172617
e4.load()
2618-
assert e4.instance.vevent.uid == e1.instance.vevent.uid
2618+
assert e4.vobject_instance.vevent.uid == e1.vobject_instance.vevent.uid
26192619

26202620
with pytest.raises(error.NotFoundError):
26212621
c.event_by_uid("0")
@@ -2674,16 +2674,16 @@ def testCreateOverwriteDeleteEvent(self):
26742674
t2 = c.save_todo(todo, no_create=no_create)
26752675

26762676
## this should also work.
2677-
e2.instance.vevent.summary.value = e2.instance.vevent.summary.value + "!"
2677+
e2.vobject_instance.vevent.summary.value = e2.vobject_instance.vevent.summary.value + "!"
26782678
e2.save(no_create=no_create)
26792679

26802680
if todo_ok:
2681-
t2.instance.vtodo.summary.value = t2.instance.vtodo.summary.value + "!"
2681+
t2.vobject_instance.vtodo.summary.value = t2.vobject_instance.vtodo.summary.value + "!"
26822682
t2.save(no_create=no_create)
26832683

26842684
if not self.check_compatibility_flag("event_by_url_is_broken"):
26852685
e3 = c.event_by_url(e1.url)
2686-
assert e3.instance.vevent.summary.value == "Bastille Day Party!"
2686+
assert e3.vobject_instance.vevent.summary.value == "Bastille Day Party!"
26872687

26882688
## "no_overwrite" should throw a ConsistencyError. But it depends on object_by_uid.
26892689
if not self.check_compatibility_flag("object_by_uid_is_broken"):
@@ -2745,8 +2745,8 @@ def testDateSearchAndFreeBusy(self):
27452745
expand=False,
27462746
)
27472747

2748-
assert e.instance.vevent.uid == r1[0].instance.vevent.uid
2749-
assert e.instance.vevent.uid == r2[0].instance.vevent.uid
2748+
assert e.vobject_instance.vevent.uid == r1[0].vobject_instance.vevent.uid
2749+
assert e.vobject_instance.vevent.uid == r2[0].vobject_instance.vevent.uid
27502750
assert len(r1) == 1
27512751
assert len(r2) == 1
27522752

@@ -2794,7 +2794,7 @@ def testDateSearchAndFreeBusy(self):
27942794
)
27952795
# TODO: assert something more complex on the return object
27962796
assert isinstance(freebusy, FreeBusy)
2797-
assert freebusy.instance.vfreebusy
2797+
assert freebusy.vobject_instance.vfreebusy
27982798

27992799
def testRecurringDateSearch(self):
28002800
"""

tests/test_examples.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ def test_get_events_example(self):
2626
from examples import get_events_example
2727

2828
get_events_example.fetch_and_print()
29+
30+
def test_basic_usage_examples(self):
31+
from examples import basic_usage_examples
32+
basic_usage_examples.run_examples()
33+

0 commit comments

Comments
 (0)