1
+ from cached_property import cached_property
1
2
from copy import deepcopy
2
- from datetime import datetime
3
+ from datetime import datetime , date
4
+ from tzlocal import get_localzone
3
5
4
6
from .block import Block , PageBlock
5
7
from .logger import logger
10
12
from .utils import add_signed_prefix_as_needed , remove_signed_prefix_as_needed , slugify
11
13
12
14
15
+ class NotionDate (object ):
16
+
17
+ start = None
18
+ end = None
19
+ timezone = None
20
+
21
+ def __init__ (self , start , end = None , timezone = None ):
22
+ self .start = start
23
+ self .end = end
24
+ self .timezone = timezone
25
+
26
+ @classmethod
27
+ def from_notion (cls , obj ):
28
+ if isinstance (obj , dict ):
29
+ data = obj
30
+ elif isinstance (obj , list ):
31
+ data = obj [0 ][1 ][0 ][1 ]
32
+ else :
33
+ return None
34
+ start = cls ._parse_datetime (data .get ("start_date" ), data .get ("start_time" ))
35
+ end = cls ._parse_datetime (data .get ("end_date" ), data .get ("end_time" ))
36
+ timezone = data .get ("timezone" )
37
+ return cls (start , end = end , timezone = timezone )
38
+
39
+ @classmethod
40
+ def _parse_datetime (cls , date_str , time_str ):
41
+ if not date_str :
42
+ return None
43
+ if time_str :
44
+ return datetime .strptime (date_str + " " + time_str , '%Y-%m-%d %H:%M' )
45
+ else :
46
+ return date .strptime (date_str , '%Y-%m-%d' )
47
+
48
+ def _format_datetime (self , date_or_datetime ):
49
+ if not date_or_datetime :
50
+ return None , None
51
+ if isinstance (date_or_datetime , datetime ):
52
+ return date_or_datetime .strftime ("%Y-%m-%d" ), date_or_datetime .strftime ("%H:%M" )
53
+ else :
54
+ return date_or_datetime .strftime ("%Y-%m-%d" ), None
55
+
56
+ def type (self ):
57
+ name = "date"
58
+ if isinstance (self .start , datetime ):
59
+ name += "time"
60
+ if self .end :
61
+ name += "range"
62
+ return name
63
+
64
+ def to_notion (self ):
65
+
66
+ if self .end :
67
+ self .start , self .end = sorted ([self .start , self .end ])
68
+
69
+ start_date , start_time = self ._format_datetime (self .start )
70
+ end_date , end_time = self ._format_datetime (self .end )
71
+
72
+ if not start_date :
73
+ return []
74
+
75
+ data = {
76
+ "type" : self .type (),
77
+ "start_date" : start_date ,
78
+ }
79
+
80
+ if end_date :
81
+ data ["end_date" ] = end_date
82
+
83
+ if "time" in data ["type" ]:
84
+ data ["time_zone" ] = str (self .timezone or get_localzone ())
85
+ data ["start_time" ] = start_time or "00:00"
86
+ if end_date :
87
+ data ["end_time" ] = end_time or "00:00"
88
+
89
+ return [["‣" , [["d" , data ]]]]
90
+
13
91
class Collection (Record ):
14
92
"""
15
93
A "collection" corresponds to what's sometimes called a "database" in the Notion UI.
@@ -21,6 +99,10 @@ class Collection(Record):
21
99
description = field_map ("description" , api_to_python = notion_to_markdown , python_to_api = markdown_to_notion )
22
100
cover = field_map ("cover" )
23
101
102
+ def __init__ (self , * args , ** kwargs ):
103
+ super ().__init__ (* args , ** kwargs )
104
+ self ._client .refresh_collection_rows (self .id )
105
+
24
106
def get_schema_properties (self ):
25
107
"""
26
108
Fetch a flattened list of all properties in the collection's schema.
@@ -43,17 +125,21 @@ def get_schema_property(self, identifier):
43
125
return prop
44
126
return None
45
127
46
- def add_row (self ):
128
+ def add_row (self , ** kwargs ):
47
129
"""
48
130
Create a new empty CollectionRowBlock under this collection, and return the instance.
49
131
"""
50
132
51
133
row_id = self ._client .create_record ("block" , self , type = "page" )
134
+ row = CollectionRowBlock (self ._client , row_id )
52
135
53
- return CollectionRowBlock (self ._client , row_id )
136
+ with self ._client .as_atomic_transaction ():
137
+ for key , val in kwargs .items ():
138
+ setattr (key , val )
54
139
55
- def get_rows ( self ):
140
+ return row
56
141
142
+ def get_rows (self ):
57
143
return [self ._client .get_block (row_id ) for row_id in self ._client ._store .get_collection_rows (self .id )]
58
144
59
145
def _convert_diff_to_changelist (self , difference , old_val , new_val ):
@@ -164,7 +250,7 @@ def execute(self):
164
250
165
251
class CollectionRowBlock (PageBlock ):
166
252
167
- @property
253
+ @cached_property
168
254
def collection (self ):
169
255
return self ._client .get_collection (self .get ("parent_id" ))
170
256
@@ -191,11 +277,11 @@ def __dir__(self):
191
277
return self ._get_property_slugs () + super ().__dir__ ()
192
278
193
279
def get_property (self , identifier ):
194
-
280
+
195
281
prop = self .collection .get_schema_property (identifier )
196
282
if prop is None :
197
283
raise AttributeError ("Object does not have property '{}'" .format (identifier ))
198
-
284
+
199
285
val = self .get (["properties" , prop ["id" ]])
200
286
201
287
return self ._convert_notion_to_python (val , prop )
@@ -246,7 +332,7 @@ def _convert_notion_to_python(self, val, prop):
246
332
if prop ["type" ] in ["email" , "phone_number" , "url" ]:
247
333
val = val [0 ][0 ] if val else ""
248
334
if prop ["type" ] in ["date" ]:
249
- val = val [ 0 ][ 1 ][ 0 ][ 1 ] if val else None
335
+ val = NotionDate . from_notion ( val )
250
336
if prop ["type" ] in ["file" ]:
251
337
val = [add_signed_prefix_as_needed (item [1 ][0 ][1 ]) for item in val if item [0 ] != "," ] if val else []
252
338
if prop ["type" ] in ["checkbox" ]:
@@ -274,7 +360,7 @@ def set_property(self, identifier, val):
274
360
prop = self .collection .get_schema_property (identifier )
275
361
if prop is None :
276
362
raise AttributeError ("Object does not have property '{}'" .format (identifier ))
277
-
363
+
278
364
path , val = self ._convert_python_to_notion (val , prop )
279
365
280
366
self .set (path , val )
@@ -316,7 +402,12 @@ def _convert_python_to_notion(self, val, prop):
316
402
if prop ["type" ] in ["email" , "phone_number" , "url" ]:
317
403
val = [[val , [["a" , val ]]]]
318
404
if prop ["type" ] in ["date" ]:
319
- val = [['‣' , [['d' , val ]]]]
405
+ if isinstance (val , date ) or isinstance (val , datetime ):
406
+ val = NotionDate (val )
407
+ if isinstance (val , NotionDate ):
408
+ val = val .to_notion ()
409
+ else :
410
+ val = []
320
411
if prop ["type" ] in ["file" ]:
321
412
filelist = []
322
413
if not isinstance (val , list ):
@@ -332,6 +423,8 @@ def _convert_python_to_notion(self, val, prop):
332
423
val = [["Yes" if val else "No" ]]
333
424
if prop ["type" ] in ["relation" ]:
334
425
pagelist = []
426
+ if not isinstance (val , list ):
427
+ val = [val ]
335
428
for page in val :
336
429
if isinstance (page , str ):
337
430
page = self ._client .get_block (page )
0 commit comments