notionary 0.2.28__py3-none-any.whl → 0.3.1__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 +9 -2
- notionary/blocks/__init__.py +5 -0
- notionary/blocks/client.py +6 -4
- notionary/blocks/enums.py +28 -1
- notionary/blocks/rich_text/markdown_rich_text_converter.py +14 -0
- notionary/blocks/rich_text/models.py +14 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +2 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +12 -0
- notionary/blocks/rich_text/rich_text_patterns.py +3 -0
- notionary/blocks/schemas.py +42 -10
- notionary/comments/__init__.py +5 -0
- notionary/comments/client.py +7 -10
- notionary/comments/factory.py +4 -6
- notionary/data_source/http/data_source_instance_client.py +14 -4
- notionary/data_source/properties/{models.py → schemas.py} +4 -8
- notionary/data_source/query/__init__.py +9 -0
- notionary/data_source/query/builder.py +38 -10
- notionary/data_source/query/schema.py +13 -10
- notionary/data_source/query/validator.py +11 -11
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +1 -1
- notionary/data_source/service.py +29 -103
- notionary/database/service.py +17 -60
- notionary/exceptions/__init__.py +5 -1
- notionary/exceptions/block_parsing.py +21 -0
- notionary/exceptions/search.py +24 -0
- notionary/http/client.py +9 -10
- notionary/http/models.py +5 -4
- notionary/page/content/factory.py +10 -3
- notionary/page/content/markdown/builder.py +76 -154
- notionary/page/content/markdown/nodes/__init__.py +0 -2
- notionary/page/content/markdown/nodes/audio.py +1 -1
- notionary/page/content/markdown/nodes/base.py +1 -1
- notionary/page/content/markdown/nodes/bookmark.py +1 -1
- notionary/page/content/markdown/nodes/breadcrumb.py +1 -1
- notionary/page/content/markdown/nodes/bulleted_list.py +31 -8
- notionary/page/content/markdown/nodes/callout.py +12 -10
- notionary/page/content/markdown/nodes/code.py +3 -5
- notionary/page/content/markdown/nodes/columns.py +39 -21
- notionary/page/content/markdown/nodes/container.py +64 -0
- notionary/page/content/markdown/nodes/divider.py +1 -1
- notionary/page/content/markdown/nodes/embed.py +1 -1
- notionary/page/content/markdown/nodes/equation.py +1 -1
- notionary/page/content/markdown/nodes/file.py +1 -1
- notionary/page/content/markdown/nodes/heading.py +26 -6
- notionary/page/content/markdown/nodes/image.py +1 -1
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +1 -1
- notionary/page/content/markdown/nodes/numbered_list.py +28 -5
- notionary/page/content/markdown/nodes/paragraph.py +1 -1
- notionary/page/content/markdown/nodes/pdf.py +1 -1
- notionary/page/content/markdown/nodes/quote.py +17 -5
- notionary/page/content/markdown/nodes/space.py +1 -1
- notionary/page/content/markdown/nodes/table.py +1 -1
- notionary/page/content/markdown/nodes/table_of_contents.py +1 -1
- notionary/page/content/markdown/nodes/todo.py +23 -7
- notionary/page/content/markdown/nodes/toggle.py +13 -14
- notionary/page/content/markdown/nodes/video.py +1 -1
- notionary/page/content/parser/context.py +98 -21
- notionary/page/content/parser/factory.py +1 -10
- notionary/page/content/parser/parsers/__init__.py +0 -2
- notionary/page/content/parser/parsers/audio.py +1 -1
- notionary/page/content/parser/parsers/base.py +1 -1
- notionary/page/content/parser/parsers/bookmark.py +1 -1
- notionary/page/content/parser/parsers/breadcrumb.py +1 -1
- notionary/page/content/parser/parsers/bulleted_list.py +52 -8
- notionary/page/content/parser/parsers/callout.py +55 -84
- notionary/page/content/parser/parsers/caption.py +1 -1
- notionary/page/content/parser/parsers/code.py +5 -5
- notionary/page/content/parser/parsers/column.py +23 -64
- notionary/page/content/parser/parsers/column_list.py +45 -45
- notionary/page/content/parser/parsers/divider.py +1 -1
- notionary/page/content/parser/parsers/embed.py +1 -1
- notionary/page/content/parser/parsers/equation.py +1 -1
- notionary/page/content/parser/parsers/file.py +1 -1
- notionary/page/content/parser/parsers/heading.py +65 -8
- notionary/page/content/parser/parsers/image.py +1 -1
- notionary/page/content/parser/parsers/numbered_list.py +52 -8
- notionary/page/content/parser/parsers/paragraph.py +3 -2
- notionary/page/content/parser/parsers/pdf.py +1 -1
- notionary/page/content/parser/parsers/quote.py +75 -15
- notionary/page/content/parser/parsers/space.py +14 -8
- notionary/page/content/parser/parsers/table.py +1 -1
- notionary/page/content/parser/parsers/table_of_contents.py +1 -1
- notionary/page/content/parser/parsers/todo.py +57 -19
- notionary/page/content/parser/parsers/toggle.py +17 -74
- notionary/page/content/parser/parsers/video.py +1 -1
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +6 -4
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +43 -22
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +4 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +108 -54
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +86 -0
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +14 -7
- notionary/page/content/parser/service.py +9 -0
- notionary/page/content/renderer/context.py +5 -2
- notionary/page/content/renderer/factory.py +2 -11
- notionary/page/content/renderer/post_processing/handlers/__init__.py +2 -2
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
- notionary/page/content/renderer/renderers/__init__.py +0 -2
- notionary/page/content/renderer/renderers/base.py +1 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +1 -1
- notionary/page/content/renderer/renderers/callout.py +6 -21
- notionary/page/content/renderer/renderers/captioned_block.py +1 -1
- notionary/page/content/renderer/renderers/column.py +28 -19
- notionary/page/content/renderer/renderers/column_list.py +24 -11
- notionary/page/content/renderer/renderers/heading.py +53 -27
- notionary/page/content/renderer/renderers/numbered_list.py +6 -5
- notionary/page/content/renderer/renderers/quote.py +1 -1
- notionary/page/content/renderer/renderers/todo.py +1 -1
- notionary/page/content/renderer/renderers/toggle.py +6 -7
- notionary/page/content/service.py +4 -1
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +0 -2
- notionary/page/content/syntax/{service.py → registry.py} +31 -91
- notionary/page/properties/client.py +3 -3
- notionary/page/properties/models.py +3 -2
- notionary/page/properties/service.py +18 -3
- notionary/page/service.py +22 -80
- notionary/shared/entity/service.py +94 -36
- notionary/shared/models/cover.py +1 -1
- notionary/shared/typings.py +3 -0
- notionary/user/base.py +60 -11
- notionary/user/factory.py +0 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +18 -6
- notionary/utils/mixins/logging.py +38 -27
- notionary/utils/pagination.py +70 -16
- notionary/workspace/__init__.py +2 -1
- notionary/workspace/client.py +4 -2
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +25 -1
- notionary/workspace/query/models.py +12 -3
- notionary/workspace/query/service.py +57 -32
- notionary/workspace/service.py +31 -21
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/METADATA +35 -105
- notionary-0.3.1.dist-info/RECORD +211 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +0 -35
- notionary/page/content/parser/parsers/toggleable_heading.py +0 -150
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +0 -62
- notionary/page/content/renderer/renderers/toggleable_heading.py +0 -78
- notionary/utils/async_retry.py +0 -39
- notionary/utils/singleton.py +0 -13
- notionary-0.2.28.dist-info/RECORD +0 -200
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/WHEEL +0 -0
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/licenses/LICENSE +0 -0
notionary/data_source/service.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Self
|
|
|
7
7
|
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
8
8
|
from notionary.data_source.http.client import DataSourceClient
|
|
9
9
|
from notionary.data_source.http.data_source_instance_client import DataSourceInstanceClient
|
|
10
|
-
from notionary.data_source.properties.
|
|
10
|
+
from notionary.data_source.properties.schemas import (
|
|
11
11
|
DataSourceMultiSelectProperty,
|
|
12
12
|
DataSourceProperty,
|
|
13
13
|
DataSourcePropertyOption,
|
|
@@ -16,26 +16,18 @@ from notionary.data_source.properties.models import (
|
|
|
16
16
|
DataSourceSelectProperty,
|
|
17
17
|
DataSourceStatusProperty,
|
|
18
18
|
)
|
|
19
|
-
from notionary.data_source.query
|
|
20
|
-
from notionary.data_source.
|
|
21
|
-
from notionary.data_source.query.schema import (
|
|
22
|
-
DataSourceQueryParams,
|
|
23
|
-
)
|
|
19
|
+
from notionary.data_source.query import DataSourceQueryBuilder, DataSourceQueryParams, QueryResolver
|
|
20
|
+
from notionary.data_source.schema.service import DataSourcePropertySchemaFormatter
|
|
24
21
|
from notionary.data_source.schemas import DataSourceDto
|
|
25
22
|
from notionary.exceptions.data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
|
|
26
23
|
from notionary.page.properties.models import PageTitleProperty
|
|
27
24
|
from notionary.page.schemas import NotionPageDto
|
|
28
25
|
from notionary.shared.entity.dto_parsers import (
|
|
29
|
-
extract_cover_image_url_from_dto,
|
|
30
|
-
extract_database_id,
|
|
31
26
|
extract_description,
|
|
32
|
-
extract_emoji_icon_from_dto,
|
|
33
|
-
extract_external_icon_url_from_dto,
|
|
34
27
|
extract_title,
|
|
35
28
|
)
|
|
36
29
|
from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
|
|
37
30
|
from notionary.shared.entity.service import Entity
|
|
38
|
-
from notionary.user.schemas import PartialUserDto
|
|
39
31
|
from notionary.workspace.query.service import WorkspaceQueryService
|
|
40
32
|
|
|
41
33
|
if TYPE_CHECKING:
|
|
@@ -45,41 +37,21 @@ if TYPE_CHECKING:
|
|
|
45
37
|
class NotionDataSource(Entity):
|
|
46
38
|
def __init__(
|
|
47
39
|
self,
|
|
48
|
-
|
|
40
|
+
dto: DataSourceDto,
|
|
49
41
|
title: str,
|
|
50
|
-
|
|
51
|
-
created_by: PartialUserDto,
|
|
52
|
-
last_edited_time: str,
|
|
53
|
-
last_edited_by: PartialUserDto,
|
|
54
|
-
archived: bool,
|
|
55
|
-
in_trash: bool,
|
|
42
|
+
description: str | None,
|
|
56
43
|
properties: dict[str, DataSourceProperty],
|
|
57
|
-
|
|
58
|
-
emoji_icon: str | None = None,
|
|
59
|
-
external_icon_url: str | None = None,
|
|
60
|
-
cover_image_url: str | None = None,
|
|
61
|
-
description: str | None = None,
|
|
62
|
-
data_source_instance_client: DataSourceInstanceClient | None = None,
|
|
44
|
+
data_source_instance_client: DataSourceInstanceClient,
|
|
63
45
|
query_resolver: QueryResolver | None = None,
|
|
64
46
|
) -> None:
|
|
65
|
-
super().__init__(
|
|
66
|
-
|
|
67
|
-
created_time=created_time,
|
|
68
|
-
created_by=created_by,
|
|
69
|
-
last_edited_time=last_edited_time,
|
|
70
|
-
last_edited_by=last_edited_by,
|
|
71
|
-
in_trash=in_trash,
|
|
72
|
-
emoji_icon=emoji_icon,
|
|
73
|
-
external_icon_url=external_icon_url,
|
|
74
|
-
cover_image_url=cover_image_url,
|
|
75
|
-
)
|
|
76
|
-
self._parent_database_id = parent_database_id
|
|
47
|
+
super().__init__(dto=dto)
|
|
48
|
+
|
|
77
49
|
self._parent_database: NotionDatabase | None = None
|
|
78
50
|
self._title = title
|
|
79
|
-
self._archived = archived
|
|
51
|
+
self._archived = dto.archived
|
|
80
52
|
self._description = description
|
|
81
53
|
self._properties = properties or {}
|
|
82
|
-
self._data_source_client = data_source_instance_client
|
|
54
|
+
self._data_source_client = data_source_instance_client
|
|
83
55
|
self.query_resolver = query_resolver or QueryResolver()
|
|
84
56
|
|
|
85
57
|
@classmethod
|
|
@@ -98,76 +70,29 @@ class NotionDataSource(Entity):
|
|
|
98
70
|
async def from_title(
|
|
99
71
|
cls,
|
|
100
72
|
data_source_title: str,
|
|
101
|
-
min_similarity: float = 0.6,
|
|
102
73
|
search_service: WorkspaceQueryService | None = None,
|
|
103
74
|
) -> Self:
|
|
104
75
|
service = search_service or WorkspaceQueryService()
|
|
105
|
-
return await service.find_data_source(data_source_title
|
|
76
|
+
return await service.find_data_source(data_source_title)
|
|
106
77
|
|
|
107
78
|
@classmethod
|
|
108
79
|
async def _create_from_dto(
|
|
109
80
|
cls,
|
|
110
|
-
|
|
81
|
+
dto: DataSourceDto,
|
|
111
82
|
rich_text_converter: RichTextToMarkdownConverter,
|
|
112
83
|
) -> Self:
|
|
113
84
|
title, description = await asyncio.gather(
|
|
114
|
-
extract_title(
|
|
115
|
-
extract_description(
|
|
85
|
+
extract_title(dto, rich_text_converter),
|
|
86
|
+
extract_description(dto, rich_text_converter),
|
|
116
87
|
)
|
|
117
88
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return cls._create(
|
|
121
|
-
id=response.id,
|
|
122
|
-
title=title,
|
|
123
|
-
description=description,
|
|
124
|
-
created_time=response.created_time,
|
|
125
|
-
created_by=response.created_by,
|
|
126
|
-
last_edited_time=response.last_edited_time,
|
|
127
|
-
last_edited_by=response.last_edited_by,
|
|
128
|
-
archived=response.archived,
|
|
129
|
-
in_trash=response.in_trash,
|
|
130
|
-
properties=response.properties,
|
|
131
|
-
parent_database_id=parent_database_id,
|
|
132
|
-
emoji_icon=extract_emoji_icon_from_dto(response),
|
|
133
|
-
external_icon_url=extract_external_icon_url_from_dto(response),
|
|
134
|
-
cover_image_url=extract_cover_image_url_from_dto(response),
|
|
135
|
-
)
|
|
89
|
+
data_source_instance_client = DataSourceInstanceClient(data_source_id=dto.id)
|
|
136
90
|
|
|
137
|
-
@classmethod
|
|
138
|
-
def _create(
|
|
139
|
-
cls,
|
|
140
|
-
id: str,
|
|
141
|
-
title: str,
|
|
142
|
-
created_time: str,
|
|
143
|
-
created_by: PartialUserDto,
|
|
144
|
-
last_edited_time: str,
|
|
145
|
-
last_edited_by: PartialUserDto,
|
|
146
|
-
archived: bool,
|
|
147
|
-
in_trash: bool,
|
|
148
|
-
properties: dict[str, DataSourceProperty],
|
|
149
|
-
parent_database_id: str | None,
|
|
150
|
-
emoji_icon: str | None = None,
|
|
151
|
-
external_icon_url: str | None = None,
|
|
152
|
-
cover_image_url: str | None = None,
|
|
153
|
-
description: str | None = None,
|
|
154
|
-
) -> Self:
|
|
155
|
-
data_source_instance_client = DataSourceInstanceClient(data_source_id=id)
|
|
156
91
|
return cls(
|
|
157
|
-
|
|
92
|
+
dto=dto,
|
|
158
93
|
title=title,
|
|
159
|
-
created_time=created_time,
|
|
160
|
-
created_by=created_by,
|
|
161
|
-
last_edited_time=last_edited_time,
|
|
162
|
-
last_edited_by=last_edited_by,
|
|
163
|
-
archived=archived,
|
|
164
|
-
in_trash=in_trash,
|
|
165
|
-
parent_database_id=parent_database_id,
|
|
166
|
-
emoji_icon=emoji_icon,
|
|
167
|
-
external_icon_url=external_icon_url,
|
|
168
|
-
cover_image_url=cover_image_url,
|
|
169
94
|
description=description,
|
|
170
|
-
properties=properties,
|
|
95
|
+
properties=dto.properties,
|
|
171
96
|
data_source_instance_client=data_source_instance_client,
|
|
172
97
|
)
|
|
173
98
|
|
|
@@ -191,11 +116,6 @@ class NotionDataSource(Entity):
|
|
|
191
116
|
def properties(self) -> dict[str, DataSourceProperty]:
|
|
192
117
|
return self._properties
|
|
193
118
|
|
|
194
|
-
async def get_parent_database(self) -> NotionDatabase | None:
|
|
195
|
-
if self._parent_database is None and self._parent_database_id:
|
|
196
|
-
self._parent_database = await NotionDatabase.from_id(self._parent_database_id)
|
|
197
|
-
return self._parent_database
|
|
198
|
-
|
|
199
119
|
async def create_blank_page(self, title: str | None = None) -> NotionPage:
|
|
200
120
|
return await self._data_source_client.create_blank_page(title=title)
|
|
201
121
|
|
|
@@ -305,19 +225,21 @@ class NotionDataSource(Entity):
|
|
|
305
225
|
def filter(self) -> DataSourceQueryBuilder:
|
|
306
226
|
return DataSourceQueryBuilder(properties=self._properties)
|
|
307
227
|
|
|
308
|
-
async def
|
|
228
|
+
async def query_pages(
|
|
229
|
+
self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
230
|
+
) -> list[NotionPage]:
|
|
309
231
|
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
310
|
-
filter_fn(builder)
|
|
311
|
-
query_params =
|
|
232
|
+
configured_builder = filter_fn(builder)
|
|
233
|
+
query_params = configured_builder.build()
|
|
312
234
|
|
|
313
235
|
return await self.get_pages(query_params)
|
|
314
236
|
|
|
315
|
-
async def
|
|
237
|
+
async def query_pages_stream(
|
|
316
238
|
self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
317
239
|
) -> AsyncIterator[NotionPage]:
|
|
318
240
|
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
319
|
-
filter_fn(builder)
|
|
320
|
-
query_params =
|
|
241
|
+
configured_builder = filter_fn(builder)
|
|
242
|
+
query_params = configured_builder.build()
|
|
321
243
|
|
|
322
244
|
async for page in self.get_pages_stream(query_params):
|
|
323
245
|
yield page
|
|
@@ -351,3 +273,7 @@ class NotionDataSource(Entity):
|
|
|
351
273
|
return None
|
|
352
274
|
|
|
353
275
|
return await self.query_resolver.resolve_params(query_params)
|
|
276
|
+
|
|
277
|
+
async def get_schema_description(self) -> str:
|
|
278
|
+
formatter = DataSourcePropertySchemaFormatter(relation_options_fetcher=self._get_relation_options)
|
|
279
|
+
return await formatter.format(title=self._title, description=self._description, properties=self._properties)
|
notionary/database/service.py
CHANGED
|
@@ -10,14 +10,10 @@ from notionary.database.client import NotionDatabaseHttpClient
|
|
|
10
10
|
from notionary.database.database_metadata_update_client import DatabaseMetadataUpdateClient
|
|
11
11
|
from notionary.database.schemas import NotionDatabaseDto
|
|
12
12
|
from notionary.shared.entity.dto_parsers import (
|
|
13
|
-
extract_cover_image_url_from_dto,
|
|
14
13
|
extract_description,
|
|
15
|
-
extract_emoji_icon_from_dto,
|
|
16
|
-
extract_external_icon_url_from_dto,
|
|
17
14
|
extract_title,
|
|
18
15
|
)
|
|
19
16
|
from notionary.shared.entity.service import Entity
|
|
20
|
-
from notionary.user.schemas import PartialUserDto
|
|
21
17
|
from notionary.workspace.query.service import WorkspaceQueryService
|
|
22
18
|
|
|
23
19
|
type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
|
|
@@ -26,46 +22,24 @@ type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
|
|
|
26
22
|
class NotionDatabase(Entity):
|
|
27
23
|
def __init__(
|
|
28
24
|
self,
|
|
29
|
-
|
|
25
|
+
dto: NotionDatabaseDto,
|
|
30
26
|
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,
|
|
27
|
+
description: str | None,
|
|
38
28
|
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,
|
|
29
|
+
client: NotionDatabaseHttpClient,
|
|
30
|
+
metadata_update_client: DatabaseMetadataUpdateClient,
|
|
46
31
|
) -> 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
|
-
)
|
|
32
|
+
super().__init__(dto=dto)
|
|
33
|
+
|
|
58
34
|
self._title = title
|
|
59
|
-
self._url = url
|
|
60
|
-
self._public_url = public_url
|
|
61
35
|
self._description = description
|
|
62
|
-
self._is_inline = is_inline
|
|
36
|
+
self._is_inline = dto.is_inline
|
|
63
37
|
|
|
64
38
|
self._data_sources: list[NotionDataSource] | None = None
|
|
65
39
|
self._data_source_ids = data_source_ids
|
|
66
40
|
|
|
67
|
-
self.client = client
|
|
68
|
-
self._metadata_update_client = metadata_update_client
|
|
41
|
+
self.client = client
|
|
42
|
+
self._metadata_update_client = metadata_update_client
|
|
69
43
|
|
|
70
44
|
@classmethod
|
|
71
45
|
async def from_id(
|
|
@@ -86,40 +60,31 @@ class NotionDatabase(Entity):
|
|
|
86
60
|
async def from_title(
|
|
87
61
|
cls,
|
|
88
62
|
database_title: str,
|
|
89
|
-
min_similarity: float = 0.6,
|
|
90
63
|
search_service: WorkspaceQueryService | None = None,
|
|
91
64
|
) -> Self:
|
|
92
65
|
service = search_service or WorkspaceQueryService()
|
|
93
|
-
return await service.find_database(database_title
|
|
66
|
+
return await service.find_database(database_title)
|
|
94
67
|
|
|
95
68
|
@classmethod
|
|
96
69
|
async def _create_from_dto(
|
|
97
70
|
cls,
|
|
98
|
-
|
|
71
|
+
dto: NotionDatabaseDto,
|
|
99
72
|
rich_text_converter: RichTextToMarkdownConverter,
|
|
100
73
|
client: NotionDatabaseHttpClient,
|
|
101
74
|
) -> Self:
|
|
102
75
|
title, description = await asyncio.gather(
|
|
103
|
-
extract_title(
|
|
76
|
+
extract_title(dto, rich_text_converter), extract_description(dto, rich_text_converter)
|
|
104
77
|
)
|
|
105
78
|
|
|
79
|
+
metadata_update_client = DatabaseMetadataUpdateClient(database_id=dto.id)
|
|
80
|
+
|
|
106
81
|
return cls(
|
|
107
|
-
|
|
82
|
+
dto=dto,
|
|
108
83
|
title=title,
|
|
109
84
|
description=description,
|
|
110
|
-
|
|
111
|
-
created_by=response.created_by,
|
|
112
|
-
last_edited_time=response.last_edited_time,
|
|
113
|
-
last_edited_by=response.last_edited_by,
|
|
114
|
-
in_trash=response.in_trash,
|
|
115
|
-
is_inline=response.is_inline,
|
|
116
|
-
url=response.url,
|
|
117
|
-
public_url=response.public_url,
|
|
118
|
-
emoji_icon=extract_emoji_icon_from_dto(response),
|
|
119
|
-
external_icon_url=extract_external_icon_url_from_dto(response),
|
|
120
|
-
cover_image_url=extract_cover_image_url_from_dto(response),
|
|
121
|
-
data_source_ids=[ds.id for ds in response.data_sources],
|
|
85
|
+
data_source_ids=[ds.id for ds in dto.data_sources],
|
|
122
86
|
client=client,
|
|
87
|
+
metadata_update_client=metadata_update_client,
|
|
123
88
|
)
|
|
124
89
|
|
|
125
90
|
@property
|
|
@@ -130,14 +95,6 @@ class NotionDatabase(Entity):
|
|
|
130
95
|
def title(self) -> str:
|
|
131
96
|
return self._title
|
|
132
97
|
|
|
133
|
-
@property
|
|
134
|
-
def url(self) -> str:
|
|
135
|
-
return self._url
|
|
136
|
-
|
|
137
|
-
@property
|
|
138
|
-
def public_url(self) -> str | None:
|
|
139
|
-
return self._public_url
|
|
140
|
-
|
|
141
98
|
@property
|
|
142
99
|
def is_inline(self) -> bool:
|
|
143
100
|
return self._is_inline
|
notionary/exceptions/__init__.py
CHANGED
|
@@ -8,7 +8,8 @@ from .api import (
|
|
|
8
8
|
NotionValidationError,
|
|
9
9
|
)
|
|
10
10
|
from .base import NotionaryError
|
|
11
|
-
from .
|
|
11
|
+
from .block_parsing import InsufficientColumnsError, InvalidColumnRatioSumError, UnsupportedVideoFormatError
|
|
12
|
+
from .data_source import DataSourcePropertyNotFound, DataSourcePropertyTypeError
|
|
12
13
|
from .properties import AccessPagePropertyWithoutDataSourceError, PagePropertyNotFoundError, PagePropertyTypeError
|
|
13
14
|
from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
|
|
14
15
|
|
|
@@ -19,6 +20,8 @@ __all__ = [
|
|
|
19
20
|
"DataSourcePropertyTypeError",
|
|
20
21
|
"DatabaseNotFound",
|
|
21
22
|
"EntityNotFound",
|
|
23
|
+
"InsufficientColumnsError",
|
|
24
|
+
"InvalidColumnRatioSumError",
|
|
22
25
|
"NotionApiError",
|
|
23
26
|
"NotionAuthenticationError",
|
|
24
27
|
"NotionConnectionError",
|
|
@@ -30,4 +33,5 @@ __all__ = [
|
|
|
30
33
|
"PageNotFound",
|
|
31
34
|
"PagePropertyNotFoundError",
|
|
32
35
|
"PagePropertyTypeError",
|
|
36
|
+
"UnsupportedVideoFormatError",
|
|
33
37
|
]
|
|
@@ -14,3 +14,24 @@ class InvalidColumnRatioSumError(NotionaryError):
|
|
|
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
|
+
)
|
notionary/exceptions/search.py
CHANGED
|
@@ -31,3 +31,27 @@ class DataSourceNotFound(EntityNotFound):
|
|
|
31
31
|
class DatabaseNotFound(EntityNotFound):
|
|
32
32
|
def __init__(self, query: str, available_titles: list[str] | None = None) -> None:
|
|
33
33
|
super().__init__("database", query, available_titles)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NoUsersInWorkspace(NotionaryError):
|
|
37
|
+
def __init__(self, user_type: str) -> None:
|
|
38
|
+
self.user_type = user_type
|
|
39
|
+
message = f"No '{user_type}' users found in the workspace."
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UserNotFound(NotionaryError):
|
|
44
|
+
def __init__(self, user_type: str, query: str, available_names: list[str] | None = None) -> None:
|
|
45
|
+
self.user_type = user_type
|
|
46
|
+
self.query = query
|
|
47
|
+
self.available_names = available_names or []
|
|
48
|
+
|
|
49
|
+
if self.available_names:
|
|
50
|
+
message = (
|
|
51
|
+
f"No '{user_type}' user found with exact name '{query}'. "
|
|
52
|
+
f"Did you mean one of these? {self.available_names}"
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
message = f"No '{user_type}' user found with name '{query}'."
|
|
56
|
+
|
|
57
|
+
super().__init__(message)
|
notionary/http/client.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
|
-
from typing import Any
|
|
4
3
|
|
|
5
4
|
import httpx
|
|
6
5
|
from dotenv import load_dotenv
|
|
@@ -16,6 +15,7 @@ from notionary.exceptions.api import (
|
|
|
16
15
|
NotionValidationError,
|
|
17
16
|
)
|
|
18
17
|
from notionary.http.models import HttpMethod
|
|
18
|
+
from notionary.shared.typings import JsonDict
|
|
19
19
|
from notionary.utils.mixins.logging import LoggingMixin
|
|
20
20
|
|
|
21
21
|
load_dotenv()
|
|
@@ -80,25 +80,25 @@ class NotionHttpClient(LoggingMixin):
|
|
|
80
80
|
self._is_initialized = False
|
|
81
81
|
self.logger.debug("NotionHttpClient closed")
|
|
82
82
|
|
|
83
|
-
async def get(self, endpoint: str, params:
|
|
83
|
+
async def get(self, endpoint: str, params: JsonDict | None = None) -> JsonDict | None:
|
|
84
84
|
return await self.make_request(HttpMethod.GET, endpoint, params=params)
|
|
85
85
|
|
|
86
|
-
async def post(self, endpoint: str, data:
|
|
86
|
+
async def post(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
|
|
87
87
|
return await self.make_request(HttpMethod.POST, endpoint, data)
|
|
88
88
|
|
|
89
|
-
async def patch(self, endpoint: str, data:
|
|
89
|
+
async def patch(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
|
|
90
90
|
return await self.make_request(HttpMethod.PATCH, endpoint, data)
|
|
91
91
|
|
|
92
|
-
async def delete(self, endpoint: str) ->
|
|
92
|
+
async def delete(self, endpoint: str) -> JsonDict | None:
|
|
93
93
|
return await self.make_request(HttpMethod.DELETE, endpoint)
|
|
94
94
|
|
|
95
95
|
async def make_request(
|
|
96
96
|
self,
|
|
97
97
|
method: HttpMethod,
|
|
98
98
|
endpoint: str,
|
|
99
|
-
data:
|
|
100
|
-
params:
|
|
101
|
-
) ->
|
|
99
|
+
data: JsonDict | None = None,
|
|
100
|
+
params: JsonDict | None = None,
|
|
101
|
+
) -> JsonDict | None:
|
|
102
102
|
"""
|
|
103
103
|
Executes an HTTP request and returns the data or None on error.
|
|
104
104
|
|
|
@@ -158,7 +158,7 @@ class NotionHttpClient(LoggingMixin):
|
|
|
158
158
|
)
|
|
159
159
|
if status_code == 404:
|
|
160
160
|
raise NotionResourceNotFoundError(
|
|
161
|
-
"The requested resource was not found. Please verify the page/database ID.",
|
|
161
|
+
"The requested resource was not found. Please verify the page/database/datasource ID.",
|
|
162
162
|
status_code=status_code,
|
|
163
163
|
response_text=response_text,
|
|
164
164
|
)
|
|
@@ -193,7 +193,6 @@ class NotionHttpClient(LoggingMixin):
|
|
|
193
193
|
None,
|
|
194
194
|
)
|
|
195
195
|
if token:
|
|
196
|
-
self.logger.debug("Found token in environment variable.")
|
|
197
196
|
return token
|
|
198
197
|
self.logger.warning("No Notion API token found in environment variables")
|
|
199
198
|
return None
|
notionary/http/models.py
CHANGED
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import time
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from enum import StrEnum
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
from notionary.shared.typings import JsonDict
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class HttpMethod(StrEnum):
|
|
@@ -17,8 +18,8 @@ class HttpMethod(StrEnum):
|
|
|
17
18
|
class HttpRequest:
|
|
18
19
|
method: HttpMethod
|
|
19
20
|
endpoint: str
|
|
20
|
-
data:
|
|
21
|
-
params:
|
|
21
|
+
data: JsonDict | None = None
|
|
22
|
+
params: JsonDict | None = None
|
|
22
23
|
timestamp: float = field(default_factory=time.time)
|
|
23
24
|
cached_response: HttpResponse | None = None
|
|
24
25
|
|
|
@@ -38,7 +39,7 @@ class HttpRequest:
|
|
|
38
39
|
|
|
39
40
|
@dataclass
|
|
40
41
|
class HttpResponse:
|
|
41
|
-
data:
|
|
42
|
+
data: JsonDict | None
|
|
42
43
|
status_code: int = 200
|
|
43
44
|
headers: dict[str, str] = field(default_factory=dict)
|
|
44
45
|
timestamp: float = field(default_factory=time.time)
|
|
@@ -2,11 +2,16 @@ from notionary.blocks.client import NotionBlockHttpClient
|
|
|
2
2
|
from notionary.page.content.parser.factory import ConverterChainFactory
|
|
3
3
|
from notionary.page.content.parser.post_processing.handlers import RichTextLengthTruncationPostProcessor
|
|
4
4
|
from notionary.page.content.parser.post_processing.service import BlockPostProcessor
|
|
5
|
-
from notionary.page.content.parser.pre_processsing.handlers import
|
|
5
|
+
from notionary.page.content.parser.pre_processsing.handlers import (
|
|
6
|
+
ColumnSyntaxPreProcessor,
|
|
7
|
+
IndentationNormalizer,
|
|
8
|
+
VideoFormatPreProcessor,
|
|
9
|
+
WhitespacePreProcessor,
|
|
10
|
+
)
|
|
6
11
|
from notionary.page.content.parser.pre_processsing.service import MarkdownPreProcessor
|
|
7
12
|
from notionary.page.content.parser.service import MarkdownToNotionConverter
|
|
8
13
|
from notionary.page.content.renderer.factory import RendererChainFactory
|
|
9
|
-
from notionary.page.content.renderer.post_processing.handlers import
|
|
14
|
+
from notionary.page.content.renderer.post_processing.handlers import NumberedListPlaceholderReplacerPostProcessor
|
|
10
15
|
from notionary.page.content.renderer.post_processing.service import MarkdownRenderingPostProcessor
|
|
11
16
|
from notionary.page.content.renderer.service import NotionToMarkdownConverter
|
|
12
17
|
from notionary.page.content.service import PageContentService
|
|
@@ -55,6 +60,8 @@ class PageContentServiceFactory:
|
|
|
55
60
|
pre_processor = MarkdownPreProcessor()
|
|
56
61
|
pre_processor.register(ColumnSyntaxPreProcessor())
|
|
57
62
|
pre_processor.register(WhitespacePreProcessor())
|
|
63
|
+
pre_processor.register(IndentationNormalizer())
|
|
64
|
+
pre_processor.register(VideoFormatPreProcessor())
|
|
58
65
|
return pre_processor
|
|
59
66
|
|
|
60
67
|
def _create_post_processor(self) -> BlockPostProcessor:
|
|
@@ -64,5 +71,5 @@ class PageContentServiceFactory:
|
|
|
64
71
|
|
|
65
72
|
def _create_markdown_rendering_post_processor(self) -> MarkdownRenderingPostProcessor:
|
|
66
73
|
post_processor = MarkdownRenderingPostProcessor()
|
|
67
|
-
post_processor.register(
|
|
74
|
+
post_processor.register(NumberedListPlaceholderReplacerPostProcessor())
|
|
68
75
|
return post_processor
|