summaryrefslogtreecommitdiff
path: root/examples/basic_usage_examples.py
blob: 1ab3c2b19de2be581e8356ead019ae6d542bb673 (plain)
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
from datetime import datetime, date
import sys

## 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'

## When using the caldav library, one should always start off with initiating a
## DAVClient object, which should contain connection details and credentials.
## Initiating the object does not cause any requests to the server, so this
## will not break even if caldav url is set to example.com
client = caldav.DAVClient(url=caldav_url, username=username, password=password)

## For the convenience, if things are correctly set up in test config,
## the code below may replace the client object with one that works.
if 'example.com' in caldav_url and password == 'hunter2':
    from tests.conf import client as client_
    client = 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()
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: %-20s  URL: %s" % (c.name, c.url))
else:
    print("your principal has no calendars")

## Let's try to find or create a calendar ...
try:
    ## This will raise a NotFoundError if calendar does not exist
    my_new_calendar = my_principal.calendar(name="Test calendar")
    assert(my_new_calendar)
    ## calendar did exist, probably it was made on an earlier run
    ## of this script
except caldav.error.NotFoundError:
    ## Let's create a calendar
    my_new_calendar = my_principal.make_calendar(name="Test calendar")

## Let's add an event to our newly created calendar
## (This usage pattern is new from v0.9.
## Earlier save_event would only accept some ical data)
my_event = my_new_calendar.save_event(
    dtstart=datetime(2020,5,17,8),
    dtend=datetime(2020,5,18,1),
    summary="Do the needful",
    rrule={'FREQ': 'YEARLY'})

## Let's search for the newly added event.
## (this may fail if the server doesn't support expand)
print("Here is some icalendar data:")
try:
    events_fetched = my_new_calendar.date_search(
        start=datetime(2021, 5, 16), end=datetime(2024, 1, 1), expand=True)
    print(events_fetched[0].data)
except:
    print("Your calendar server does apparently not support expanded search")
    events_fetched = my_new_calendar.date_search(
        start=datetime(2020, 5, 16), end=datetime(2024, 1, 1), expand=False)
    print(events_fetched[0].data)

event = events_fetched[0]

## To modify an event, it's best to use either the vobject or icalendar module for it.
## The caldav library has always been supporting vobject out of the box, but icalendar is more popular.
## event.instance will as of version 0.x yield a vobject instance, but this may change in future versions.
## Both event.vobject_instance and event.icalendar_instance works from 0.7.
event.vobject_instance.vevent.summary.value = 'Norwegian national day celebratiuns'
event.icalendar_instance.subcomponents[0]['summary'] = event.icalendar_instance.subcomponents[0]['summary'].replace('celebratiuns', 'celebrations')
event.save()

## Please note that the proper way to save new icalendar data
## to the calendar is calendar.save_event(ics_data),
## while the proper way to update a calendar event is
## event.save().  Doing calendar.save_event(event.data)
## may break.  See https://github.com/python-caldav/caldav/issues/153
## for details.

## It's possible to access objects such as calendars without going
## through a Principal object if one knows the calendar URL
the_same_calendar = client.calendar(url=my_new_calendar.url)

## to get all events from the calendar, it's also possible to use the
## events()-method.  Recurring events will not be expanded.
all_events = the_same_calendar.events()

## It's also possible to use .objects.
all_objects = the_same_calendar.objects()

## since we have only added events (and neither todos nor journals), those
## should be equal ... except, all_objects is an iterator and not a list.
assert(len(all_events) == len(list(all_objects)))

## Let's check that the summary got right
assert all_events[0].vobject_instance.vevent.summary.value.startswith('Norwegian')
assert all_events[0].vobject_instance.vevent.summary.value.endswith('celebrations')

## This calendar should as a minimum support VEVENTs ... most likely
## it also supports VTODOs and maybe even VJOURNALs.  We can query the
## server what it can accept:
acceptable_component_types = my_new_calendar.get_supported_components()
assert 'VEVENT' in acceptable_component_types

## Clean up - remove the new calendar
my_new_calendar.delete()

## Let's try with a task list.  Some servers cannot combine events and todos in the same calendar.
my_new_tasklist = my_principal.make_calendar(
            name="Test tasklist", supported_calendar_component_set=['VTODO'])

## We'll add a task to the task list
my_new_tasklist.add_todo(
    ics = "RRULE:FREQ=YEARLY",
    summary="Deliver some data to the Tax authorities",
    dtstart=date(2020, 4, 1),
    due=date(2020,5,1),
    categories=['family', 'finance'],
    status='NEEDS-ACTION')

## Fetch the tasks
todos = my_new_tasklist.todos()
assert(len(todos) == 1)
assert('FREQ=YEARLY' in todos[0].data)

print("Here is some more icalendar data:")
print(todos[0].data)

## date_search also works on task lists, but one has to be explicit to get them
todos_found = my_new_tasklist.date_search(
    start=datetime(2021, 1, 1), end=datetime(2024, 1, 1),
    compfilter='VTODO', expand=True)
if not todos_found:
    print("Apparently your calendar server does not support searching for future instances of reoccurring tasks")
else:
    print("Here is even more icalendar data:")
    print(todos_found[0].data)

## Mark the task as completed
todos[0].complete()

## This is a yearly task.  Completing it for one year should probably
## spawn a new task recurrence instance for the next year.  The RFC
## says nothing about it, it seems like it's up to the clients weather
## to implement such logic or not.  I've implemented such logic in the
## calendar-cli project, perhaps it should be moved into the caldav
## library, but as for now ... completing the task will cause the task
## list to be emptied.
todos = my_new_tasklist.todos()
assert(len(todos) == 0)

## It's possible to fetch historic tasks too
todos = my_new_tasklist.todos(include_completed=True)
assert(len(todos) == 1)

## and it's possible to delete tasks completely
todos[0].delete()

my_new_tasklist.delete()