Skip to content

Commit

Permalink
Added "NotionDate" object, some caching, quick fixes, and version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
jamalex committed Jan 18, 2019
1 parent 1a39b92 commit f5e66f2
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 14 deletions.
3 changes: 2 additions & 1 deletion notion/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import time
import uuid

from cached_property import cached_property
from copy import deepcopy

from .logger import logger
Expand Down Expand Up @@ -608,7 +609,7 @@ class CollectionViewBlock(MediaBlock):

_type = "collection_view"

@property
@cached_property
def collection(self):
collection_id = self.get("collection_id")
if not collection_id:
Expand Down
113 changes: 103 additions & 10 deletions notion/collection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from cached_property import cached_property
from copy import deepcopy
from datetime import datetime
from datetime import datetime, date
from tzlocal import get_localzone

from .block import Block, PageBlock
from .logger import logger
Expand All @@ -10,6 +12,82 @@
from .utils import add_signed_prefix_as_needed, remove_signed_prefix_as_needed, slugify


class NotionDate(object):

start = None
end = None
timezone = None

def __init__(self, start, end=None, timezone=None):
self.start = start
self.end = end
self.timezone = timezone

@classmethod
def from_notion(cls, obj):
if isinstance(obj, dict):
data = obj
elif isinstance(obj, list):
data = obj[0][1][0][1]
else:
return None
start = cls._parse_datetime(data.get("start_date"), data.get("start_time"))
end = cls._parse_datetime(data.get("end_date"), data.get("end_time"))
timezone = data.get("timezone")
return cls(start, end=end, timezone=timezone)

@classmethod
def _parse_datetime(cls, date_str, time_str):
if not date_str:
return None
if time_str:
return datetime.strptime(date_str + " " + time_str, '%Y-%m-%d %H:%M')
else:
return date.strptime(date_str, '%Y-%m-%d')

def _format_datetime(self, date_or_datetime):
if not date_or_datetime:
return None, None
if isinstance(date_or_datetime, datetime):
return date_or_datetime.strftime("%Y-%m-%d"), date_or_datetime.strftime("%H:%M")
else:
return date_or_datetime.strftime("%Y-%m-%d"), None

def type(self):
name = "date"
if isinstance(self.start, datetime):
name += "time"
if self.end:
name += "range"
return name

def to_notion(self):

if self.end:
self.start, self.end = sorted([self.start, self.end])

start_date, start_time = self._format_datetime(self.start)
end_date, end_time = self._format_datetime(self.end)

if not start_date:
return []

data = {
"type": self.type(),
"start_date": start_date,
}

if end_date:
data["end_date"] = end_date

if "time" in data["type"]:
data["time_zone"] = str(self.timezone or get_localzone())
data["start_time"] = start_time or "00:00"
if end_date:
data["end_time"] = end_time or "00:00"

return [["‣", [["d", data]]]]

class Collection(Record):
"""
A "collection" corresponds to what's sometimes called a "database" in the Notion UI.
Expand All @@ -21,6 +99,10 @@ class Collection(Record):
description = field_map("description", api_to_python=notion_to_markdown, python_to_api=markdown_to_notion)
cover = field_map("cover")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._client.refresh_collection_rows(self.id)

def get_schema_properties(self):
"""
Fetch a flattened list of all properties in the collection's schema.
Expand All @@ -43,17 +125,21 @@ def get_schema_property(self, identifier):
return prop
return None

def add_row(self):
def add_row(self, **kwargs):
"""
Create a new empty CollectionRowBlock under this collection, and return the instance.
"""

row_id = self._client.create_record("block", self, type="page")
row = CollectionRowBlock(self._client, row_id)

return CollectionRowBlock(self._client, row_id)
with self._client.as_atomic_transaction():
for key, val in kwargs.items():
setattr(key, val)

def get_rows(self):
return row

def get_rows(self):
return [self._client.get_block(row_id) for row_id in self._client._store.get_collection_rows(self.id)]

def _convert_diff_to_changelist(self, difference, old_val, new_val):
Expand Down Expand Up @@ -164,7 +250,7 @@ def execute(self):

class CollectionRowBlock(PageBlock):

@property
@cached_property
def collection(self):
return self._client.get_collection(self.get("parent_id"))

Expand All @@ -191,11 +277,11 @@ def __dir__(self):
return self._get_property_slugs() + super().__dir__()

def get_property(self, identifier):

prop = self.collection.get_schema_property(identifier)
if prop is None:
raise AttributeError("Object does not have property '{}'".format(identifier))

val = self.get(["properties", prop["id"]])

return self._convert_notion_to_python(val, prop)
Expand Down Expand Up @@ -246,7 +332,7 @@ def _convert_notion_to_python(self, val, prop):
if prop["type"] in ["email", "phone_number", "url"]:
val = val[0][0] if val else ""
if prop["type"] in ["date"]:
val = val[0][1][0][1] if val else None
val = NotionDate.from_notion(val)
if prop["type"] in ["file"]:
val = [add_signed_prefix_as_needed(item[1][0][1]) for item in val if item[0] != ","] if val else []
if prop["type"] in ["checkbox"]:
Expand Down Expand Up @@ -274,7 +360,7 @@ def set_property(self, identifier, val):
prop = self.collection.get_schema_property(identifier)
if prop is None:
raise AttributeError("Object does not have property '{}'".format(identifier))

path, val = self._convert_python_to_notion(val, prop)

self.set(path, val)
Expand Down Expand Up @@ -316,7 +402,12 @@ def _convert_python_to_notion(self, val, prop):
if prop["type"] in ["email", "phone_number", "url"]:
val = [[val, [["a", val]]]]
if prop["type"] in ["date"]:
val = [['‣', [['d', val]]]]
if isinstance(val, date) or isinstance(val, datetime):
val = NotionDate(val)
if isinstance(val, NotionDate):
val = val.to_notion()
else:
val = []
if prop["type"] in ["file"]:
filelist = []
if not isinstance(val, list):
Expand All @@ -332,6 +423,8 @@ def _convert_python_to_notion(self, val, prop):
val = [["Yes" if val else "No"]]
if prop["type"] in ["relation"]:
pagelist = []
if not isinstance(val, list):
val = [val]
for page in val:
if isinstance(page, str):
page = self._client.get_block(page)
Expand Down
7 changes: 6 additions & 1 deletion notion/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,9 @@ def poll_async(self):

def poll_forever(self):
while True:
self.poll()
try:
self.poll()
except Exception as e:
logger.error("Encountered error during polling!")
logger.error(e, exc_info=True)
time.sleep(1)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ commonmark
bs4
tzlocal
python-slugify
dictdiffer
dictdiffer
cached-property
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

setuptools.setup(
name="notion",
version="0.0.15",
version="0.0.17",
author="Jamie Alexandre",
author_email="[email protected]",
description="Unofficial Python API client for Notion.so",
Expand Down

0 comments on commit f5e66f2

Please sign in to comment.