Skip to content

Commit f4368ef

Browse files
Castavoemersion
authored andcommitted
✨(backend) add schemas for Notion API
1 parent 86ccd98 commit f4368ef

File tree

5 files changed

+480
-0
lines changed

5 files changed

+480
-0
lines changed
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
from datetime import datetime
2+
from enum import StrEnum
3+
from typing import Annotated, Any, Literal
4+
5+
from pydantic import BaseModel, Discriminator, Field, ValidationError, model_validator
6+
7+
from .notion_color import NotionColor
8+
from .notion_file import NotionFile
9+
from .notion_rich_text import NotionRichText
10+
11+
"""Usage: NotionBlock.model_validate(response.json())"""
12+
13+
14+
class NotionBlock(BaseModel):
15+
id: str
16+
created_time: datetime
17+
last_edited_time: datetime
18+
archived: bool
19+
specific: "NotionBlockSpecifics"
20+
has_children: bool
21+
children: list["NotionBlock"] = Field(init=False, default_factory=list)
22+
23+
@model_validator(mode="before")
24+
@classmethod
25+
def move_type_inward_and_rename(cls, data: Any) -> Any:
26+
if not isinstance(data, dict):
27+
return data
28+
29+
if "type" not in data:
30+
raise ValidationError("Type must be specified")
31+
32+
data_type = data.pop("type")
33+
data["specific"] = data.pop(data_type)
34+
data["specific"]["block_type"] = data_type
35+
36+
return data
37+
38+
39+
class NotionBlockType(StrEnum):
40+
"""https://developers.notion.com/reference/block"""
41+
42+
BOOKMARK = "bookmark"
43+
BREADCRUMB = "breadcrumb"
44+
BULLETED_LIST_ITEM = "bulleted_list_item"
45+
CALLOUT = "callout"
46+
CHILD_DATABASE = "child_database"
47+
CHILD_PAGE = "child_page"
48+
CODE = "code"
49+
COLUMN = "column"
50+
COLUMN_LIST = "column_list"
51+
DIVIDER = "divider"
52+
EMBED = "embed"
53+
EQUATION = "equation"
54+
FILE = "file"
55+
HEADING_1 = "heading_1"
56+
HEADING_2 = "heading_2"
57+
HEADING_3 = "heading_3"
58+
IMAGE = "image"
59+
LINK_PREVIEW = "link_preview"
60+
LINK_TO_PAGE = "link_to_page"
61+
NUMBERED_LIST_ITEM = "numbered_list_item"
62+
PARAGRAPH = "paragraph"
63+
PDF = "pdf"
64+
QUOTE = "quote"
65+
SYNCED_BLOCK = "synced_block"
66+
TABLE = "table"
67+
TABLE_OF_CONTENTS = "table_of_contents"
68+
TABLE_ROW = "table_row"
69+
TEMPLATE = "template"
70+
TO_DO = "to_do"
71+
TOGGLE = "toggle"
72+
UNSUPPORTED = "unsupported"
73+
VIDEO = "video"
74+
75+
76+
class NotionHeadingBase(BaseModel):
77+
"""https://developers.notion.com/reference/block#headings"""
78+
79+
rich_text: list[NotionRichText]
80+
color: NotionColor
81+
is_toggleable: bool = False
82+
83+
84+
class NotionHeading1(NotionHeadingBase):
85+
block_type: Literal[NotionBlockType.HEADING_1] = NotionBlockType.HEADING_1
86+
87+
88+
class NotionHeading2(NotionHeadingBase):
89+
block_type: Literal[NotionBlockType.HEADING_2] = NotionBlockType.HEADING_2
90+
91+
92+
class NotionHeading3(NotionHeadingBase):
93+
block_type: Literal[NotionBlockType.HEADING_3] = NotionBlockType.HEADING_3
94+
95+
96+
class NotionParagraph(BaseModel):
97+
"""https://developers.notion.com/reference/block#paragraph"""
98+
99+
block_type: Literal[NotionBlockType.PARAGRAPH] = NotionBlockType.PARAGRAPH
100+
rich_text: list[NotionRichText]
101+
color: NotionColor
102+
children: list["NotionBlock"] = Field(default_factory=list)
103+
104+
105+
class NotionBulletedListItem(BaseModel):
106+
"""https://developers.notion.com/reference/block#bulleted-list-item"""
107+
108+
block_type: Literal[NotionBlockType.BULLETED_LIST_ITEM] = (
109+
NotionBlockType.BULLETED_LIST_ITEM
110+
)
111+
rich_text: list[NotionRichText]
112+
color: NotionColor
113+
children: list["NotionBlock"] = Field(default_factory=list)
114+
115+
116+
class NotionNumberedListItem(BaseModel):
117+
"""https://developers.notion.com/reference/block#numbered-list-item"""
118+
119+
block_type: Literal[NotionBlockType.NUMBERED_LIST_ITEM] = (
120+
NotionBlockType.NUMBERED_LIST_ITEM
121+
)
122+
rich_text: list[NotionRichText]
123+
color: NotionColor
124+
children: list["NotionBlock"] = Field(default_factory=list)
125+
126+
127+
class NotionToDo(BaseModel):
128+
"""https://developers.notion.com/reference/block#to-do"""
129+
130+
block_type: Literal[NotionBlockType.TO_DO] = NotionBlockType.TO_DO
131+
rich_text: list[NotionRichText]
132+
checked: bool
133+
color: NotionColor
134+
children: list["NotionBlock"] = Field(default_factory=list)
135+
136+
137+
class NotionCode(BaseModel):
138+
"""https://developers.notion.com/reference/block#code"""
139+
140+
block_type: Literal[NotionBlockType.CODE] = NotionBlockType.CODE
141+
caption: list[NotionRichText]
142+
rich_text: list[NotionRichText]
143+
language: str # Actually an enum
144+
145+
146+
class NotionCallout(BaseModel):
147+
"""https://developers.notion.com/reference/block#callout"""
148+
149+
block_type: Literal[NotionBlockType.CALLOUT] = NotionBlockType.CALLOUT
150+
rich_text: list[NotionRichText]
151+
# icon: Any # could be an emoji or an image
152+
color: NotionColor
153+
154+
155+
class NotionDivider(BaseModel):
156+
"""https://developers.notion.com/reference/block#divider"""
157+
158+
block_type: Literal[NotionBlockType.DIVIDER] = NotionBlockType.DIVIDER
159+
160+
161+
class NotionEmbed(BaseModel):
162+
"""https://developers.notion.com/reference/block#embed"""
163+
164+
block_type: Literal[NotionBlockType.EMBED] = NotionBlockType.EMBED
165+
url: str
166+
167+
168+
class NotionFileType(StrEnum):
169+
FILE = "file"
170+
EXTERNAL = "external"
171+
FILE_UPLOAD = "file_upload"
172+
173+
174+
class NotionFile(BaseModel):
175+
# FIXME: this is actually another occurrence of type discriminating
176+
"""https://developers.notion.com/reference/block#file"""
177+
178+
block_type: Literal[NotionBlockType.FILE] = NotionBlockType.FILE
179+
caption: list[NotionRichText]
180+
type: NotionFileType
181+
...
182+
183+
184+
class NotionImage(BaseModel):
185+
"""https://developers.notion.com/reference/block#image"""
186+
187+
block_type: Literal[NotionBlockType.IMAGE] = NotionBlockType.IMAGE
188+
file: NotionFile
189+
190+
191+
class NotionVideo(BaseModel):
192+
"""https://developers.notion.com/reference/block#video"""
193+
194+
block_type: Literal[NotionBlockType.VIDEO] = NotionBlockType.VIDEO
195+
# FIXME: this actually contains a file reference which will be defined for the above, but with the "video" attribute
196+
197+
198+
class NotionLinkPreview(BaseModel):
199+
"""https://developers.notion.com/reference/block#link-preview"""
200+
201+
block_type: Literal[NotionBlockType.LINK_PREVIEW] = NotionBlockType.LINK_PREVIEW
202+
url: str
203+
204+
205+
class NotionBookmark(BaseModel):
206+
"""https://developers.notion.com/reference/block#bookmark"""
207+
208+
block_type: Literal[NotionBlockType.BOOKMARK] = NotionBlockType.BOOKMARK
209+
url: str
210+
caption: list[NotionRichText] = Field(default_factory=list)
211+
212+
213+
class NotionTable(BaseModel):
214+
"""https://developers.notion.com/reference/block#table
215+
216+
The children of this block are NotionTableRow blocks."""
217+
218+
block_type: Literal[NotionBlockType.TABLE] = NotionBlockType.TABLE
219+
table_width: int
220+
has_column_header: bool
221+
has_row_header: bool
222+
223+
224+
class NotionTableRow(BaseModel):
225+
"""https://developers.notion.com/reference/block#table-row"""
226+
227+
block_type: Literal[NotionBlockType.TABLE_ROW] = NotionBlockType.TABLE_ROW
228+
cells: list[list[NotionRichText]] # Each cell is a list of rich text objects
229+
230+
231+
class NotionColumnList(BaseModel):
232+
"""https://developers.notion.com/reference/block#column-list-and-column"""
233+
234+
block_type: Literal[NotionBlockType.COLUMN_LIST] = NotionBlockType.COLUMN_LIST
235+
236+
237+
class NotionColumn(BaseModel):
238+
"""https://developers.notion.com/reference/block#column-list-and-column"""
239+
240+
block_type: Literal[NotionBlockType.COLUMN] = NotionBlockType.COLUMN
241+
242+
243+
class NotionChildPage(BaseModel):
244+
"""https://developers.notion.com/reference/block#child-page
245+
246+
My guess is that the actual child page is a child of this block ? We don't have the id..."""
247+
248+
block_type: Literal[NotionBlockType.CHILD_PAGE] = NotionBlockType.CHILD_PAGE
249+
title: str
250+
251+
252+
class NotionUnsupported(BaseModel):
253+
block_type: str
254+
raw: dict[str, Any] | None = None
255+
256+
@model_validator(mode="before")
257+
@classmethod
258+
def put_all_in_raw(cls, data: Any) -> Any:
259+
if not isinstance(data, dict):
260+
return data
261+
262+
if "raw" not in data:
263+
data["raw"] = data.copy()
264+
265+
return data
266+
267+
268+
NotionBlockSpecifics = Annotated[
269+
Annotated[
270+
NotionHeading1
271+
| NotionHeading2
272+
| NotionHeading3
273+
| NotionParagraph
274+
| NotionNumberedListItem
275+
| NotionBulletedListItem
276+
| NotionToDo
277+
| NotionCode
278+
| NotionColumn
279+
| NotionColumnList
280+
| NotionDivider
281+
| NotionEmbed
282+
| NotionFile
283+
| NotionImage
284+
| NotionVideo
285+
| NotionLinkPreview
286+
| NotionTable
287+
| NotionTableRow
288+
| NotionChildPage
289+
| NotionCallout
290+
| NotionLinkPreview
291+
| NotionBookmark,
292+
Discriminator(discriminator="block_type"),
293+
]
294+
| NotionUnsupported,
295+
Field(union_mode="left_to_right"),
296+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from enum import StrEnum
2+
3+
4+
class NotionColor(StrEnum):
5+
DEFAULT = "default"
6+
BLUE = "blue"
7+
BLUE_BACKGROUND = "blue_background"
8+
BROWN = "brown"
9+
BROWN_BACKGROUND = "brown_background"
10+
GRAY = "gray"
11+
GRAY_BACKGROUND = "gray_background"
12+
GREEN = "green"
13+
GREEN_BACKGROUND = "green_background"
14+
ORANGE = "orange"
15+
ORANGE_BACKGROUND = "orange_background"
16+
YELLOW = "yellow"
17+
YELLOW_BACKGROUND = "yellow_background"
18+
PINK = "pink"
19+
PINK_BACKGROUND = "pink_background"
20+
PURPLE = "purple"
21+
PURPLE_BACKGROUND = "purple_background"
22+
RED = "red"
23+
RED_BACKGROUND = "red_background"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from enum import StrEnum
2+
from typing import Annotated, Literal
3+
4+
from pydantic import BaseModel, Discriminator
5+
6+
7+
class NotionFileType(StrEnum):
8+
HOSTED = "file"
9+
UPLOAD = "file_upload"
10+
EXTERNAL = "external"
11+
12+
13+
class NotionFileHosted(BaseModel):
14+
type: Literal[NotionFileType.HOSTED] = NotionFileType.HOSTED
15+
file: dict # TODO
16+
17+
18+
class NotionFileUpload(BaseModel):
19+
type: Literal[NotionFileType.UPLOAD] = NotionFileType.UPLOAD
20+
file_upload: dict # TODO
21+
22+
23+
class NotionFileExternal(BaseModel):
24+
type: Literal[NotionFileType.EXTERNAL] = NotionFileType.EXTERNAL
25+
external: dict # TODO
26+
27+
28+
NotionFile = Annotated[
29+
NotionFileHosted | NotionFileUpload | NotionFileExternal,
30+
Discriminator(discriminator="type"),
31+
]

0 commit comments

Comments
 (0)