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
@@ -16,27 +16,20 @@ from notionary.data_source.properties.schemas import (
16
16
  DataSourceSelectProperty,
17
17
  DataSourceStatusProperty,
18
18
  )
19
- from notionary.data_source.query.builder import DataSourceQueryBuilder
20
- from notionary.data_source.query.resolver import QueryResolver
21
- from notionary.data_source.query.schema import (
22
- DataSourceQueryParams,
23
- )
19
+ from notionary.data_source.query import DataSourceQueryBuilder, DataSourceQueryParams, QueryResolver
24
20
  from notionary.data_source.schema.service import DataSourcePropertySchemaFormatter
25
21
  from notionary.data_source.schemas import DataSourceDto
26
22
  from notionary.exceptions.data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
27
- from notionary.page.properties.models import PageTitleProperty
23
+ from notionary.file_upload.service import NotionFileUpload
24
+ from notionary.page.properties.schemas import PageTitleProperty
28
25
  from notionary.page.schemas import NotionPageDto
29
26
  from notionary.shared.entity.dto_parsers import (
30
- extract_cover_image_url_from_dto,
31
- extract_database_id,
32
27
  extract_description,
33
- extract_emoji_icon_from_dto,
34
- extract_external_icon_url_from_dto,
35
28
  extract_title,
36
29
  )
37
30
  from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
38
31
  from notionary.shared.entity.service import Entity
39
- from notionary.user.schemas import PartialUserDto
32
+ from notionary.user.service import UserService
40
33
  from notionary.workspace.query.service import WorkspaceQueryService
41
34
 
42
35
  if TYPE_CHECKING:
@@ -46,45 +39,23 @@ if TYPE_CHECKING:
46
39
  class NotionDataSource(Entity):
47
40
  def __init__(
48
41
  self,
49
- id: str,
42
+ dto: DataSourceDto,
50
43
  title: str,
51
- created_time: str,
52
- created_by: PartialUserDto,
53
- last_edited_time: str,
54
- last_edited_by: PartialUserDto,
55
- archived: bool,
56
- in_trash: bool,
57
- url: str,
44
+ description: str | None,
58
45
  properties: dict[str, DataSourceProperty],
59
- parent_database_id: str | None,
60
- emoji_icon: str | None = None,
61
- external_icon_url: str | None = None,
62
- cover_image_url: str | None = None,
63
- description: str | None = None,
64
- public_url: str | None = None,
65
- data_source_instance_client: DataSourceInstanceClient | None = None,
46
+ data_source_instance_client: DataSourceInstanceClient,
66
47
  query_resolver: QueryResolver | None = None,
48
+ user_service: UserService | None = None,
49
+ file_upload_service: NotionFileUpload | None = None,
67
50
  ) -> None:
68
- super().__init__(
69
- id=id,
70
- created_time=created_time,
71
- created_by=created_by,
72
- last_edited_time=last_edited_time,
73
- last_edited_by=last_edited_by,
74
- in_trash=in_trash,
75
- emoji_icon=emoji_icon,
76
- external_icon_url=external_icon_url,
77
- cover_image_url=cover_image_url,
78
- )
79
- self._parent_database_id = parent_database_id
51
+ super().__init__(dto=dto, user_service=user_service, file_upload_service=file_upload_service)
52
+
80
53
  self._parent_database: NotionDatabase | None = None
81
54
  self._title = title
82
- self._archived = archived
83
- self._url = url
84
- self._public_url = public_url
55
+ self._archived = dto.archived
85
56
  self._description = description
86
57
  self._properties = properties or {}
87
- self._data_source_client = data_source_instance_client or DataSourceInstanceClient(data_source_id=id)
58
+ self._data_source_client = data_source_instance_client
88
59
  self.query_resolver = query_resolver or QueryResolver()
89
60
 
90
61
  @classmethod
@@ -111,73 +82,21 @@ class NotionDataSource(Entity):
111
82
  @classmethod
112
83
  async def _create_from_dto(
113
84
  cls,
114
- response: DataSourceDto,
85
+ dto: DataSourceDto,
115
86
  rich_text_converter: RichTextToMarkdownConverter,
116
87
  ) -> Self:
117
88
  title, description = await asyncio.gather(
118
- extract_title(response, rich_text_converter),
119
- extract_description(response, rich_text_converter),
89
+ extract_title(dto, rich_text_converter),
90
+ extract_description(dto, rich_text_converter),
120
91
  )
121
92
 
122
- parent_database_id = extract_database_id(response)
123
-
124
- return cls._create(
125
- id=response.id,
126
- title=title,
127
- description=description,
128
- created_time=response.created_time,
129
- created_by=response.created_by,
130
- last_edited_time=response.last_edited_time,
131
- last_edited_by=response.last_edited_by,
132
- archived=response.archived,
133
- in_trash=response.in_trash,
134
- url=response.url,
135
- properties=response.properties,
136
- parent_database_id=parent_database_id,
137
- emoji_icon=extract_emoji_icon_from_dto(response),
138
- external_icon_url=extract_external_icon_url_from_dto(response),
139
- cover_image_url=extract_cover_image_url_from_dto(response),
140
- public_url=response.url,
141
- )
93
+ data_source_instance_client = DataSourceInstanceClient(data_source_id=dto.id)
142
94
 
143
- @classmethod
144
- def _create(
145
- cls,
146
- id: str,
147
- title: str,
148
- created_time: str,
149
- created_by: PartialUserDto,
150
- last_edited_time: str,
151
- last_edited_by: PartialUserDto,
152
- archived: bool,
153
- in_trash: bool,
154
- url: str,
155
- properties: dict[str, DataSourceProperty],
156
- parent_database_id: str | None,
157
- emoji_icon: str | None = None,
158
- external_icon_url: str | None = None,
159
- cover_image_url: str | None = None,
160
- description: str | None = None,
161
- public_url: str | None = None,
162
- ) -> Self:
163
- data_source_instance_client = DataSourceInstanceClient(data_source_id=id)
164
95
  return cls(
165
- id=id,
96
+ dto=dto,
166
97
  title=title,
167
- created_time=created_time,
168
- created_by=created_by,
169
- last_edited_time=last_edited_time,
170
- last_edited_by=last_edited_by,
171
- archived=archived,
172
- in_trash=in_trash,
173
- url=url,
174
- parent_database_id=parent_database_id,
175
- emoji_icon=emoji_icon,
176
- external_icon_url=external_icon_url,
177
- cover_image_url=cover_image_url,
178
98
  description=description,
179
- public_url=public_url,
180
- properties=properties,
99
+ properties=dto.properties,
181
100
  data_source_instance_client=data_source_instance_client,
182
101
  )
183
102
 
@@ -202,17 +121,8 @@ class NotionDataSource(Entity):
202
121
  return self._properties
203
122
 
204
123
  @property
205
- def url(self) -> str:
206
- return self._url
207
-
208
- @property
209
- def public_url(self) -> str | None:
210
- return self._public_url
211
-
212
- async def get_parent_database(self) -> NotionDatabase | None:
213
- if self._parent_database is None and self._parent_database_id:
214
- self._parent_database = await NotionDatabase.from_id(self._parent_database_id)
215
- return self._parent_database
124
+ def data_source_query_builder(self) -> DataSourceQueryBuilder:
125
+ return DataSourceQueryBuilder(properties=self._properties)
216
126
 
217
127
  async def create_blank_page(self, title: str | None = None) -> NotionPage:
218
128
  return await self._data_source_client.create_blank_page(title=title)
@@ -320,44 +230,45 @@ class NotionDataSource(Entity):
320
230
 
321
231
  return prop
322
232
 
323
- def filter(self) -> DataSourceQueryBuilder:
233
+ def get_query_builder(self) -> DataSourceQueryBuilder:
324
234
  return DataSourceQueryBuilder(properties=self._properties)
325
235
 
326
- async def query_pages(
327
- self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
328
- ) -> list[NotionPage]:
329
- builder = DataSourceQueryBuilder(properties=self._properties)
330
- configured_builder = filter_fn(builder)
331
- query_params = configured_builder.build()
332
-
333
- return await self.get_pages(query_params)
334
-
335
- async def query_pages_stream(
336
- self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
337
- ) -> AsyncIterator[NotionPage]:
338
- builder = DataSourceQueryBuilder(properties=self._properties)
339
- configured_builder = filter_fn(builder)
340
- query_params = configured_builder.build()
341
-
342
- async for page in self.get_pages_stream(query_params):
343
- yield page
344
-
345
236
  async def get_pages(
346
237
  self,
238
+ *,
239
+ filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder] | None = None,
347
240
  query_params: DataSourceQueryParams | None = None,
348
241
  ) -> list[NotionPage]:
349
242
  from notionary import NotionPage
350
243
 
244
+ if filter_fn is not None and query_params is not None:
245
+ raise ValueError("Use either filter_fn OR query_params, not both")
246
+
247
+ if filter_fn is not None:
248
+ builder = DataSourceQueryBuilder(properties=self._properties)
249
+ configured_builder = filter_fn(builder)
250
+ query_params = configured_builder.build()
251
+
351
252
  resolved_params = await self._resolve_query_params_if_needed(query_params)
352
253
  query_response = await self._data_source_client.query(query_params=resolved_params)
353
254
  return [await NotionPage.from_id(page.id) for page in query_response.results]
354
255
 
355
- async def get_pages_stream(
256
+ async def iter_pages(
356
257
  self,
258
+ *,
259
+ filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder] | None = None,
357
260
  query_params: DataSourceQueryParams | None = None,
358
261
  ) -> AsyncIterator[NotionPage]:
359
262
  from notionary import NotionPage
360
263
 
264
+ if filter_fn is not None and query_params is not None:
265
+ raise ValueError("Use either filter_fn OR query_params, not both")
266
+
267
+ if filter_fn is not None:
268
+ builder = DataSourceQueryBuilder(properties=self._properties)
269
+ configured_builder = filter_fn(builder)
270
+ query_params = configured_builder.build()
271
+
361
272
  resolved_params = await self._resolve_query_params_if_needed(query_params)
362
273
 
363
274
  async for page in self._data_source_client.query_stream(query_params=resolved_params):
@@ -2,7 +2,7 @@ from pydantic import BaseModel, Field
2
2
 
3
3
  from notionary.blocks.rich_text.models import RichText
4
4
  from notionary.shared.entity.schemas import EntityResponseDto
5
- from notionary.shared.models.cover import NotionCover
5
+ from notionary.shared.models.file import File
6
6
  from notionary.shared.models.icon import Icon
7
7
 
8
8
 
@@ -24,6 +24,6 @@ class NotionDatabaseDto(EntityResponseDto):
24
24
  class NotionDatabaseUpdateDto(BaseModel):
25
25
  title: list[RichText] | None = None
26
26
  icon: Icon | None = None
27
- cover: NotionCover | None = None
27
+ cover: File | None = None
28
28
  archived: bool | None = None
29
29
  description: list[RichText] | None = None
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  import asyncio
4
2
  from collections.abc import Awaitable, Callable
5
3
  from typing import Self
@@ -10,62 +8,36 @@ from notionary.database.client import NotionDatabaseHttpClient
10
8
  from notionary.database.database_metadata_update_client import DatabaseMetadataUpdateClient
11
9
  from notionary.database.schemas import NotionDatabaseDto
12
10
  from notionary.shared.entity.dto_parsers import (
13
- extract_cover_image_url_from_dto,
14
11
  extract_description,
15
- extract_emoji_icon_from_dto,
16
- extract_external_icon_url_from_dto,
17
12
  extract_title,
18
13
  )
19
14
  from notionary.shared.entity.service import Entity
20
- from notionary.user.schemas import PartialUserDto
21
15
  from notionary.workspace.query.service import WorkspaceQueryService
22
16
 
23
- type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
17
+ type _DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
24
18
 
25
19
 
26
20
  class NotionDatabase(Entity):
27
21
  def __init__(
28
22
  self,
29
- id: str,
23
+ dto: NotionDatabaseDto,
30
24
  title: str,
31
- created_time: str,
32
- created_by: PartialUserDto,
33
- last_edited_time: str,
34
- last_edited_by: PartialUserDto,
35
- url: str,
36
- in_trash: bool,
37
- is_inline: bool,
25
+ description: str | None,
38
26
  data_source_ids: list[str],
39
- public_url: str | None = None,
40
- emoji_icon: str | None = None,
41
- external_icon_url: str | None = None,
42
- cover_image_url: str | None = None,
43
- description: str | None = None,
44
- client: NotionDatabaseHttpClient | None = None,
45
- metadata_update_client: DatabaseMetadataUpdateClient | None = None,
27
+ client: NotionDatabaseHttpClient,
28
+ metadata_update_client: DatabaseMetadataUpdateClient,
46
29
  ) -> None:
47
- super().__init__(
48
- id=id,
49
- created_time=created_time,
50
- created_by=created_by,
51
- last_edited_time=last_edited_time,
52
- last_edited_by=last_edited_by,
53
- in_trash=in_trash,
54
- emoji_icon=emoji_icon,
55
- external_icon_url=external_icon_url,
56
- cover_image_url=cover_image_url,
57
- )
30
+ super().__init__(dto=dto)
31
+
58
32
  self._title = title
59
- self._url = url
60
- self._public_url = public_url
61
33
  self._description = description
62
- self._is_inline = is_inline
34
+ self._is_inline = dto.is_inline
63
35
 
64
36
  self._data_sources: list[NotionDataSource] | None = None
65
37
  self._data_source_ids = data_source_ids
66
38
 
67
- self.client = client or NotionDatabaseHttpClient(database_id=id)
68
- self._metadata_update_client = metadata_update_client or DatabaseMetadataUpdateClient(database_id=id)
39
+ self.client = client
40
+ self._metadata_update_client = metadata_update_client
69
41
 
70
42
  @classmethod
71
43
  async def from_id(
@@ -94,31 +66,23 @@ class NotionDatabase(Entity):
94
66
  @classmethod
95
67
  async def _create_from_dto(
96
68
  cls,
97
- response: NotionDatabaseDto,
69
+ dto: NotionDatabaseDto,
98
70
  rich_text_converter: RichTextToMarkdownConverter,
99
71
  client: NotionDatabaseHttpClient,
100
72
  ) -> Self:
101
73
  title, description = await asyncio.gather(
102
- extract_title(response, rich_text_converter), extract_description(response, rich_text_converter)
74
+ extract_title(dto, rich_text_converter), extract_description(dto, rich_text_converter)
103
75
  )
104
76
 
77
+ metadata_update_client = DatabaseMetadataUpdateClient(database_id=dto.id)
78
+
105
79
  return cls(
106
- id=response.id,
80
+ dto=dto,
107
81
  title=title,
108
82
  description=description,
109
- created_time=response.created_time,
110
- created_by=response.created_by,
111
- last_edited_time=response.last_edited_time,
112
- last_edited_by=response.last_edited_by,
113
- in_trash=response.in_trash,
114
- is_inline=response.is_inline,
115
- url=response.url,
116
- public_url=response.public_url,
117
- emoji_icon=extract_emoji_icon_from_dto(response),
118
- external_icon_url=extract_external_icon_url_from_dto(response),
119
- cover_image_url=extract_cover_image_url_from_dto(response),
120
- data_source_ids=[ds.id for ds in response.data_sources],
83
+ data_source_ids=[ds.id for ds in dto.data_sources],
121
84
  client=client,
85
+ metadata_update_client=metadata_update_client,
122
86
  )
123
87
 
124
88
  @property
@@ -129,14 +93,6 @@ class NotionDatabase(Entity):
129
93
  def title(self) -> str:
130
94
  return self._title
131
95
 
132
- @property
133
- def url(self) -> str:
134
- return self._url
135
-
136
- @property
137
- def public_url(self) -> str | None:
138
- return self._public_url
139
-
140
96
  @property
141
97
  def is_inline(self) -> bool:
142
98
  return self._is_inline
@@ -146,7 +102,7 @@ class NotionDatabase(Entity):
146
102
 
147
103
  async def get_data_sources(
148
104
  self,
149
- data_source_factory: DataSourceFactory = NotionDataSource.from_id,
105
+ data_source_factory: _DataSourceFactory = NotionDataSource.from_id,
150
106
  ) -> list[NotionDataSource]:
151
107
  if self._data_sources is None:
152
108
  self._data_sources = await self._load_data_sources(data_source_factory)
@@ -154,7 +110,7 @@ class NotionDatabase(Entity):
154
110
 
155
111
  async def _load_data_sources(
156
112
  self,
157
- data_source_factory: DataSourceFactory,
113
+ data_source_factory: _DataSourceFactory,
158
114
  ) -> list[NotionDataSource]:
159
115
  tasks = [data_source_factory(ds_id) for ds_id in self._data_source_ids]
160
116
  return list(await asyncio.gather(*tasks))
@@ -7,8 +7,10 @@ from .api import (
7
7
  NotionServerError,
8
8
  NotionValidationError,
9
9
  )
10
- from .base import NotionaryError
10
+ from .base import NotionaryException
11
+ from .block_parsing import InsufficientColumnsError, InvalidColumnRatioSumError, UnsupportedVideoFormatError
11
12
  from .data_source import DataSourcePropertyNotFound, DataSourcePropertyTypeError
13
+ from .file_upload import FileSizeException, NoFileExtensionException, UnsupportedFileTypeException
12
14
  from .properties import AccessPagePropertyWithoutDataSourceError, PagePropertyNotFoundError, PagePropertyTypeError
13
15
  from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
14
16
 
@@ -19,6 +21,10 @@ __all__ = [
19
21
  "DataSourcePropertyTypeError",
20
22
  "DatabaseNotFound",
21
23
  "EntityNotFound",
24
+ "FileSizeException",
25
+ "InsufficientColumnsError",
26
+ "InvalidColumnRatioSumError",
27
+ "NoFileExtensionException",
22
28
  "NotionApiError",
23
29
  "NotionAuthenticationError",
24
30
  "NotionConnectionError",
@@ -26,8 +32,10 @@ __all__ = [
26
32
  "NotionResourceNotFoundError",
27
33
  "NotionServerError",
28
34
  "NotionValidationError",
29
- "NotionaryError",
35
+ "NotionaryException",
30
36
  "PageNotFound",
31
37
  "PagePropertyNotFoundError",
32
38
  "PagePropertyTypeError",
39
+ "UnsupportedFileTypeException",
40
+ "UnsupportedVideoFormatError",
33
41
  ]
@@ -1,7 +1,7 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
- class NotionApiError(NotionaryError):
4
+ class NotionApiError(NotionaryException):
5
5
  def __init__(
6
6
  self,
7
7
  message: str,
@@ -1,2 +1,2 @@
1
- class NotionaryError(Exception):
1
+ class NotionaryException(Exception):
2
2
  pass
@@ -1,16 +1,37 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
  RATIO_TOLERANCE = 0.0001
4
4
 
5
5
 
6
- class InsufficientColumnsError(NotionaryError):
6
+ class InsufficientColumnsError(NotionaryException):
7
7
  def __init__(self, column_count: int) -> None:
8
8
  self.column_count = column_count
9
9
  super().__init__(f"Columns container must contain at least 2 column blocks, but only {column_count} found")
10
10
 
11
11
 
12
- class InvalidColumnRatioSumError(NotionaryError):
12
+ class InvalidColumnRatioSumError(NotionaryException):
13
13
  def __init__(self, total: float, tolerance: float = RATIO_TOLERANCE) -> None:
14
14
  self.total = total
15
15
  self.tolerance = tolerance
16
16
  super().__init__(f"Width ratios must sum to 1.0 (±{tolerance}), but sum is {total}")
17
+
18
+
19
+ class UnsupportedVideoFormatError(ValueError):
20
+ def __init__(self, url: str, supported_formats: list[str]) -> None:
21
+ self.url = url
22
+ self.supported_formats = supported_formats
23
+ super().__init__(self._create_user_friendly_message())
24
+
25
+ def _create_user_friendly_message(self) -> str:
26
+ formats = ", ".join(self.supported_formats[:5])
27
+ remaining = len(self.supported_formats) - 5
28
+
29
+ if remaining > 0:
30
+ formats += f" and {remaining} more"
31
+
32
+ return (
33
+ f"The video URL '{self.url}' uses an unsupported format.\n"
34
+ f"Supported formats include: {formats}.\n"
35
+ f"YouTube embed and watch URLs are also supported."
36
+ f"Also see https://developers.notion.com/reference/block#video for more information."
37
+ )
@@ -8,11 +8,11 @@ from notionary.data_source.query.schema import (
8
8
  Operator,
9
9
  StringOperator,
10
10
  )
11
- from notionary.exceptions.base import NotionaryError
11
+ from notionary.exceptions.base import NotionaryException
12
12
  from notionary.shared.properties.type import PropertyType
13
13
 
14
14
 
15
- class QueryBuilderError(NotionaryError):
15
+ class QueryBuilderError(NotionaryException):
16
16
  def __init__(self, message: str, property_name: str | None = None) -> None:
17
17
  self.property_name = property_name
18
18
  super().__init__(message)
@@ -1,9 +1,9 @@
1
1
  import difflib
2
2
 
3
- from notionary.exceptions.base import NotionaryError
3
+ from notionary.exceptions.base import NotionaryException
4
4
 
5
5
 
6
- class DataSourcePropertyNotFound(NotionaryError):
6
+ class DataSourcePropertyNotFound(NotionaryException):
7
7
  def __init__(
8
8
  self,
9
9
  property_name: str,
@@ -28,7 +28,7 @@ class DataSourcePropertyNotFound(NotionaryError):
28
28
  super().__init__(message)
29
29
 
30
30
 
31
- class DataSourcePropertyTypeError(NotionaryError):
31
+ class DataSourcePropertyTypeError(NotionaryException):
32
32
  def __init__(self, property_name: str, expected_type: str, actual_type: str) -> None:
33
33
  message = f"Property '{property_name}' has the wrong type. Expected: '{expected_type}', found: '{actual_type}'."
34
34
  super().__init__(message)
@@ -0,0 +1,67 @@
1
+ from notionary.exceptions.base import NotionaryException
2
+
3
+
4
+ class UnsupportedFileTypeException(NotionaryException):
5
+ def __init__(self, extension: str, filename: str, supported_extensions_by_category: dict[str, list[str]]):
6
+ supported_exts = []
7
+ for category, extensions in supported_extensions_by_category.items():
8
+ supported_exts.append(f"{category}: {', '.join(extensions[:5])}...")
9
+
10
+ supported_info = "\n ".join(supported_exts)
11
+ super().__init__(
12
+ f"File '{filename}' has unsupported extension '{extension}'.\n"
13
+ f"Supported file types by category:\n {supported_info}"
14
+ )
15
+ self.extension = extension
16
+ self.filename = filename
17
+
18
+
19
+ class NoFileExtensionException(NotionaryException):
20
+ def __init__(self, filename: str):
21
+ super().__init__(
22
+ f"File '{filename}' has no extension. Files must have a valid extension to determine their type."
23
+ )
24
+ self.filename = filename
25
+
26
+
27
+ class FileSizeException(NotionaryException):
28
+ def __init__(self, filename: str, file_size_bytes: int, max_size_bytes: int):
29
+ file_size_mb = file_size_bytes / (1024 * 1024)
30
+ max_size_mb = max_size_bytes / (1024 * 1024)
31
+ super().__init__(
32
+ f"File '{filename}' is too large ({file_size_mb:.2f} MB). Maximum allowed size is {max_size_mb:.2f} MB."
33
+ )
34
+ self.filename = filename
35
+ self.file_size_bytes = file_size_bytes
36
+ self.max_size_bytes = max_size_bytes
37
+
38
+
39
+ class FileNotFoundError(NotionaryException):
40
+ def __init__(self, file_path: str):
41
+ super().__init__(f"File does not exist: {file_path}")
42
+ self.file_path = file_path
43
+
44
+
45
+ class FilenameTooLongError(NotionaryException):
46
+ def __init__(self, filename: str, filename_bytes: int, max_filename_bytes: int):
47
+ super().__init__(f"Filename too long: {filename_bytes} bytes (max {max_filename_bytes}). Filename: {filename}")
48
+ self.filename = filename
49
+ self.filename_bytes = filename_bytes
50
+ self.max_filename_bytes = max_filename_bytes
51
+
52
+
53
+ class UploadFailedError(NotionaryException):
54
+ def __init__(self, file_upload_id: str, reason: str | None = None):
55
+ message = f"Upload failed for file_upload_id: {file_upload_id}"
56
+ if reason:
57
+ message += f". Reason: {reason}"
58
+ super().__init__(message)
59
+ self.file_upload_id = file_upload_id
60
+ self.reason = reason
61
+
62
+
63
+ class UploadTimeoutError(NotionaryException):
64
+ def __init__(self, file_upload_id: str, timeout_seconds: int):
65
+ super().__init__(f"Upload timeout after {timeout_seconds}s for file_upload_id: {file_upload_id}")
66
+ self.file_upload_id = file_upload_id
67
+ self.timeout_seconds = timeout_seconds
@@ -1,11 +1,11 @@
1
1
  import difflib
2
2
  from typing import ClassVar
3
3
 
4
- from notionary.exceptions.base import NotionaryError
4
+ from notionary.exceptions.base import NotionaryException
5
5
  from notionary.shared.models.parent import ParentType
6
6
 
7
7
 
8
- class PagePropertyNotFoundError(NotionaryError):
8
+ class PagePropertyNotFoundError(NotionaryException):
9
9
  def __init__(
10
10
  self,
11
11
  page_url: str,
@@ -31,7 +31,7 @@ class PagePropertyNotFoundError(NotionaryError):
31
31
  super().__init__(message)
32
32
 
33
33
 
34
- class PagePropertyTypeError(NotionaryError):
34
+ class PagePropertyTypeError(NotionaryException):
35
35
  def __init__(
36
36
  self,
37
37
  property_name: str,
@@ -41,7 +41,7 @@ class PagePropertyTypeError(NotionaryError):
41
41
  super().__init__(message)
42
42
 
43
43
 
44
- class AccessPagePropertyWithoutDataSourceError(NotionaryError):
44
+ class AccessPagePropertyWithoutDataSourceError(NotionaryException):
45
45
  _PARENT_DESCRIPTIONS: ClassVar[dict[ParentType, str]] = {
46
46
  ParentType.WORKSPACE: "the workspace itself",
47
47
  ParentType.PAGE_ID: "another page",
@@ -1,7 +1,7 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
- class EntityNotFound(NotionaryError):
4
+ class EntityNotFound(NotionaryException):
5
5
  def __init__(self, entity_type: str, query: str, available_titles: list[str] | None = None) -> None:
6
6
  self.entity_type = entity_type
7
7
  self.query = query
@@ -33,14 +33,14 @@ class DatabaseNotFound(EntityNotFound):
33
33
  super().__init__("database", query, available_titles)
34
34
 
35
35
 
36
- class NoUsersInWorkspace(NotionaryError):
36
+ class NoUsersInWorkspace(NotionaryException):
37
37
  def __init__(self, user_type: str) -> None:
38
38
  self.user_type = user_type
39
39
  message = f"No '{user_type}' users found in the workspace."
40
40
  super().__init__(message)
41
41
 
42
42
 
43
- class UserNotFound(NotionaryError):
43
+ class UserNotFound(NotionaryException):
44
44
  def __init__(self, user_type: str, query: str, available_names: list[str] | None = None) -> None:
45
45
  self.user_type = user_type
46
46
  self.query = query