|
| 1 | +from typing import List, Union |
| 2 | + |
| 3 | + |
| 4 | +def class_from_type_name(type_name: str): |
| 5 | + return { |
| 6 | + "child_page": ChildPage, |
| 7 | + "paragraph": Paragraph, |
| 8 | + "heading_1": Heading, |
| 9 | + "heading_2": SubHeading, |
| 10 | + "heading_3": SubSubHeading, |
| 11 | + "quote": Quote, |
| 12 | + }[type_name] |
| 13 | + |
| 14 | + |
| 15 | +class ChildrenMixin: |
| 16 | + @property |
| 17 | + def children(self) -> list: |
| 18 | + return [ |
| 19 | + class_from_type_name(data["type"])(client=self._client, data=data) |
| 20 | + for data in self._client.retrieve_block_children(self.id)["results"] |
| 21 | + ] |
| 22 | + |
| 23 | + def append_children(self, children: Union[dict, List[dict]]): |
| 24 | + """Append blocks or pages to a parent. |
| 25 | +
|
| 26 | + TODO: Instead of appending items one by one, batch blocks to be more efficient. |
| 27 | + """ |
| 28 | + if not isinstance(children, list): |
| 29 | + children = [children] |
| 30 | + children = [c._data for c in children] |
| 31 | + |
| 32 | + res = [] |
| 33 | + for c in children: |
| 34 | + object_name = c["object"] |
| 35 | + if object_name == "block": |
| 36 | + res.extend(self._client.append_block_children(self.id, [c])) |
| 37 | + elif object_name == "page": |
| 38 | + # TODO Also support database_id |
| 39 | + c["parent"] = {"type": "page_id", "page_id": self.id} |
| 40 | + res.append(self._client.create_page(c)) |
| 41 | + else: |
| 42 | + raise TypeError( |
| 43 | + f"Appending objects of type {object_name} is not supported." |
| 44 | + ) |
| 45 | + |
| 46 | + return res |
| 47 | + |
| 48 | + |
| 49 | +class TitleMixin: |
| 50 | + @property |
| 51 | + def title(self) -> str: |
| 52 | + return self._data["properties"]["title"]["title"][0]["plain_text"] |
| 53 | + |
| 54 | + @title.setter |
| 55 | + def title(self, new_title: str): |
| 56 | + new_data = self._client.update_page( |
| 57 | + self.id, |
| 58 | + {"properties": {"title": {"title": [{"text": {"content": new_title}}]}}}, |
| 59 | + ) |
| 60 | + self._data = new_data |
| 61 | + |
| 62 | + |
| 63 | +class Block: |
| 64 | + def __init__(self, client=None, data=None): |
| 65 | + self._client = client |
| 66 | + self._data = data |
| 67 | + |
| 68 | + @property |
| 69 | + def id(self) -> str: |
| 70 | + return self._data["id"].replace("-", "") |
| 71 | + |
| 72 | + @property |
| 73 | + def type(self) -> str: |
| 74 | + return self._data["type"] |
| 75 | + |
| 76 | + def delete(self): |
| 77 | + self._client.delete_block(self.id) |
| 78 | + |
| 79 | + |
| 80 | +# TODO: Pages are technically not Blocks. So model this in inheritance as well. |
| 81 | +class Page(Block, ChildrenMixin, TitleMixin): |
| 82 | + def __init__(self, title: str = None, data=None, client=None): |
| 83 | + if title: |
| 84 | + data = { |
| 85 | + "object": "page", |
| 86 | + "properties": { |
| 87 | + "title": {"title": [{"text": {"content": title}}]}, |
| 88 | + }, |
| 89 | + } |
| 90 | + super().__init__(client, data) |
| 91 | + |
| 92 | + def delete(self): |
| 93 | + self._client.delete_page(self.id) |
| 94 | + |
| 95 | + |
| 96 | +class ChildPage(Block): |
| 97 | + """A page contained in another page. |
| 98 | +
|
| 99 | + From the Notion docs (https://developers.notion.com/docs/working-with-page-content#modeling-content-as-blocks): |
| 100 | + When a child page appears inside another page, it's represented as a `child_page` block, which does not have children. |
| 101 | + You should think of this as a reference to the page block. |
| 102 | + """ |
| 103 | + |
| 104 | + @property |
| 105 | + def title(self) -> str: |
| 106 | + return self._data["child_page"]["title"] |
| 107 | + |
| 108 | + def delete(self): |
| 109 | + """Delete the ChildPage. |
| 110 | +
|
| 111 | + Needs to be overwritten to use the `delete_page` endpoint instead of `delete_block`. |
| 112 | + """ |
| 113 | + self._client.delete_page(self.id) |
| 114 | + |
| 115 | + |
| 116 | +class RichText(Block): |
| 117 | + def __init__( |
| 118 | + self, class_name: str = None, text: str = None, data=None, client=None |
| 119 | + ) -> None: |
| 120 | + if class_name and text: |
| 121 | + data = { |
| 122 | + "object": "block", |
| 123 | + "type": class_name, |
| 124 | + class_name: { |
| 125 | + "rich_text": [{"type": "text", "text": {"content": text}}] |
| 126 | + }, |
| 127 | + } |
| 128 | + |
| 129 | + super().__init__(client, data) |
| 130 | + |
| 131 | + @property |
| 132 | + def text(self) -> str: |
| 133 | + return self._data[self.type]["rich_text"][0]["text"]["content"] |
| 134 | + |
| 135 | + @text.setter |
| 136 | + def text(self, new_text: str): |
| 137 | + new_data = self._client.update_block( |
| 138 | + self.id, {self.type: {"rich_text": [{"text": {"content": new_text}}]}} |
| 139 | + ) |
| 140 | + self._data = new_data |
| 141 | + |
| 142 | + |
| 143 | +class Paragraph(RichText): |
| 144 | + def __init__(self, text: str = None, data=None, client=None) -> None: |
| 145 | + super().__init__("paragraph", text, data, client) |
| 146 | + |
| 147 | + |
| 148 | +class Heading(RichText): |
| 149 | + def __init__(self, text: str = None, data=None, client=None) -> None: |
| 150 | + super().__init__("heading_1", text, data, client) |
| 151 | + |
| 152 | + |
| 153 | +class SubHeading(RichText): |
| 154 | + def __init__(self, text: str = None, data=None, client=None) -> None: |
| 155 | + super().__init__("heading_2", text, data, client) |
| 156 | + |
| 157 | + |
| 158 | +class SubSubHeading(RichText): |
| 159 | + def __init__(self, text: str = None, data=None, client=None) -> None: |
| 160 | + super().__init__("heading_3", text, data, client) |
| 161 | + |
| 162 | + |
| 163 | +class Quote(RichText): |
| 164 | + def __init__(self, text: str = None, data=None, client=None) -> None: |
| 165 | + super().__init__("quote", text, data, client) |
0 commit comments