notionary 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. notionary/__init__.py +14 -2
  2. notionary/blocks/enums.py +27 -6
  3. notionary/blocks/schemas.py +32 -78
  4. notionary/comments/client.py +6 -9
  5. notionary/comments/schemas.py +2 -29
  6. notionary/data_source/http/data_source_instance_client.py +4 -4
  7. notionary/data_source/properties/schemas.py +128 -107
  8. notionary/data_source/query/__init__.py +9 -0
  9. notionary/data_source/query/builder.py +12 -3
  10. notionary/data_source/query/schema.py +5 -0
  11. notionary/data_source/schemas.py +2 -2
  12. notionary/data_source/service.py +43 -132
  13. notionary/database/schemas.py +2 -2
  14. notionary/database/service.py +19 -63
  15. notionary/exceptions/__init__.py +10 -2
  16. notionary/exceptions/api.py +2 -2
  17. notionary/exceptions/base.py +1 -1
  18. notionary/exceptions/block_parsing.py +24 -3
  19. notionary/exceptions/data_source/builder.py +2 -2
  20. notionary/exceptions/data_source/properties.py +3 -3
  21. notionary/exceptions/file_upload.py +67 -0
  22. notionary/exceptions/properties.py +4 -4
  23. notionary/exceptions/search.py +4 -4
  24. notionary/file_upload/__init__.py +4 -0
  25. notionary/file_upload/client.py +124 -210
  26. notionary/file_upload/config/__init__.py +17 -0
  27. notionary/file_upload/config/config.py +32 -0
  28. notionary/file_upload/config/constants.py +16 -0
  29. notionary/file_upload/file/reader.py +28 -0
  30. notionary/file_upload/query/__init__.py +7 -0
  31. notionary/file_upload/query/builder.py +54 -0
  32. notionary/file_upload/query/models.py +37 -0
  33. notionary/file_upload/schemas.py +78 -0
  34. notionary/file_upload/service.py +152 -289
  35. notionary/file_upload/validation/factory.py +64 -0
  36. notionary/file_upload/validation/impl/file_name_length.py +23 -0
  37. notionary/file_upload/validation/models.py +124 -0
  38. notionary/file_upload/validation/port.py +7 -0
  39. notionary/file_upload/validation/service.py +17 -0
  40. notionary/file_upload/validation/validators/__init__.py +11 -0
  41. notionary/file_upload/validation/validators/file_exists.py +15 -0
  42. notionary/file_upload/validation/validators/file_extension.py +122 -0
  43. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  44. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  45. notionary/http/client.py +7 -23
  46. notionary/page/content/factory.py +2 -0
  47. notionary/page/content/parser/factory.py +8 -5
  48. notionary/page/content/parser/parsers/audio.py +8 -33
  49. notionary/page/content/parser/parsers/embed.py +0 -2
  50. notionary/page/content/parser/parsers/file.py +8 -35
  51. notionary/page/content/parser/parsers/file_like_block.py +89 -0
  52. notionary/page/content/parser/parsers/image.py +8 -35
  53. notionary/page/content/parser/parsers/pdf.py +8 -35
  54. notionary/page/content/parser/parsers/video.py +8 -35
  55. notionary/page/content/parser/pre_processsing/handlers/__init__.py +2 -0
  56. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +12 -8
  57. notionary/page/content/parser/pre_processsing/handlers/indentation.py +2 -0
  58. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
  59. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +2 -0
  60. notionary/page/content/renderer/renderers/audio.py +9 -21
  61. notionary/page/content/renderer/renderers/file.py +9 -21
  62. notionary/page/content/renderer/renderers/file_like_block.py +43 -0
  63. notionary/page/content/renderer/renderers/image.py +9 -21
  64. notionary/page/content/renderer/renderers/pdf.py +9 -21
  65. notionary/page/content/renderer/renderers/video.py +9 -21
  66. notionary/page/content/syntax/__init__.py +2 -1
  67. notionary/page/content/syntax/registry.py +38 -60
  68. notionary/page/properties/client.py +3 -3
  69. notionary/page/properties/{models.py → schemas.py} +93 -107
  70. notionary/page/properties/service.py +15 -4
  71. notionary/page/schemas.py +3 -3
  72. notionary/page/service.py +18 -79
  73. notionary/shared/entity/dto_parsers.py +1 -36
  74. notionary/shared/entity/entity_metadata_update_client.py +18 -4
  75. notionary/shared/entity/schemas.py +6 -6
  76. notionary/shared/entity/service.py +121 -40
  77. notionary/shared/models/file.py +34 -6
  78. notionary/shared/models/icon.py +5 -12
  79. notionary/user/bot.py +12 -12
  80. notionary/utils/decorators.py +8 -8
  81. notionary/utils/pagination.py +36 -32
  82. notionary/workspace/__init__.py +2 -2
  83. notionary/workspace/client.py +2 -0
  84. notionary/workspace/query/__init__.py +3 -2
  85. notionary/workspace/query/builder.py +25 -1
  86. notionary/workspace/query/models.py +9 -1
  87. notionary/workspace/query/service.py +15 -11
  88. notionary/workspace/service.py +46 -36
  89. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/METADATA +9 -5
  90. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/RECORD +92 -71
  91. notionary/file_upload/models.py +0 -69
  92. notionary/page/page_context.py +0 -50
  93. notionary/shared/models/cover.py +0 -20
  94. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
  95. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,5 @@
1
1
  from .grammar import MarkdownGrammar
2
+ from .models import SyntaxDefinition
2
3
  from .registry import SyntaxRegistry
3
4
 
4
- __all__ = ["MarkdownGrammar", "SyntaxRegistry"]
5
+ __all__ = ["MarkdownGrammar", "SyntaxDefinition", "SyntaxRegistry"]
@@ -4,6 +4,8 @@ from notionary.page.content.syntax.grammar import MarkdownGrammar
4
4
  from notionary.page.content.syntax.models import SyntaxDefinition, SyntaxRegistryKey
5
5
 
6
6
 
7
+ # TODO: Add support for file upload in blocks for file types (refactor file types aswell)
8
+ # differentiate between external and uploaded files (file like blocks)
7
9
  class SyntaxRegistry:
8
10
  def __init__(self, markdown_markdown_grammar: MarkdownGrammar | None = None) -> None:
9
11
  self._markdown_grammar = markdown_markdown_grammar or MarkdownGrammar()
@@ -92,63 +94,79 @@ class SyntaxRegistry:
92
94
  def get_heading_syntax(self) -> SyntaxDefinition:
93
95
  return self._definitions[SyntaxRegistryKey.HEADING]
94
96
 
97
+ def _create_media_syntax(self, media_type: str, url_pattern: str | None = None) -> SyntaxDefinition:
98
+ url_pattern = url_pattern or "[^)]+"
99
+ return SyntaxDefinition(
100
+ start_delimiter=f"[{media_type}](",
101
+ end_delimiter=")",
102
+ regex_pattern=re.compile(rf"(?<!\!)\[{re.escape(media_type)}\]\(({url_pattern})\)"),
103
+ )
104
+
105
+ def _create_url_media_syntax(self, media_type: str) -> SyntaxDefinition:
106
+ return SyntaxDefinition(
107
+ start_delimiter=f"[{media_type}](",
108
+ end_delimiter=")",
109
+ regex_pattern=re.compile(rf"(?<!\!)\[{re.escape(media_type)}\]\((https?://[^\s)]+)\)"),
110
+ )
111
+
95
112
  def _register_defaults(self) -> None:
96
113
  self._register_audio_syntax()
97
- self._register_bookmark_syntax()
98
- self._register_image_syntax()
99
114
  self._register_video_syntax()
115
+ self._register_image_syntax()
100
116
  self._register_file_syntax()
101
117
  self._register_pdf_syntax()
118
+ self._register_bookmark_syntax()
119
+ self._register_embed_syntax()
102
120
 
103
- # List blocks
104
121
  self._register_bulleted_list_syntax()
105
122
  self._register_numbered_list_syntax()
106
123
  self._register_todo_syntax()
107
124
  self._register_todo_done_syntax()
108
125
 
109
- # Container blocks
110
126
  self._register_toggle_syntax()
111
127
  self._register_toggleable_heading_syntax()
112
128
  self._register_callout_syntax()
113
129
  self._register_quote_syntax()
114
130
  self._register_code_syntax()
115
131
 
116
- # Column layout blocks
117
132
  self._register_column_list_syntax()
118
133
  self._register_column_syntax()
119
134
 
120
135
  self._register_heading_1_syntax()
121
136
  self._register_heading_2_syntax()
122
137
  self._register_heading_3_syntax()
123
- self._register_heading_syntax() # Shared pattern for regular headings
138
+ self._register_heading_syntax()
124
139
 
125
140
  self._register_divider_syntax()
126
141
  self._register_breadcrumb_syntax()
127
142
  self._register_table_of_contents_syntax()
128
143
  self._register_equation_syntax()
129
- self._register_embed_syntax()
130
144
  self._register_table_syntax()
131
145
  self._register_table_row_syntax()
132
146
 
133
- # Post-processing and utility blocks
134
147
  self._register_caption_syntax()
135
148
  self._register_space_syntax()
136
149
 
137
150
  def _register_audio_syntax(self) -> None:
138
- definition = SyntaxDefinition(
139
- start_delimiter="[audio](",
140
- end_delimiter=")",
141
- regex_pattern=re.compile(r"\[audio\]\(([^)]+)\)"),
142
- )
143
- self._definitions[SyntaxRegistryKey.AUDIO] = definition
151
+ self._definitions[SyntaxRegistryKey.AUDIO] = self._create_media_syntax("audio")
152
+
153
+ def _register_video_syntax(self) -> None:
154
+ self._definitions[SyntaxRegistryKey.VIDEO] = self._create_media_syntax("video")
155
+
156
+ def _register_image_syntax(self) -> None:
157
+ self._definitions[SyntaxRegistryKey.IMAGE] = self._create_media_syntax("image")
158
+
159
+ def _register_file_syntax(self) -> None:
160
+ self._definitions[SyntaxRegistryKey.FILE] = self._create_media_syntax("file")
161
+
162
+ def _register_pdf_syntax(self) -> None:
163
+ self._definitions[SyntaxRegistryKey.PDF] = self._create_media_syntax("pdf")
144
164
 
145
165
  def _register_bookmark_syntax(self) -> None:
146
- definition = SyntaxDefinition(
147
- start_delimiter="[bookmark](",
148
- end_delimiter=")",
149
- regex_pattern=re.compile(r"\[bookmark\]\((https?://[^\s\"]+)\)"),
150
- )
151
- self._definitions[SyntaxRegistryKey.BOOKMARK] = definition
166
+ self._definitions[SyntaxRegistryKey.BOOKMARK] = self._create_url_media_syntax("bookmark")
167
+
168
+ def _register_embed_syntax(self) -> None:
169
+ self._definitions[SyntaxRegistryKey.EMBED] = self._create_url_media_syntax("embed")
152
170
 
153
171
  def _register_breadcrumb_syntax(self) -> None:
154
172
  definition = SyntaxDefinition(
@@ -217,14 +235,6 @@ class SyntaxRegistry:
217
235
  )
218
236
  self._definitions[SyntaxRegistryKey.DIVIDER] = definition
219
237
 
220
- def _register_embed_syntax(self) -> None:
221
- definition = SyntaxDefinition(
222
- start_delimiter="[embed](",
223
- end_delimiter=")",
224
- regex_pattern=re.compile(r"\[embed\]\((https?://[^\s)]+)\)"),
225
- )
226
- self._definitions[SyntaxRegistryKey.EMBED] = definition
227
-
228
238
  def _register_equation_syntax(self) -> None:
229
239
  definition = SyntaxDefinition(
230
240
  start_delimiter="$$",
@@ -233,14 +243,6 @@ class SyntaxRegistry:
233
243
  )
234
244
  self._definitions[SyntaxRegistryKey.EQUATION] = definition
235
245
 
236
- def _register_file_syntax(self) -> None:
237
- definition = SyntaxDefinition(
238
- start_delimiter="[file](",
239
- end_delimiter=")",
240
- regex_pattern=re.compile(r"\[file\]\(([^)]+)\)"),
241
- )
242
- self._definitions[SyntaxRegistryKey.FILE] = definition
243
-
244
246
  def _register_heading_1_syntax(self) -> None:
245
247
  definition = SyntaxDefinition(
246
248
  start_delimiter="# ",
@@ -265,14 +267,6 @@ class SyntaxRegistry:
265
267
  )
266
268
  self._definitions[SyntaxRegistryKey.HEADING_3] = definition
267
269
 
268
- def _register_image_syntax(self) -> None:
269
- definition = SyntaxDefinition(
270
- start_delimiter="[image](",
271
- end_delimiter=")",
272
- regex_pattern=re.compile(r"(?<!!)\[image\]\(([^)]+)\)"),
273
- )
274
- self._definitions[SyntaxRegistryKey.IMAGE] = definition
275
-
276
270
  def _register_numbered_list_syntax(self) -> None:
277
271
  definition = SyntaxDefinition(
278
272
  start_delimiter="1. ",
@@ -281,14 +275,6 @@ class SyntaxRegistry:
281
275
  )
282
276
  self._definitions[SyntaxRegistryKey.NUMBERED_LIST] = definition
283
277
 
284
- def _register_pdf_syntax(self) -> None:
285
- definition = SyntaxDefinition(
286
- start_delimiter="[pdf](",
287
- end_delimiter=")",
288
- regex_pattern=re.compile(r"\[pdf\]\(([^)]+)\)"),
289
- )
290
- self._definitions[SyntaxRegistryKey.PDF] = definition
291
-
292
278
  def _register_quote_syntax(self) -> None:
293
279
  definition = SyntaxDefinition(
294
280
  start_delimiter="> ",
@@ -360,14 +346,6 @@ class SyntaxRegistry:
360
346
  )
361
347
  self._definitions[SyntaxRegistryKey.TOGGLEABLE_HEADING] = definition
362
348
 
363
- def _register_video_syntax(self) -> None:
364
- definition = SyntaxDefinition(
365
- start_delimiter="[video](",
366
- end_delimiter=")",
367
- regex_pattern=re.compile(r"\[video\]\(([^)]+)\)"),
368
- )
369
- self._definitions[SyntaxRegistryKey.VIDEO] = definition
370
-
371
349
  def _register_caption_syntax(self) -> None:
372
350
  definition = SyntaxDefinition(
373
351
  start_delimiter="[caption]",
@@ -4,7 +4,7 @@ from pydantic import BaseModel
4
4
 
5
5
  from notionary.blocks.rich_text.models import RichText
6
6
  from notionary.http.client import NotionHttpClient
7
- from notionary.page.properties.models import (
7
+ from notionary.page.properties.schemas import (
8
8
  DateValue,
9
9
  PageCheckboxProperty,
10
10
  PageDateProperty,
@@ -51,8 +51,8 @@ class PagePropertyHttpClient(NotionHttpClient):
51
51
 
52
52
  return await self.patch_page(update_dto)
53
53
 
54
- async def patch_title(self, title: str) -> NotionPageDto:
55
- return await self._patch_property("title", title, PageTitleProperty)
54
+ async def patch_title(self, property_name: str, title: str) -> NotionPageDto:
55
+ return await self._patch_property(property_name, title, PageTitleProperty)
56
56
 
57
57
  async def patch_rich_text_property(self, property_name: str, text: str) -> NotionPageDto:
58
58
  return await self._patch_property(property_name, text, PageRichTextProperty)
@@ -1,9 +1,10 @@
1
1
  from enum import StrEnum
2
- from typing import Annotated, Any, Literal, TypeVar
2
+ from typing import Any, Literal, TypeVar
3
3
 
4
- from pydantic import BaseModel, Field
4
+ from pydantic import BaseModel, ConfigDict, Field
5
5
 
6
6
  from notionary.blocks.rich_text.models import RichText
7
+ from notionary.shared.models.file import File
7
8
  from notionary.shared.properties.type import PropertyType
8
9
  from notionary.shared.typings import JsonDict
9
10
  from notionary.user.schemas import PersonUserResponseDto, UserResponseDto
@@ -38,38 +39,6 @@ class DateValue(BaseModel):
38
39
  time_zone: str | None = None
39
40
 
40
41
 
41
- # ============================================================================
42
- # File Models
43
- # ============================================================================
44
-
45
-
46
- class FileType(StrEnum):
47
- EXTERNAL = "external"
48
- FILE = "file"
49
-
50
-
51
- class ExternalFile(BaseModel):
52
- """External file hosted outside of Notion."""
53
-
54
- url: str
55
-
56
-
57
- class NotionFile(BaseModel):
58
- """File uploaded to Notion with expiration."""
59
-
60
- url: str
61
- expiry_time: str
62
-
63
-
64
- class FileObject(BaseModel):
65
- """File object can be external or uploaded to Notion."""
66
-
67
- name: str
68
- type: FileType
69
- external: ExternalFile | None = None
70
- file: NotionFile | None = None
71
-
72
-
73
42
  # ============================================================================
74
43
  # Formula Models
75
44
  # ============================================================================
@@ -142,25 +111,9 @@ class VerificationValue(BaseModel):
142
111
  # ============================================================================
143
112
 
144
113
 
145
- class PageStatusProperty(PageProperty):
146
- type: Literal[PropertyType.STATUS] = PropertyType.STATUS
147
- status: StatusOption | None = None
148
- options: list[StatusOption] = Field(default_factory=list)
149
-
150
- @property
151
- def option_names(self) -> list[str]:
152
- return [option.name for option in self.options]
153
-
154
-
155
- class PageRelationProperty(PageProperty):
156
- type: Literal[PropertyType.RELATION] = PropertyType.RELATION
157
- relation: list[RelationItem] = Field(default_factory=list)
158
- has_more: bool = False
159
-
160
-
161
- class PageURLProperty(PageProperty):
162
- type: Literal[PropertyType.URL] = PropertyType.URL
163
- url: str | None = None
114
+ class PageTitleProperty(PageProperty):
115
+ type: Literal[PropertyType.TITLE] = PropertyType.TITLE
116
+ title: list[RichText] = Field(default_factory=list)
164
117
 
165
118
 
166
119
  class PageRichTextProperty(PageProperty):
@@ -168,6 +121,16 @@ class PageRichTextProperty(PageProperty):
168
121
  rich_text: list[RichText] = Field(default_factory=list)
169
122
 
170
123
 
124
+ class PageSelectProperty(PageProperty):
125
+ type: Literal[PropertyType.SELECT] = PropertyType.SELECT
126
+ select: SelectOption | None = None
127
+ options: list[SelectOption] = Field(default_factory=list)
128
+
129
+ @property
130
+ def option_names(self) -> list[str]:
131
+ return [option.name for option in self.options]
132
+
133
+
171
134
  class PageMultiSelectProperty(PageProperty):
172
135
  type: Literal[PropertyType.MULTI_SELECT] = PropertyType.MULTI_SELECT
173
136
  multi_select: list[SelectOption] = Field(default_factory=list)
@@ -178,19 +141,19 @@ class PageMultiSelectProperty(PageProperty):
178
141
  return [option.name for option in self.options]
179
142
 
180
143
 
181
- class PageSelectProperty(PageProperty):
182
- type: Literal[PropertyType.SELECT] = PropertyType.SELECT
183
- select: SelectOption | None = None
184
- options: list[SelectOption] = Field(default_factory=list)
144
+ class PageStatusProperty(PageProperty):
145
+ type: Literal[PropertyType.STATUS] = PropertyType.STATUS
146
+ status: StatusOption | None = None
147
+ options: list[StatusOption] = Field(default_factory=list)
185
148
 
186
149
  @property
187
150
  def option_names(self) -> list[str]:
188
151
  return [option.name for option in self.options]
189
152
 
190
153
 
191
- class PagePeopleProperty(PageProperty):
192
- type: Literal[PropertyType.PEOPLE] = PropertyType.PEOPLE
193
- people: list[PersonUserResponseDto] = Field(default_factory=list)
154
+ class PageNumberProperty(PageProperty):
155
+ type: Literal[PropertyType.NUMBER] = PropertyType.NUMBER
156
+ number: float | None = None
194
157
 
195
158
 
196
159
  class PageDateProperty(PageProperty):
@@ -198,21 +161,16 @@ class PageDateProperty(PageProperty):
198
161
  date: DateValue | None = None
199
162
 
200
163
 
201
- class PageTitleProperty(PageProperty):
202
- type: Literal[PropertyType.TITLE] = PropertyType.TITLE
203
- title: list[RichText] = Field(default_factory=list)
204
-
205
-
206
- class PageNumberProperty(PageProperty):
207
- type: Literal[PropertyType.NUMBER] = PropertyType.NUMBER
208
- number: float | None = None
209
-
210
-
211
164
  class PageCheckboxProperty(PageProperty):
212
165
  type: Literal[PropertyType.CHECKBOX] = PropertyType.CHECKBOX
213
166
  checkbox: bool = False
214
167
 
215
168
 
169
+ class PageURLProperty(PageProperty):
170
+ type: Literal[PropertyType.URL] = PropertyType.URL
171
+ url: str | None = None
172
+
173
+
216
174
  class PageEmailProperty(PageProperty):
217
175
  type: Literal[PropertyType.EMAIL] = PropertyType.EMAIL
218
176
  email: str | None = None
@@ -223,29 +181,34 @@ class PagePhoneNumberProperty(PageProperty):
223
181
  phone_number: str | None = None
224
182
 
225
183
 
226
- class PageCreatedTimeProperty(PageProperty):
227
- type: Literal[PropertyType.CREATED_TIME] = PropertyType.CREATED_TIME
228
- created_time: str # ISO 8601 datetime - read-only
229
-
230
-
231
- class PageLastEditedTimeProperty(PageProperty):
232
- type: Literal[PropertyType.LAST_EDITED_TIME] = PropertyType.LAST_EDITED_TIME
233
- last_edited_time: str # ISO 8601 datetime - read-only
184
+ class PagePeopleProperty(PageProperty):
185
+ type: Literal[PropertyType.PEOPLE] = PropertyType.PEOPLE
186
+ people: list[PersonUserResponseDto] = Field(default_factory=list)
234
187
 
235
188
 
236
189
  class PageCreatedByProperty(PageProperty):
237
190
  type: Literal[PropertyType.CREATED_BY] = PropertyType.CREATED_BY
238
- created_by: UserResponseDto # User object - read-only
191
+ created_by: UserResponseDto
239
192
 
240
193
 
241
194
  class PageLastEditedByProperty(PageProperty):
242
195
  type: Literal[PropertyType.LAST_EDITED_BY] = PropertyType.LAST_EDITED_BY
243
- last_edited_by: UserResponseDto # User object - read-only
196
+ last_edited_by: UserResponseDto
244
197
 
245
198
 
246
- class PageFilesProperty(PageProperty):
247
- type: Literal[PropertyType.FILES] = PropertyType.FILES
248
- files: list[FileObject] = Field(default_factory=list)
199
+ class PageCreatedTimeProperty(PageProperty):
200
+ type: Literal[PropertyType.CREATED_TIME] = PropertyType.CREATED_TIME
201
+ created_time: str
202
+
203
+
204
+ class PageLastEditedTimeProperty(PageProperty):
205
+ type: Literal[PropertyType.LAST_EDITED_TIME] = PropertyType.LAST_EDITED_TIME
206
+ last_edited_time: str
207
+
208
+
209
+ class PageLastVisitedTimeProperty(PageProperty):
210
+ type: Literal[PropertyType.LAST_VISITED_TIME] = PropertyType.LAST_VISITED_TIME
211
+ last_visited_time: str | None = None
249
212
 
250
213
 
251
214
  class PageFormulaProperty(PageProperty):
@@ -258,14 +221,15 @@ class PageRollupProperty(PageProperty):
258
221
  rollup: RollupValue
259
222
 
260
223
 
261
- class PageUniqueIdProperty(PageProperty):
262
- type: Literal[PropertyType.UNIQUE_ID] = PropertyType.UNIQUE_ID
263
- unique_id: UniqueIdValue
224
+ class PageFilesProperty(PageProperty):
225
+ type: Literal[PropertyType.FILES] = PropertyType.FILES
226
+ files: list[File] = Field(default_factory=list)
264
227
 
265
228
 
266
- class PageVerificationProperty(PageProperty):
267
- type: Literal[PropertyType.VERIFICATION] = PropertyType.VERIFICATION
268
- verification: VerificationValue
229
+ class PageRelationProperty(PageProperty):
230
+ type: Literal[PropertyType.RELATION] = PropertyType.RELATION
231
+ relation: list[RelationItem] = Field(default_factory=list)
232
+ has_more: bool = False
269
233
 
270
234
 
271
235
  class PageButtonProperty(PageProperty):
@@ -273,36 +237,58 @@ class PageButtonProperty(PageProperty):
273
237
  button: JsonDict = Field(default_factory=dict)
274
238
 
275
239
 
276
- # ============================================================================
277
- # Discriminated Union
278
- # ============================================================================
240
+ class PageLocationProperty(PageProperty):
241
+ type: Literal[PropertyType.LOCATION] = PropertyType.LOCATION
242
+ location: JsonDict | None = None
279
243
 
280
244
 
281
- DiscriminatedPageProperty = Annotated[
282
- PageStatusProperty
283
- | PageRelationProperty
284
- | PageURLProperty
245
+ class PagePlaceProperty(PageProperty):
246
+ type: Literal[PropertyType.PLACE] = PropertyType.PLACE
247
+ place: JsonDict | None = None
248
+
249
+
250
+ class PageVerificationProperty(PageProperty):
251
+ type: Literal[PropertyType.VERIFICATION] = PropertyType.VERIFICATION
252
+ verification: VerificationValue
253
+
254
+
255
+ class PageUniqueIdProperty(PageProperty):
256
+ type: Literal[PropertyType.UNIQUE_ID] = PropertyType.UNIQUE_ID
257
+ unique_id: UniqueIdValue
258
+
259
+
260
+ class PageUnknownProperty(PageProperty):
261
+ model_config = ConfigDict(extra="allow")
262
+
263
+
264
+ type AnyPageProperty = (
265
+ PageTitleProperty
285
266
  | PageRichTextProperty
286
- | PageMultiSelectProperty
287
267
  | PageSelectProperty
288
- | PagePeopleProperty
289
- | PageDateProperty
290
- | PageTitleProperty
268
+ | PageMultiSelectProperty
269
+ | PageStatusProperty
291
270
  | PageNumberProperty
271
+ | PageDateProperty
292
272
  | PageCheckboxProperty
273
+ | PageURLProperty
293
274
  | PageEmailProperty
294
275
  | PagePhoneNumberProperty
295
- | PageCreatedTimeProperty
296
- | PageLastEditedTimeProperty
276
+ | PagePeopleProperty
297
277
  | PageCreatedByProperty
298
278
  | PageLastEditedByProperty
299
- | PageFilesProperty
279
+ | PageCreatedTimeProperty
280
+ | PageLastEditedTimeProperty
281
+ | PageLastVisitedTimeProperty
300
282
  | PageFormulaProperty
301
283
  | PageRollupProperty
302
- | PageUniqueIdProperty
284
+ | PageFilesProperty
285
+ | PageRelationProperty
286
+ | PageButtonProperty
287
+ | PageLocationProperty
288
+ | PagePlaceProperty
303
289
  | PageVerificationProperty
304
- | PageButtonProperty,
305
- Field(discriminator="type"),
306
- ]
290
+ | PageUniqueIdProperty
291
+ | PageUnknownProperty
292
+ )
307
293
 
308
294
  PagePropertyT = TypeVar("PagePropertyT", bound=PageProperty)
@@ -10,7 +10,7 @@ from notionary.exceptions.properties import (
10
10
  PagePropertyTypeError,
11
11
  )
12
12
  from notionary.page.properties.client import PagePropertyHttpClient
13
- from notionary.page.properties.models import (
13
+ from notionary.page.properties.schemas import (
14
14
  PageCheckboxProperty,
15
15
  PageCreatedTimeProperty,
16
16
  PageDateProperty,
@@ -147,11 +147,22 @@ class PagePropertyHandler:
147
147
  # Writer Methods
148
148
  # =========================================================================
149
149
 
150
- async def set_title_property(self, property_name: str, title: str) -> None:
151
- self._get_typed_property_or_raise(property_name, PageTitleProperty)
152
- updated_page = await self._property_http_client.patch_title(property_name, title)
150
+ async def set_title_property(self, title: str) -> None:
151
+ title_property_name = self._extract_title_property_name()
152
+
153
+ self._get_typed_property_or_raise(title_property_name, PageTitleProperty)
154
+ updated_page = await self._property_http_client.patch_title(title_property_name, title)
153
155
  self._properties = updated_page.properties
154
156
 
157
+ def _extract_title_property_name(self) -> str | None:
158
+ if not self._properties:
159
+ return None
160
+
161
+ return next(
162
+ (key for key, prop in self._properties.items() if isinstance(prop, PageTitleProperty)),
163
+ None,
164
+ )
165
+
155
166
  async def set_rich_text_property(self, property_name: str, text: str) -> None:
156
167
  self._get_typed_property_or_raise(property_name, PageRichTextProperty)
157
168
  updated_page = await self._property_http_client.patch_rich_text_property(property_name, text)
notionary/page/schemas.py CHANGED
@@ -1,13 +1,13 @@
1
1
  from pydantic import BaseModel
2
2
 
3
- from notionary.page.properties.models import DiscriminatedPageProperty
3
+ from notionary.page.properties.schemas import AnyPageProperty
4
4
  from notionary.shared.entity.schemas import EntityResponseDto
5
5
 
6
6
 
7
7
  class NotionPageDto(EntityResponseDto):
8
8
  archived: bool
9
- properties: dict[str, DiscriminatedPageProperty]
9
+ properties: dict[str, AnyPageProperty]
10
10
 
11
11
 
12
12
  class PgePropertiesUpdateDto(BaseModel):
13
- properties: dict[str, DiscriminatedPageProperty]
13
+ properties: dict[str, AnyPageProperty]