30
30
import logging
31
31
import os
32
32
import os .path
33
+ from typing import Dict , Iterable , List , Optional , Tuple , Union # noqa
33
34
34
35
from . import backend
35
36
from .event import Event
42
43
logger = logging .getLogger (__name__ )
43
44
44
45
45
- def create_directory (path ):
46
+ def create_directory (path : str ):
46
47
if not os .path .isdir (path ):
47
48
if os .path .exists (path ):
48
49
raise RuntimeError ('{0} is not a directory.' .format (path ))
49
50
try :
50
51
os .makedirs (path , mode = 0o750 )
51
52
except OSError as error :
52
- logger .fatal ('failed to create {0}: {1}' .format (path , error ))
53
+ logger .critical ('failed to create {0}: {1}' .format (path , error ))
53
54
raise CouldNotCreateDbDir ()
54
55
55
56
@@ -60,19 +61,19 @@ class CalendarCollection(object):
60
61
61
62
def __init__ (self ,
62
63
calendars = None ,
63
- hmethod = 'fg' ,
64
- default_color = '' ,
65
- multiple = '' ,
66
- color = '' ,
67
- highlight_event_days = 0 ,
68
- locale = None ,
69
- dbpath = None ,
70
- ):
64
+ hmethod : str = 'fg' ,
65
+ default_color : str = '' ,
66
+ multiple : str = '' ,
67
+ color : str = '' ,
68
+ highlight_event_days : bool = False ,
69
+ locale : Optional [ dict ] = None ,
70
+ dbpath : Optional [ str ] = None ,
71
+ ) -> None :
71
72
assert dbpath is not None
72
73
assert calendars is not None
73
74
self ._calendars = calendars
74
- self ._default_calendar_name = None
75
- self ._storages = dict ()
75
+ self ._default_calendar_name = None # type: Optional[str]
76
+ self ._storages = dict () # type: Dict[str, Vdir]
76
77
for name , calendar in self ._calendars .items ():
77
78
ctype = calendar .get ('ctype' , 'calendar' )
78
79
if ctype == 'calendar' :
@@ -94,29 +95,28 @@ def __init__(self,
94
95
self .color = color
95
96
self .highlight_event_days = highlight_event_days
96
97
self ._locale = locale
97
- self ._backend = backend .SQLiteDb (
98
- calendars = self .names , db_path = dbpath , locale = self ._locale )
99
- self ._last_ctags = dict ()
98
+ self ._backend = backend .SQLiteDb (self .names , dbpath , self ._locale )
99
+ self ._last_ctags = dict () # type: Dict[str, str]
100
100
self .update_db ()
101
101
102
102
@property
103
- def writable_names (self ):
103
+ def writable_names (self ) -> List [ str ] :
104
104
return [c for c in self ._calendars if not self ._calendars [c ].get ('readonly' , False )]
105
105
106
106
@property
107
- def calendars (self ):
107
+ def calendars (self ) -> Iterable [ str ] :
108
108
return self ._calendars .values ()
109
109
110
110
@property
111
- def names (self ):
111
+ def names (self ) -> Iterable [ str ] :
112
112
return self ._calendars .keys ()
113
113
114
114
@property
115
- def default_calendar_name (self ):
115
+ def default_calendar_name (self ) -> str :
116
116
return self ._default_calendar_name
117
117
118
118
@default_calendar_name .setter
119
- def default_calendar_name (self , default ):
119
+ def default_calendar_name (self , default : str ):
120
120
if default is None :
121
121
self ._default_calendar_name = default
122
122
elif default not in self .names :
@@ -130,38 +130,37 @@ def default_calendar_name(self, default):
130
130
raise ValueError (
131
131
'Calendar "{0}" is read-only and cannot be used as default' .format (default ))
132
132
133
- def _local_ctag (self , calendar ) :
133
+ def _local_ctag (self , calendar : str ) -> str :
134
134
return get_etag_from_file (self ._calendars [calendar ]['path' ])
135
135
136
- def _cover_event (self , event ):
137
- event .color = self ._calendars [event .calendar ]['color' ]
138
- event .readonly = self ._calendars [event .calendar ]['readonly' ]
139
- event .unicode_symbols = self ._locale ['unicode_symbols' ]
140
- return event
141
-
142
- def get_floating (self , start , end , minimal = False ):
143
- events = self ._backend .get_floating (start , end , minimal )
144
- return (self ._cover_event (event ) for event in events )
145
-
146
- def get_localized (self , start , end , minimal = False ):
147
- events = self ._backend .get_localized (start , end , minimal )
148
- return (self ._cover_event (event ) for event in events )
136
+ def get_floating (self , start : dt .datetime , end : dt .datetime ) -> Iterable [Event ]:
137
+ for args in self ._backend .get_floating (start , end ):
138
+ yield self ._construct_event (* args )
149
139
150
- def get_events_on (self , day , minimal = False ):
151
- """return all events on `day`
140
+ def get_localized (self , start : dt .datetime , end : dt .datetime ) -> Iterable [Event ]:
141
+ for args in self ._backend .get_localized (start , end ):
142
+ yield self ._construct_event (* args )
152
143
153
- :param day: datetime.date
154
- :rtype: list()
155
- """
144
+ def get_events_on (self , day : dt .date ) -> Iterable [Event ]:
145
+ """return all events on `day`"""
156
146
start = dt .datetime .combine (day , dt .time .min )
157
147
end = dt .datetime .combine (day , dt .time .max )
158
- floating_events = self .get_floating (start , end , minimal )
148
+ floating_events = self .get_floating (start , end )
159
149
localize = self ._locale ['local_timezone' ].localize
160
- localized_events = self .get_localized (localize (start ), localize (end ), minimal )
161
-
150
+ localized_events = self .get_localized (localize (start ), localize (end ))
162
151
return itertools .chain (floating_events , localized_events )
163
152
164
- def update (self , event ):
153
+ def get_calendars_on (self , day : dt .date ) -> List [str ]:
154
+ start = dt .datetime .combine (day , dt .time .min )
155
+ end = dt .datetime .combine (day , dt .time .max )
156
+ localize = self ._locale ['local_timezone' ].localize
157
+ calendars = itertools .chain (
158
+ self ._backend .get_floating_calendars (start , end ),
159
+ self ._backend .get_localized_calendars (localize (start ), localize (end )),
160
+ )
161
+ return list (calendars )
162
+
163
+ def update (self , event : Event ):
165
164
"""update `event` in vdir and db"""
166
165
assert event .etag
167
166
if self ._calendars [event .calendar ]['readonly' ]:
@@ -171,7 +170,7 @@ def update(self, event):
171
170
self ._backend .update (event .raw , event .href , event .etag , calendar = event .calendar )
172
171
self ._backend .set_ctag (self ._local_ctag (event .calendar ), calendar = event .calendar )
173
172
174
- def force_update (self , event , collection = None ):
173
+ def force_update (self , event : Event , collection : Optional [ str ] = None ):
175
174
"""update `event` even if an event with the same uid/href already exists"""
176
175
calendar = collection if collection is not None else event .calendar
177
176
if self ._calendars [calendar ]['readonly' ]:
@@ -187,7 +186,7 @@ def force_update(self, event, collection=None):
187
186
self ._backend .update (event .raw , href , etag , calendar = calendar )
188
187
self ._backend .set_ctag (self ._local_ctag (calendar ), calendar = calendar )
189
188
190
- def new (self , event , collection = None ):
189
+ def new (self , event : Event , collection : Optional [ str ] = None ):
191
190
"""save a new event to the vdir and the database
192
191
193
192
param event: the event that should be updated, will get a new href and
@@ -210,22 +209,48 @@ def new(self, event, collection=None):
210
209
self ._backend .update (event .raw , event .href , event .etag , calendar = calendar )
211
210
self ._backend .set_ctag (self ._local_ctag (calendar ), calendar = calendar )
212
211
213
- def delete (self , href , etag , calendar ):
212
+ def delete (self , href : str , etag : str , calendar : str ):
214
213
if self ._calendars [calendar ]['readonly' ]:
215
214
raise ReadOnlyCalendarError ()
216
215
self ._storages [calendar ].delete (href , etag )
217
216
self ._backend .delete (href , calendar = calendar )
218
217
219
- def get_event (self , href , calendar ):
220
- return self ._cover_event (self ._backend .get (href , calendar = calendar ))
218
+ def get_event (self , href : str , calendar : str ) -> Event :
219
+ """get an event by its href from the datatbase"""
220
+ return self ._construct_event (
221
+ self ._backend .get (href , calendar ), href = href , calendar = calendar ,
222
+ )
223
+
224
+ def _construct_event (self ,
225
+ item : str ,
226
+ href : str ,
227
+ start : dt .datetime = None ,
228
+ end : dt .datetime = None ,
229
+ ref : str = 'PROTO' ,
230
+ etag : str = None ,
231
+ calendar : str = None ,
232
+ ) -> Event :
233
+ event = Event .fromString (
234
+ item ,
235
+ locale = self ._locale ,
236
+ href = href ,
237
+ calendar = calendar ,
238
+ etag = etag ,
239
+ start = start ,
240
+ end = end ,
241
+ ref = ref ,
242
+ color = self ._calendars [calendar ]['color' ],
243
+ readonly = self ._calendars [calendar ]['readonly' ],
244
+ )
245
+ return event
221
246
222
- def change_collection (self , event , new_collection ):
247
+ def change_collection (self , event : Event , new_collection : str ):
223
248
href , etag , calendar = event .href , event .etag , event .calendar
224
249
event .etag = None
225
250
self .new (event , new_collection )
226
251
self .delete (href , etag , calendar = calendar )
227
252
228
- def new_event (self , ical , collection ):
253
+ def new_event (self , ical : str , collection : str ):
229
254
"""creates and returns (but does not insert) new event from ical
230
255
string"""
231
256
calendar = collection or self .writable_names [0 ]
@@ -240,7 +265,7 @@ def update_db(self):
240
265
if self ._needs_update (calendar , remember = True ):
241
266
self ._db_update (calendar )
242
267
243
- def needs_update (self ):
268
+ def needs_update (self ) -> bool :
244
269
"""Check if you need to call update_db.
245
270
246
271
This could either be the case because the vdirs were changed externally,
@@ -266,14 +291,14 @@ def needs_update(self):
266
291
return True
267
292
return False
268
293
269
- def _needs_update (self , calendar , remember = False ):
294
+ def _needs_update (self , calendar : str , remember : bool = False ) -> bool :
270
295
"""checks if the db for the given calendar needs an update"""
271
296
local_ctag = self ._local_ctag (calendar )
272
297
if remember :
273
298
self ._last_ctags [calendar ] = local_ctag
274
299
return local_ctag != self ._backend .get_ctag (calendar )
275
300
276
- def _db_update (self , calendar ):
301
+ def _db_update (self , calendar : str ):
277
302
"""implements the actual db update on a per calendar base"""
278
303
local_ctag = self ._local_ctag (calendar )
279
304
db_hrefs = set (href for href , etag in self ._backend .list (calendar ))
@@ -291,7 +316,7 @@ def _db_update(self, calendar):
291
316
self ._backend .set_ctag (local_ctag , calendar = calendar )
292
317
self ._last_ctags [calendar ] = local_ctag
293
318
294
- def _update_vevent (self , href , calendar ) :
319
+ def _update_vevent (self , href : str , calendar : str ) -> bool :
295
320
"""should only be called during db_update, only updates the db,
296
321
does not check for readonly"""
297
322
event , etag = self ._storages [calendar ].get (href )
@@ -301,7 +326,6 @@ def _update_vevent(self, href, calendar):
301
326
else :
302
327
update = self ._backend .update
303
328
update (event .raw , href = href , etag = etag , calendar = calendar )
304
-
305
329
return True
306
330
except Exception as e :
307
331
if not isinstance (e , (UpdateFailed , UnsupportedFeatureError )):
@@ -311,24 +335,23 @@ def _update_vevent(self, href, calendar):
311
335
'This event will not be available in khal.' .format (calendar , href , str (e )))
312
336
return False
313
337
314
- def search (self , search_string ) :
338
+ def search (self , search_string : str ) -> Iterable [ Event ] :
315
339
"""search for the db for events matching `search_string`"""
316
- return (self ._cover_event ( event ) for event in self ._backend .search (search_string ))
340
+ return (self ._construct_event ( * args ) for args in self ._backend .search (search_string ))
317
341
318
- def get_day_styles (self , day , focus ) :
319
- devents = list ( self .get_events_on (day , minimal = True ) )
320
- if len (devents ) == 0 :
342
+ def get_day_styles (self , day : dt . date , focus : bool ) -> Union [ str , Tuple [ str , str ]] :
343
+ calendars = self .get_calendars_on (day )
344
+ if len (calendars ) == 0 :
321
345
return None
322
346
if self .color != '' :
323
347
return 'highlight_days_color'
324
- dcalendars = list (set (map (lambda event : event .calendar , devents )))
325
- if len (dcalendars ) == 1 :
326
- return 'calendar ' + dcalendars [0 ]
348
+ if len (calendars ) == 1 :
349
+ return 'calendar ' + calendars [0 ]
327
350
if self .multiple != '' :
328
351
return 'highlight_days_multiple'
329
- return ('calendar ' + dcalendars [0 ], 'calendar ' + dcalendars [1 ])
352
+ return ('calendar ' + calendars [0 ], 'calendar ' + calendars [1 ])
330
353
331
- def get_styles (self , date , focus ) :
354
+ def get_styles (self , date : dt . date , focus : bool ) -> Union [ str , None , Tuple [ str , str ]] :
332
355
if focus :
333
356
if date == date .today ():
334
357
return 'today focus'
0 commit comments