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.
- notionary/__init__.py +14 -2
- notionary/blocks/enums.py +27 -6
- notionary/blocks/schemas.py +32 -78
- notionary/comments/client.py +6 -9
- notionary/comments/schemas.py +2 -29
- notionary/data_source/http/data_source_instance_client.py +4 -4
- notionary/data_source/properties/schemas.py +128 -107
- notionary/data_source/query/__init__.py +9 -0
- notionary/data_source/query/builder.py +12 -3
- notionary/data_source/query/schema.py +5 -0
- notionary/data_source/schemas.py +2 -2
- notionary/data_source/service.py +43 -132
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +19 -63
- notionary/exceptions/__init__.py +10 -2
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +24 -3
- notionary/exceptions/data_source/builder.py +2 -2
- notionary/exceptions/data_source/properties.py +3 -3
- notionary/exceptions/file_upload.py +67 -0
- notionary/exceptions/properties.py +4 -4
- notionary/exceptions/search.py +4 -4
- notionary/file_upload/__init__.py +4 -0
- notionary/file_upload/client.py +124 -210
- notionary/file_upload/config/__init__.py +17 -0
- notionary/file_upload/config/config.py +32 -0
- notionary/file_upload/config/constants.py +16 -0
- notionary/file_upload/file/reader.py +28 -0
- notionary/file_upload/query/__init__.py +7 -0
- notionary/file_upload/query/builder.py +54 -0
- notionary/file_upload/query/models.py +37 -0
- notionary/file_upload/schemas.py +78 -0
- notionary/file_upload/service.py +152 -289
- notionary/file_upload/validation/factory.py +64 -0
- notionary/file_upload/validation/impl/file_name_length.py +23 -0
- notionary/file_upload/validation/models.py +124 -0
- notionary/file_upload/validation/port.py +7 -0
- notionary/file_upload/validation/service.py +17 -0
- notionary/file_upload/validation/validators/__init__.py +11 -0
- notionary/file_upload/validation/validators/file_exists.py +15 -0
- notionary/file_upload/validation/validators/file_extension.py +122 -0
- notionary/file_upload/validation/validators/file_name_length.py +21 -0
- notionary/file_upload/validation/validators/upload_limit.py +31 -0
- notionary/http/client.py +7 -23
- notionary/page/content/factory.py +2 -0
- notionary/page/content/parser/factory.py +8 -5
- notionary/page/content/parser/parsers/audio.py +8 -33
- notionary/page/content/parser/parsers/embed.py +0 -2
- notionary/page/content/parser/parsers/file.py +8 -35
- notionary/page/content/parser/parsers/file_like_block.py +89 -0
- notionary/page/content/parser/parsers/image.py +8 -35
- notionary/page/content/parser/parsers/pdf.py +8 -35
- notionary/page/content/parser/parsers/video.py +8 -35
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +2 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +12 -8
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +2 -0
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +2 -0
- notionary/page/content/renderer/renderers/audio.py +9 -21
- notionary/page/content/renderer/renderers/file.py +9 -21
- notionary/page/content/renderer/renderers/file_like_block.py +43 -0
- notionary/page/content/renderer/renderers/image.py +9 -21
- notionary/page/content/renderer/renderers/pdf.py +9 -21
- notionary/page/content/renderer/renderers/video.py +9 -21
- notionary/page/content/syntax/__init__.py +2 -1
- notionary/page/content/syntax/registry.py +38 -60
- notionary/page/properties/client.py +3 -3
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +15 -4
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +18 -79
- notionary/shared/entity/dto_parsers.py +1 -36
- notionary/shared/entity/entity_metadata_update_client.py +18 -4
- notionary/shared/entity/schemas.py +6 -6
- notionary/shared/entity/service.py +121 -40
- notionary/shared/models/file.py +34 -6
- notionary/shared/models/icon.py +5 -12
- notionary/user/bot.py +12 -12
- notionary/utils/decorators.py +8 -8
- notionary/utils/pagination.py +36 -32
- notionary/workspace/__init__.py +2 -2
- notionary/workspace/client.py +2 -0
- notionary/workspace/query/__init__.py +3 -2
- notionary/workspace/query/builder.py +25 -1
- notionary/workspace/query/models.py +9 -1
- notionary/workspace/query/service.py +15 -11
- notionary/workspace/service.py +46 -36
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/METADATA +9 -5
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/RECORD +92 -71
- notionary/file_upload/models.py +0 -69
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
notionary/data_source/service.py
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
42
|
+
dto: DataSourceDto,
|
|
50
43
|
title: str,
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
85
|
+
dto: DataSourceDto,
|
|
115
86
|
rich_text_converter: RichTextToMarkdownConverter,
|
|
116
87
|
) -> Self:
|
|
117
88
|
title, description = await asyncio.gather(
|
|
118
|
-
extract_title(
|
|
119
|
-
extract_description(
|
|
89
|
+
extract_title(dto, rich_text_converter),
|
|
90
|
+
extract_description(dto, rich_text_converter),
|
|
120
91
|
)
|
|
121
92
|
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
206
|
-
return self.
|
|
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
|
|
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
|
|
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):
|
notionary/database/schemas.py
CHANGED
|
@@ -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.
|
|
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:
|
|
27
|
+
cover: File | None = None
|
|
28
28
|
archived: bool | None = None
|
|
29
29
|
description: list[RichText] | None = None
|
notionary/database/service.py
CHANGED
|
@@ -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
|
|
17
|
+
type _DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
|
|
24
18
|
|
|
25
19
|
|
|
26
20
|
class NotionDatabase(Entity):
|
|
27
21
|
def __init__(
|
|
28
22
|
self,
|
|
29
|
-
|
|
23
|
+
dto: NotionDatabaseDto,
|
|
30
24
|
title: str,
|
|
31
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
68
|
-
self._metadata_update_client = metadata_update_client
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
80
|
+
dto=dto,
|
|
107
81
|
title=title,
|
|
108
82
|
description=description,
|
|
109
|
-
|
|
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:
|
|
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:
|
|
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))
|
notionary/exceptions/__init__.py
CHANGED
|
@@ -7,8 +7,10 @@ from .api import (
|
|
|
7
7
|
NotionServerError,
|
|
8
8
|
NotionValidationError,
|
|
9
9
|
)
|
|
10
|
-
from .base import
|
|
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
|
-
"
|
|
35
|
+
"NotionaryException",
|
|
30
36
|
"PageNotFound",
|
|
31
37
|
"PagePropertyNotFoundError",
|
|
32
38
|
"PagePropertyTypeError",
|
|
39
|
+
"UnsupportedFileTypeException",
|
|
40
|
+
"UnsupportedVideoFormatError",
|
|
33
41
|
]
|
notionary/exceptions/api.py
CHANGED
notionary/exceptions/base.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
class
|
|
1
|
+
class NotionaryException(Exception):
|
|
2
2
|
pass
|
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
from notionary.exceptions.base import
|
|
1
|
+
from notionary.exceptions.base import NotionaryException
|
|
2
2
|
|
|
3
3
|
RATIO_TOLERANCE = 0.0001
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class InsufficientColumnsError(
|
|
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(
|
|
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
|
|
11
|
+
from notionary.exceptions.base import NotionaryException
|
|
12
12
|
from notionary.shared.properties.type import PropertyType
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class QueryBuilderError(
|
|
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
|
|
3
|
+
from notionary.exceptions.base import NotionaryException
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class DataSourcePropertyNotFound(
|
|
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(
|
|
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
|
|
4
|
+
from notionary.exceptions.base import NotionaryException
|
|
5
5
|
from notionary.shared.models.parent import ParentType
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class PagePropertyNotFoundError(
|
|
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(
|
|
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(
|
|
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",
|
notionary/exceptions/search.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from notionary.exceptions.base import
|
|
1
|
+
from notionary.exceptions.base import NotionaryException
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class EntityNotFound(
|
|
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(
|
|
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(
|
|
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
|