1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
|
import sys
from datetime import date
from datetime import datetime
from datetime import timedelta
## We'll try to use the local caldav library, not the system-installed
sys.path.insert(0, "..")
sys.path.insert(0, ".")
import caldav
## DO NOT name your file calendar.py or caldav.py! We've had several
## issues filed, things break because the wrong files are imported.
## It's not a bug with the caldav library per se.
## CONFIGURATION. Edit here, or set up something in
## tests/conf_private.py (see tests/conf_private.py.EXAMPLE).
caldav_url = "https://calendar.example.com/dav"
username = "somebody"
password = "hunter2"
headers = {"X-MY-CUSTOMER-HEADER": "123"}
def run_examples():
"""
Run through all the examples, one by one
"""
## We need a client object.
## The client object stores http session information, username, password, etc.
## As of 1.0, Initiating the client object will not cause any server communication,
## so the credentials aren't validated.
## The client object can be used as a context manager, like this:
with caldav.DAVClient(
url=caldav_url,
username=username,
password=password,
headers=headers, # Optional parameter to set HTTP headers on each request if needed
) as client:
## Typically the next step is to fetch a principal object.
## This will cause communication with the server.
my_principal = client.principal()
## The principals calendars can be fetched like this:
calendars = my_principal.calendars()
## print out some information
print_calendars_demo(calendars)
## This cleans up from previous runs, if needed:
find_delete_calendar_demo(my_principal, "Test calendar from caldav examples")
## Let's create a new calendar to play with.
## This may raise an error for multiple reasons:
## * server may not support it (it's not mandatory in the CalDAV RFC)
## * principal may not have the permission to create calendars
## * some cloud providers have a global namespace
my_new_calendar = my_principal.make_calendar(
name="Test calendar from caldav examples"
)
## Let's add some events to our newly created calendar
add_stuff_to_calendar_demo(my_new_calendar)
## Let's find the stuff we just added to the calendar
event = search_calendar_demo(my_new_calendar)
## Inspecting and modifying an event
read_modify_event_demo(event)
## Accessing a calendar by a calendar URL
calendar_by_url_demo(client, my_new_calendar.url)
## Clean up - delete things
## (The event would normally be deleted together with the calendar,
## but different calendar servers may behave differently ...)
event.delete()
my_new_calendar.delete()
def calendar_by_url_demo(client, url):
"""Sometimes one may have a calendar URL. Sometimes maybe one would
not want to fetch the principal object from the server (it's not
even required to support it by the caldav protocol).
"""
## No network traffic will be initiated by this:
calendar = client.calendar(url=url)
## At the other hand, this will cause network activity:
events = calendar.events()
## We should still have only one event in the calendar
assert len(events) == 1
event_url = events[0].url
## there is no similar method for fetching an event through
## a URL. One may construct the object like this though:
same_event = caldav.Event(client=client, parent=calendar, url=event_url)
## That was also done without any network traffic. To get the same_event
## populated with data it needs to be loaded:
same_event.load()
assert same_event.data == events[0].data
def read_modify_event_demo(event):
"""This demonstrates how to edit properties in the ical object
and save it back to the calendar. It takes an event -
caldav.Event - as input. This event is found through the
`search_calendar_demo`. The event needs some editing, which will
be done below. Keep in mind that the differences between an
Event, a Todo and a Journal is small, everything that is done to
he event here could as well be done towards a task.
"""
## The objects (events, journals and tasks) comes with some properties that
## can be used for inspecting the data and modifying it.
## event.data is the raw data, as a string, with unix linebreaks
print("here comes some icalendar data:")
print(event.data)
## event.wire_data is the raw data as a byte string with CRLN linebreaks
assert len(event.wire_data) >= len(event.data)
## Two libraries exists to handle icalendar data - vobject and
## icalendar. The caldav library traditionally supported the
## first one, but icalendar is more popular.
## Here is an example
## on how to modify the summary using vobject:
event.vobject_instance.vevent.summary.value = "norwegian national day celebratiuns"
## event.icalendar_instance gives an icalendar instance - which
## normally would be one icalendar calendar object containing one
## subcomponent. Quite often the fourth property,
## icalendar_component is preferable - it gives us the component -
## but be aware that if the server returns a recurring events with
## exceptions, event.icalendar_component will ignore all the
## exceptions.
uid = event.icalendar_component["uid"]
## Let's correct that typo using the icalendar library.
event.icalendar_component["summary"] = event.icalendar_component["summary"].replace(
"celebratiuns", "celebrations"
)
## timestamps (DTSTAMP, DTSTART, DTEND for events, DUE for tasks,
## etc) can be fetched using the icalendar library like this:
dtstart = event.icalendar_component.get("dtstart")
## but, dtstart is not a python datetime - it's a vDatetime from
## the icalendar package. If you want it as a python datetime,
## use the .dt property. (In this case dtstart is set - and it's
## pretty much mandatory for events - but the code here is robust
## enough to handle cases where it's undefined):
dtstart_dt = dtstart and dtstart.dt
## We can modify it:
if dtstart:
event.icalendar_component["dtstart"].dt = dtstart.dt + timedelta(seconds=3600)
## And finally, get the casing correct
event.data = event.data.replace("norwegian", "Norwegian")
## Note that this is not quite thread-safe:
icalendar_component = event.icalendar_component
## accessing the data (and setting it) will "disconnect" the
## icalendar_component from the event
event.data = event.data
## So this will not affect the event anymore:
icalendar_component["summary"] = "do the needful"
assert not "do the needful" in event.data
## The mofifications are still only saved locally in memory -
## let's save it to the server:
event.save()
## NOTE: always use event.save() for updating events and
## calendar.save_event(data) for creating a new event.
## This may break:
# event.save(event.data)
## ref https://github.com/python-caldav/caldav/issues/153
## Finally, let's verify that the correct data was saved
calendar = event.parent
same_event = calendar.event_by_uid(uid)
assert (
same_event.icalendar_component["summary"]
== "Norwegian national day celebrations"
)
def search_calendar_demo(calendar):
"""
some examples on how to fetch objects from the calendar
"""
## It should theoretically be possible to find both the events and
## tasks in one calendar query, but not all server implementations
## supports it, hence either event, todo or journal should be set
## to True when searching. Here is a date search for events, with
## expand:
events_fetched = calendar.search(
start=datetime.now(),
end=datetime(date.today().year + 5, 1, 1),
event=True,
expand=True,
)
## "expand" causes the recurrences to be expanded.
## The yearly event will give us one object for each year
assert len(events_fetched) > 1
print("here is some ical data:")
print(events_fetched[0].data)
## We can also do the same thing without expand, then the "master"
## from 2020 will be fetched
events_fetched = calendar.search(
start=datetime.now(),
end=datetime(date.today().year + 5, 1, 1),
event=True,
expand=False,
)
assert len(events_fetched) == 1
## search can be done by other things, i.e. keyword
tasks_fetched = calendar.search(todo=True, category="outdoor")
assert len(tasks_fetched) == 1
## This those should also work:
all_objects = calendar.objects()
# updated_objects = calendar.objects_by_sync_token(some_sync_token)
# some_object = calendar.object_by_uid(some_uid)
# some_event = calendar.event_by_uid(some_uid)
children = calendar.children()
events = calendar.events()
tasks = calendar.todos()
assert len(events) + len(tasks) == len(all_objects)
assert len(children) == len(all_objects)
## TODO: Some of those should probably be deprecated.
## children is a good candidate.
## Tasks can be completed
tasks[0].complete()
## They will then disappear from the task list
assert not calendar.todos()
## But they are not deleted
assert len(calendar.todos(include_completed=True)) == 1
## Let's delete it completely
tasks[0].delete()
return events_fetched[0]
def print_calendars_demo(calendars):
"""
This example prints the name and URL for every calendar on the list
"""
if calendars:
## Some calendar servers will include all calendars you have
## access to in this list, and not only the calendars owned by
## this principal.
print("your principal has %i calendars:" % len(calendars))
for c in calendars:
print(" Name: %-36s URL: %s" % (c.name, c.url))
else:
print("your principal has no calendars")
def find_delete_calendar_demo(my_principal, calendar_name):
"""
This example takes a calendar name, finds the calendar if it
exists, and deletes the calendar if it exists.
"""
## Let's try to find or create a calendar ...
try:
## This will raise a NotFoundError if calendar does not exist
demo_calendar = my_principal.calendar(name="Test calendar from caldav examples")
assert demo_calendar
print(
f"We found an existing calendar with name {calendar_name}, now deleting it"
)
demo_calendar.delete()
except caldav.error.NotFoundError:
## Calendar was not found
pass
def add_stuff_to_calendar_demo(calendar):
"""
This demo adds some stuff to the calendar
Unfortunately the arguments that it's possible to pass to save_* is poorly documented.
https://github.com/python-caldav/caldav/issues/253
"""
## Add an event with some certain attributes
may_event = calendar.save_event(
dtstart=datetime(2020, 5, 17, 6),
dtend=datetime(2020, 5, 18, 1),
summary="Do the needful",
rrule={"FREQ": "YEARLY"},
)
## not all calendars supports tasks ... but if it's supported, it should be
## told here:
acceptable_component_types = calendar.get_supported_components()
assert "VTODO" in acceptable_component_types
## Add a task that should contain some ical lines
## Note that this may break on your server:
## * not all servers accepts tasks and events mixed on the same calendar.
## * not all servers accepts tasks at all
dec_task = calendar.save_todo(
ical_fragment="""DTSTART;VALUE=DATE:20201213
DUE;VALUE=DATE:20201220
SUMMARY:Chop down a tree and drag it into the living room
RRULE:FREQ=YEARLY
PRIORITY: 2
CATEGORIES: outdoor"""
)
## ical_fragment parameter -> just some lines
## ical parameter -> full ical object
def _please_ignore_this_hack():
"""
This hack is to be used for the maintainer (or other people
having set up testing servers in tests/private_conf.py) to be able
to verify that this example code works, without editing the
example code itself.
"""
if password == "hunter2":
from tests.conf import client as client_
client = client_()
def _wrapper(*args, **kwargs):
return client
caldav.DAVClient = _wrapper
if __name__ == "__main__":
_please_ignore_this_hack()
run_examples()
|