notionary 0.4.0__py3-none-any.whl → 0.4.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 +44 -1
- notionary/blocks/client.py +37 -11
- notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
- notionary/blocks/rich_text/models.py +13 -4
- notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
- notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
- notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
- notionary/blocks/schemas.py +2 -1
- notionary/comments/client.py +19 -6
- notionary/comments/factory.py +10 -3
- notionary/comments/schemas.py +9 -3
- notionary/comments/service.py +12 -4
- notionary/data_source/http/data_source_instance_client.py +59 -17
- notionary/data_source/properties/schemas.py +30 -10
- notionary/data_source/query/builder.py +67 -18
- notionary/data_source/query/resolver.py +16 -5
- notionary/data_source/query/schema.py +24 -6
- notionary/data_source/query/validator.py +18 -6
- notionary/data_source/schema/registry.py +31 -12
- notionary/data_source/schema/service.py +66 -20
- notionary/data_source/service.py +74 -23
- notionary/database/client.py +27 -9
- notionary/database/database_metadata_update_client.py +12 -4
- notionary/database/service.py +11 -4
- notionary/exceptions/__init__.py +15 -3
- notionary/exceptions/block_parsing.py +6 -2
- notionary/exceptions/data_source/builder.py +11 -5
- notionary/exceptions/data_source/properties.py +3 -1
- notionary/exceptions/file_upload.py +12 -3
- notionary/exceptions/properties.py +3 -1
- notionary/exceptions/search.py +6 -2
- notionary/file_upload/client.py +5 -1
- notionary/file_upload/config/config.py +10 -3
- notionary/file_upload/query/builder.py +6 -2
- notionary/file_upload/schemas.py +3 -1
- notionary/file_upload/service.py +42 -14
- notionary/file_upload/validation/factory.py +3 -1
- notionary/file_upload/validation/impl/file_name_length.py +3 -1
- notionary/file_upload/validation/models.py +15 -5
- notionary/file_upload/validation/validators/file_extension.py +12 -3
- notionary/http/client.py +27 -8
- notionary/page/content/__init__.py +9 -0
- notionary/page/content/factory.py +21 -7
- notionary/page/content/markdown/builder.py +85 -23
- notionary/page/content/markdown/nodes/audio.py +8 -4
- notionary/page/content/markdown/nodes/base.py +3 -3
- notionary/page/content/markdown/nodes/bookmark.py +5 -3
- notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
- notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
- notionary/page/content/markdown/nodes/callout.py +2 -2
- notionary/page/content/markdown/nodes/code.py +5 -3
- notionary/page/content/markdown/nodes/columns.py +3 -3
- notionary/page/content/markdown/nodes/container.py +9 -5
- notionary/page/content/markdown/nodes/divider.py +2 -2
- notionary/page/content/markdown/nodes/embed.py +8 -4
- notionary/page/content/markdown/nodes/equation.py +4 -2
- notionary/page/content/markdown/nodes/file.py +8 -4
- notionary/page/content/markdown/nodes/heading.py +2 -2
- notionary/page/content/markdown/nodes/image.py +8 -4
- notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
- notionary/page/content/markdown/nodes/numbered_list.py +5 -3
- notionary/page/content/markdown/nodes/paragraph.py +4 -2
- notionary/page/content/markdown/nodes/pdf.py +8 -4
- notionary/page/content/markdown/nodes/quote.py +2 -2
- notionary/page/content/markdown/nodes/space.py +2 -2
- notionary/page/content/markdown/nodes/table.py +8 -5
- notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
- notionary/page/content/markdown/nodes/todo.py +15 -7
- notionary/page/content/markdown/nodes/toggle.py +2 -2
- notionary/page/content/markdown/nodes/video.py +8 -4
- notionary/page/content/markdown/structured_output/__init__.py +73 -0
- notionary/page/content/markdown/structured_output/models.py +391 -0
- notionary/page/content/markdown/structured_output/service.py +211 -0
- notionary/page/content/parser/context.py +1 -1
- notionary/page/content/parser/factory.py +23 -8
- notionary/page/content/parser/parsers/audio.py +7 -2
- notionary/page/content/parser/parsers/base.py +2 -2
- notionary/page/content/parser/parsers/bookmark.py +2 -2
- notionary/page/content/parser/parsers/breadcrumb.py +2 -2
- notionary/page/content/parser/parsers/bulleted_list.py +19 -6
- notionary/page/content/parser/parsers/callout.py +15 -5
- notionary/page/content/parser/parsers/caption.py +9 -3
- notionary/page/content/parser/parsers/code.py +21 -7
- notionary/page/content/parser/parsers/column.py +8 -4
- notionary/page/content/parser/parsers/column_list.py +19 -7
- notionary/page/content/parser/parsers/divider.py +2 -2
- notionary/page/content/parser/parsers/embed.py +2 -2
- notionary/page/content/parser/parsers/equation.py +8 -4
- notionary/page/content/parser/parsers/file.py +7 -2
- notionary/page/content/parser/parsers/file_like_block.py +30 -10
- notionary/page/content/parser/parsers/heading.py +31 -10
- notionary/page/content/parser/parsers/image.py +7 -2
- notionary/page/content/parser/parsers/numbered_list.py +18 -6
- notionary/page/content/parser/parsers/paragraph.py +3 -1
- notionary/page/content/parser/parsers/pdf.py +7 -2
- notionary/page/content/parser/parsers/quote.py +28 -9
- notionary/page/content/parser/parsers/space.py +2 -2
- notionary/page/content/parser/parsers/table.py +31 -10
- notionary/page/content/parser/parsers/table_of_contents.py +7 -3
- notionary/page/content/parser/parsers/todo.py +15 -5
- notionary/page/content/parser/parsers/toggle.py +15 -5
- notionary/page/content/parser/parsers/video.py +7 -2
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
- notionary/page/content/parser/post_processing/service.py +3 -1
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
- notionary/page/content/parser/service.py +4 -1
- notionary/page/content/renderer/context.py +15 -5
- notionary/page/content/renderer/factory.py +12 -6
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
- notionary/page/content/renderer/renderers/audio.py +14 -5
- notionary/page/content/renderer/renderers/base.py +3 -3
- notionary/page/content/renderer/renderers/bookmark.py +3 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
- notionary/page/content/renderer/renderers/callout.py +19 -7
- notionary/page/content/renderer/renderers/captioned_block.py +11 -5
- notionary/page/content/renderer/renderers/code.py +6 -2
- notionary/page/content/renderer/renderers/column.py +3 -1
- notionary/page/content/renderer/renderers/column_list.py +3 -1
- notionary/page/content/renderer/renderers/embed.py +3 -1
- notionary/page/content/renderer/renderers/equation.py +3 -1
- notionary/page/content/renderer/renderers/file.py +14 -5
- notionary/page/content/renderer/renderers/file_like_block.py +8 -4
- notionary/page/content/renderer/renderers/heading.py +22 -8
- notionary/page/content/renderer/renderers/image.py +13 -4
- notionary/page/content/renderer/renderers/numbered_list.py +8 -3
- notionary/page/content/renderer/renderers/paragraph.py +12 -4
- notionary/page/content/renderer/renderers/pdf.py +14 -5
- notionary/page/content/renderer/renderers/quote.py +14 -6
- notionary/page/content/renderer/renderers/table.py +15 -5
- notionary/page/content/renderer/renderers/todo.py +16 -6
- notionary/page/content/renderer/renderers/toggle.py +8 -4
- notionary/page/content/renderer/renderers/video.py +14 -5
- notionary/page/content/renderer/service.py +9 -3
- notionary/page/content/service.py +21 -7
- notionary/page/content/syntax/definition/__init__.py +11 -0
- notionary/page/content/syntax/definition/models.py +57 -0
- notionary/page/content/syntax/definition/registry.py +371 -0
- notionary/page/content/syntax/prompts/__init__.py +4 -0
- notionary/page/content/syntax/prompts/models.py +11 -0
- notionary/page/content/syntax/prompts/registry.py +703 -0
- notionary/page/page_metadata_update_client.py +12 -4
- notionary/page/properties/client.py +45 -15
- notionary/page/properties/factory.py +6 -2
- notionary/page/properties/service.py +110 -36
- notionary/page/service.py +20 -6
- notionary/shared/entity/client.py +6 -2
- notionary/shared/entity/dto_parsers.py +3 -1
- notionary/shared/entity/entity_metadata_update_client.py +9 -3
- notionary/shared/entity/service.py +53 -22
- notionary/shared/models/file.py +3 -1
- notionary/user/base.py +6 -2
- notionary/user/bot.py +10 -2
- notionary/user/client.py +3 -1
- notionary/user/person.py +3 -1
- notionary/user/schemas.py +3 -1
- notionary/user/service.py +6 -2
- notionary/utils/decorators.py +6 -2
- notionary/utils/fuzzy.py +6 -2
- notionary/utils/mixins/logging.py +3 -1
- notionary/utils/pagination.py +14 -4
- notionary/workspace/__init__.py +5 -1
- notionary/workspace/query/service.py +59 -16
- notionary/workspace/service.py +39 -11
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
- notionary-0.4.1.dist-info/RECORD +236 -0
- notionary/page/blocks/client.py +0 -1
- notionary/page/content/syntax/__init__.py +0 -5
- notionary/page/content/syntax/models.py +0 -66
- notionary/page/content/syntax/registry.py +0 -371
- notionary-0.4.0.dist-info/RECORD +0 -230
- /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,14 +3,25 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import AsyncIterator
|
|
4
4
|
from typing import TYPE_CHECKING, Any, override
|
|
5
5
|
|
|
6
|
-
from notionary.blocks.rich_text.rich_text_markdown_converter import
|
|
6
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import (
|
|
7
|
+
RichTextToMarkdownConverter,
|
|
8
|
+
)
|
|
7
9
|
from notionary.data_source.query.schema import DataSourceQueryParams
|
|
8
|
-
from notionary.data_source.schemas import
|
|
10
|
+
from notionary.data_source.schemas import (
|
|
11
|
+
DataSourceDto,
|
|
12
|
+
QueryDataSourceResponse,
|
|
13
|
+
UpdateDataSourceDto,
|
|
14
|
+
)
|
|
9
15
|
from notionary.http.client import NotionHttpClient
|
|
10
16
|
from notionary.page.schemas import NotionPageDto
|
|
11
|
-
from notionary.shared.entity.entity_metadata_update_client import
|
|
17
|
+
from notionary.shared.entity.entity_metadata_update_client import (
|
|
18
|
+
EntityMetadataUpdateClient,
|
|
19
|
+
)
|
|
12
20
|
from notionary.shared.typings import JsonDict
|
|
13
|
-
from notionary.utils.pagination import
|
|
21
|
+
from notionary.utils.pagination import (
|
|
22
|
+
paginate_notion_api,
|
|
23
|
+
paginate_notion_api_generator,
|
|
24
|
+
)
|
|
14
25
|
|
|
15
26
|
if TYPE_CHECKING:
|
|
16
27
|
from notionary import NotionPage
|
|
@@ -22,9 +33,15 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
22
33
|
self._data_source_id = data_source_id
|
|
23
34
|
|
|
24
35
|
@override
|
|
25
|
-
async def patch_metadata(
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
async def patch_metadata(
|
|
37
|
+
self, update_data_source_dto: UpdateDataSourceDto
|
|
38
|
+
) -> DataSourceDto:
|
|
39
|
+
update_data_source_dto_dict = update_data_source_dto.model_dump(
|
|
40
|
+
exclude_none=True
|
|
41
|
+
)
|
|
42
|
+
response = await self.patch(
|
|
43
|
+
f"data_sources/{self._data_source_id}", data=update_data_source_dto_dict
|
|
44
|
+
)
|
|
28
45
|
return DataSourceDto.model_validate(response)
|
|
29
46
|
|
|
30
47
|
async def update_title(self, title: str) -> DataSourceDto:
|
|
@@ -40,28 +57,38 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
40
57
|
await self.patch_metadata(update_data_source_dto)
|
|
41
58
|
|
|
42
59
|
async def update_description(self, description: str) -> str:
|
|
43
|
-
from notionary.blocks.rich_text.markdown_rich_text_converter import
|
|
60
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
61
|
+
MarkdownRichTextConverter,
|
|
62
|
+
)
|
|
44
63
|
|
|
45
64
|
markdown_rich_text_converter = MarkdownRichTextConverter()
|
|
46
|
-
rich_text_description = await markdown_rich_text_converter.to_rich_text(
|
|
65
|
+
rich_text_description = await markdown_rich_text_converter.to_rich_text(
|
|
66
|
+
description
|
|
67
|
+
)
|
|
47
68
|
update_data_source_dto = UpdateDataSourceDto(description=rich_text_description)
|
|
48
69
|
|
|
49
70
|
updated_data_source_dto = await self.patch_metadata(update_data_source_dto)
|
|
50
71
|
|
|
51
72
|
markdown_rich_text_converter = RichTextToMarkdownConverter()
|
|
52
73
|
updated_markdown_description = (
|
|
53
|
-
await markdown_rich_text_converter.to_markdown(
|
|
74
|
+
await markdown_rich_text_converter.to_markdown(
|
|
75
|
+
updated_data_source_dto.description
|
|
76
|
+
)
|
|
54
77
|
if updated_data_source_dto.description
|
|
55
78
|
else None
|
|
56
79
|
)
|
|
57
80
|
return updated_markdown_description
|
|
58
81
|
|
|
59
|
-
async def query(
|
|
82
|
+
async def query(
|
|
83
|
+
self, query_params: DataSourceQueryParams | None = None
|
|
84
|
+
) -> QueryDataSourceResponse:
|
|
60
85
|
query_params_dict = query_params.to_api_params() if query_params else {}
|
|
61
86
|
total_result_limit = query_params.total_results_limit if query_params else None
|
|
62
87
|
|
|
63
88
|
all_results = await paginate_notion_api(
|
|
64
|
-
self._make_query_request,
|
|
89
|
+
self._make_query_request,
|
|
90
|
+
query_data=query_params_dict or {},
|
|
91
|
+
total_result_limit=total_result_limit,
|
|
65
92
|
)
|
|
66
93
|
|
|
67
94
|
return QueryDataSourceResponse(
|
|
@@ -70,17 +97,24 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
70
97
|
has_more=False,
|
|
71
98
|
)
|
|
72
99
|
|
|
73
|
-
async def query_stream(
|
|
100
|
+
async def query_stream(
|
|
101
|
+
self, query_params: DataSourceQueryParams | None = None
|
|
102
|
+
) -> AsyncIterator[Any]:
|
|
74
103
|
query_params_dict = query_params.model_dump() if query_params else {}
|
|
75
104
|
total_result_limit = query_params.total_results_limit if query_params else None
|
|
76
105
|
|
|
77
106
|
async for result in paginate_notion_api_generator(
|
|
78
|
-
self._make_query_request,
|
|
107
|
+
self._make_query_request,
|
|
108
|
+
query_data=query_params_dict or {},
|
|
109
|
+
total_results_limit=total_result_limit,
|
|
79
110
|
):
|
|
80
111
|
yield result
|
|
81
112
|
|
|
82
113
|
async def _make_query_request(
|
|
83
|
-
self,
|
|
114
|
+
self,
|
|
115
|
+
query_data: JsonDict,
|
|
116
|
+
start_cursor: str | None = None,
|
|
117
|
+
page_size: int | None = None,
|
|
84
118
|
) -> QueryDataSourceResponse:
|
|
85
119
|
current_query_data = query_data.copy()
|
|
86
120
|
if start_cursor:
|
|
@@ -88,13 +122,21 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
88
122
|
if page_size:
|
|
89
123
|
current_query_data["page_size"] = page_size
|
|
90
124
|
|
|
91
|
-
response = await self.post(
|
|
125
|
+
response = await self.post(
|
|
126
|
+
f"data_sources/{self._data_source_id}/query", data=current_query_data
|
|
127
|
+
)
|
|
92
128
|
return QueryDataSourceResponse.model_validate(response)
|
|
93
129
|
|
|
94
130
|
async def create_blank_page(self, title: str | None = None) -> NotionPage:
|
|
95
131
|
from notionary import NotionPage
|
|
96
132
|
|
|
97
|
-
data = {
|
|
133
|
+
data = {
|
|
134
|
+
"parent": {
|
|
135
|
+
"type": "data_source_id",
|
|
136
|
+
"data_source_id": self._data_source_id,
|
|
137
|
+
},
|
|
138
|
+
"properties": {},
|
|
139
|
+
}
|
|
98
140
|
|
|
99
141
|
if title:
|
|
100
142
|
data["properties"]["Name"] = {"title": [{"text": {"content": title}}]}
|
|
@@ -163,7 +163,9 @@ class DataSourceMultiSelectConfig(BaseModel):
|
|
|
163
163
|
|
|
164
164
|
class DataSourceMultiSelectProperty(DataSourceProperty):
|
|
165
165
|
type: Literal[PropertyType.MULTI_SELECT] = PropertyType.MULTI_SELECT
|
|
166
|
-
multi_select: DataSourceMultiSelectConfig = Field(
|
|
166
|
+
multi_select: DataSourceMultiSelectConfig = Field(
|
|
167
|
+
default_factory=DataSourceMultiSelectConfig
|
|
168
|
+
)
|
|
167
169
|
|
|
168
170
|
@property
|
|
169
171
|
def option_names(self) -> list[str]:
|
|
@@ -199,7 +201,9 @@ class DataSourceCreatedTimeConfig(BaseModel): ...
|
|
|
199
201
|
|
|
200
202
|
class DataSourceCreatedTimeProperty(DataSourceProperty):
|
|
201
203
|
type: Literal[PropertyType.CREATED_TIME] = PropertyType.CREATED_TIME
|
|
202
|
-
created_time: DataSourceCreatedTimeConfig = Field(
|
|
204
|
+
created_time: DataSourceCreatedTimeConfig = Field(
|
|
205
|
+
default_factory=DataSourceCreatedTimeConfig
|
|
206
|
+
)
|
|
203
207
|
|
|
204
208
|
|
|
205
209
|
class DataSourceCreatedByConfig(BaseModel): ...
|
|
@@ -207,7 +211,9 @@ class DataSourceCreatedByConfig(BaseModel): ...
|
|
|
207
211
|
|
|
208
212
|
class DataSourceCreatedByProperty(DataSourceProperty):
|
|
209
213
|
type: Literal[PropertyType.CREATED_BY] = PropertyType.CREATED_BY
|
|
210
|
-
created_by: DataSourceCreatedByConfig = Field(
|
|
214
|
+
created_by: DataSourceCreatedByConfig = Field(
|
|
215
|
+
default_factory=DataSourceCreatedByConfig
|
|
216
|
+
)
|
|
211
217
|
|
|
212
218
|
|
|
213
219
|
class DataSourceLastEditedTimeConfig(BaseModel): ...
|
|
@@ -215,7 +221,9 @@ class DataSourceLastEditedTimeConfig(BaseModel): ...
|
|
|
215
221
|
|
|
216
222
|
class DataSourceLastEditedTimeProperty(DataSourceProperty):
|
|
217
223
|
type: Literal[PropertyType.LAST_EDITED_TIME] = PropertyType.LAST_EDITED_TIME
|
|
218
|
-
last_edited_time: DataSourceLastEditedTimeConfig = Field(
|
|
224
|
+
last_edited_time: DataSourceLastEditedTimeConfig = Field(
|
|
225
|
+
default_factory=DataSourceLastEditedTimeConfig
|
|
226
|
+
)
|
|
219
227
|
|
|
220
228
|
|
|
221
229
|
class DataSourceLastEditedByConfig(BaseModel): ...
|
|
@@ -223,7 +231,9 @@ class DataSourceLastEditedByConfig(BaseModel): ...
|
|
|
223
231
|
|
|
224
232
|
class DataSourceLastEditedByProperty(DataSourceProperty):
|
|
225
233
|
type: Literal[PropertyType.LAST_EDITED_BY] = PropertyType.LAST_EDITED_BY
|
|
226
|
-
last_edited_by: DataSourceLastEditedByConfig = Field(
|
|
234
|
+
last_edited_by: DataSourceLastEditedByConfig = Field(
|
|
235
|
+
default_factory=DataSourceLastEditedByConfig
|
|
236
|
+
)
|
|
227
237
|
|
|
228
238
|
|
|
229
239
|
class DataSourceLastVisitedTimeConfig(BaseModel): ...
|
|
@@ -231,7 +241,9 @@ class DataSourceLastVisitedTimeConfig(BaseModel): ...
|
|
|
231
241
|
|
|
232
242
|
class DataSourceLastVisitedTimeProperty(DataSourceProperty):
|
|
233
243
|
type: Literal[PropertyType.LAST_VISITED_TIME] = PropertyType.LAST_VISITED_TIME
|
|
234
|
-
last_visited_time: DataSourceLastVisitedTimeConfig = Field(
|
|
244
|
+
last_visited_time: DataSourceLastVisitedTimeConfig = Field(
|
|
245
|
+
default_factory=DataSourceLastVisitedTimeConfig
|
|
246
|
+
)
|
|
235
247
|
|
|
236
248
|
|
|
237
249
|
class DataSourceTitleConfig(BaseModel): ...
|
|
@@ -247,7 +259,9 @@ class DataSourceRichTextConfig(BaseModel): ...
|
|
|
247
259
|
|
|
248
260
|
class DataSourceRichTextProperty(DataSourceProperty):
|
|
249
261
|
type: Literal[PropertyType.RICH_TEXT] = PropertyType.RICH_TEXT
|
|
250
|
-
rich_text: DataSourceRichTextConfig = Field(
|
|
262
|
+
rich_text: DataSourceRichTextConfig = Field(
|
|
263
|
+
default_factory=DataSourceRichTextConfig
|
|
264
|
+
)
|
|
251
265
|
|
|
252
266
|
|
|
253
267
|
class DataSourceURLConfig(BaseModel): ...
|
|
@@ -300,7 +314,9 @@ class DataSourcePhoneNumberConfig(BaseModel): ...
|
|
|
300
314
|
|
|
301
315
|
class DataSourcePhoneNumberProperty(DataSourceProperty):
|
|
302
316
|
type: Literal[PropertyType.PHONE_NUMBER] = PropertyType.PHONE_NUMBER
|
|
303
|
-
phone_number: DataSourcePhoneNumberConfig = Field(
|
|
317
|
+
phone_number: DataSourcePhoneNumberConfig = Field(
|
|
318
|
+
default_factory=DataSourcePhoneNumberConfig
|
|
319
|
+
)
|
|
304
320
|
|
|
305
321
|
|
|
306
322
|
class DataSourceFilesConfig(BaseModel): ...
|
|
@@ -347,7 +363,9 @@ class DataSourceUniqueIdConfig(BaseModel):
|
|
|
347
363
|
|
|
348
364
|
class DataSourceUniqueIdProperty(DataSourceProperty):
|
|
349
365
|
type: Literal[PropertyType.UNIQUE_ID] = PropertyType.UNIQUE_ID
|
|
350
|
-
unique_id: DataSourceUniqueIdConfig = Field(
|
|
366
|
+
unique_id: DataSourceUniqueIdConfig = Field(
|
|
367
|
+
default_factory=DataSourceUniqueIdConfig
|
|
368
|
+
)
|
|
351
369
|
|
|
352
370
|
@property
|
|
353
371
|
def prefix(self) -> str | None:
|
|
@@ -383,7 +401,9 @@ class DataSourceVerificationConfig(BaseModel): ...
|
|
|
383
401
|
|
|
384
402
|
class DataSourceVerificationProperty(DataSourceProperty):
|
|
385
403
|
type: Literal[PropertyType.VERIFICATION] = PropertyType.VERIFICATION
|
|
386
|
-
verification: DataSourceVerificationConfig = Field(
|
|
404
|
+
verification: DataSourceVerificationConfig = Field(
|
|
405
|
+
default_factory=DataSourceVerificationConfig
|
|
406
|
+
)
|
|
387
407
|
|
|
388
408
|
|
|
389
409
|
class DataSourceUnknownProperty(BaseModel):
|
|
@@ -160,7 +160,9 @@ class DataSourceQueryBuilder:
|
|
|
160
160
|
def people_is_empty(self) -> Self:
|
|
161
161
|
return self._add_filter(ArrayOperator.IS_EMPTY, None)
|
|
162
162
|
|
|
163
|
-
def order_by(
|
|
163
|
+
def order_by(
|
|
164
|
+
self, property_name: str, direction: SortDirection = SortDirection.ASCENDING
|
|
165
|
+
) -> Self:
|
|
164
166
|
self._ensure_property_exists(property_name)
|
|
165
167
|
sort = PropertySort(property=property_name, direction=direction)
|
|
166
168
|
self._sorts.append(sort)
|
|
@@ -178,7 +180,9 @@ class DataSourceQueryBuilder:
|
|
|
178
180
|
def order_by_created_time_descending(self) -> Self:
|
|
179
181
|
return self._order_by_created_time(SortDirection.DESCENDING)
|
|
180
182
|
|
|
181
|
-
def _order_by_created_time(
|
|
183
|
+
def _order_by_created_time(
|
|
184
|
+
self, direction: SortDirection = SortDirection.DESCENDING
|
|
185
|
+
) -> Self:
|
|
182
186
|
sort = TimestampSort(timestamp=TimestampType.CREATED_TIME, direction=direction)
|
|
183
187
|
self._sorts.append(sort)
|
|
184
188
|
return self
|
|
@@ -189,8 +193,12 @@ class DataSourceQueryBuilder:
|
|
|
189
193
|
def order_by_last_edited_time_descending(self) -> Self:
|
|
190
194
|
return self._order_by_last_edited_time(SortDirection.DESCENDING)
|
|
191
195
|
|
|
192
|
-
def _order_by_last_edited_time(
|
|
193
|
-
|
|
196
|
+
def _order_by_last_edited_time(
|
|
197
|
+
self, direction: SortDirection = SortDirection.DESCENDING
|
|
198
|
+
) -> Self:
|
|
199
|
+
sort = TimestampSort(
|
|
200
|
+
timestamp=TimestampType.LAST_EDITED_TIME, direction=direction
|
|
201
|
+
)
|
|
194
202
|
self._sorts.append(sort)
|
|
195
203
|
return self
|
|
196
204
|
|
|
@@ -211,7 +219,10 @@ class DataSourceQueryBuilder:
|
|
|
211
219
|
notion_filter = self._create_notion_filter_if_needed()
|
|
212
220
|
sorts = self._create_sorts_if_needed()
|
|
213
221
|
return DataSourceQueryParams(
|
|
214
|
-
filter=notion_filter,
|
|
222
|
+
filter=notion_filter,
|
|
223
|
+
sorts=sorts,
|
|
224
|
+
page_size=self._page_size,
|
|
225
|
+
total_results_limit=self._total_results_limit,
|
|
215
226
|
)
|
|
216
227
|
|
|
217
228
|
def _select_property_without_negation(self, property_name: str) -> None:
|
|
@@ -264,7 +275,9 @@ class DataSourceQueryBuilder:
|
|
|
264
275
|
def _has_no_filters(self) -> bool:
|
|
265
276
|
return not self._filters
|
|
266
277
|
|
|
267
|
-
def _is_regular_filter_condition(
|
|
278
|
+
def _is_regular_filter_condition(
|
|
279
|
+
self, filter_item: InternalFilterCondition
|
|
280
|
+
) -> bool:
|
|
268
281
|
return isinstance(filter_item, FilterCondition)
|
|
269
282
|
|
|
270
283
|
def _finalize_current_or_group(self) -> None:
|
|
@@ -284,7 +297,11 @@ class DataSourceQueryBuilder:
|
|
|
284
297
|
|
|
285
298
|
def _add_filter(
|
|
286
299
|
self,
|
|
287
|
-
operator: StringOperator
|
|
300
|
+
operator: StringOperator
|
|
301
|
+
| NumberOperator
|
|
302
|
+
| BooleanOperator
|
|
303
|
+
| DateOperator
|
|
304
|
+
| ArrayOperator,
|
|
288
305
|
value: str | int | float | list[str | int | float] | None,
|
|
289
306
|
) -> Self:
|
|
290
307
|
self._ensure_property_is_selected()
|
|
@@ -301,7 +318,9 @@ class DataSourceQueryBuilder:
|
|
|
301
318
|
|
|
302
319
|
property_obj = self._properties.get(self._current_property)
|
|
303
320
|
if property_obj:
|
|
304
|
-
self._query_validator.validate_operator_for_property(
|
|
321
|
+
self._query_validator.validate_operator_for_property(
|
|
322
|
+
self._current_property, property_obj, operator
|
|
323
|
+
)
|
|
305
324
|
return self
|
|
306
325
|
|
|
307
326
|
def _ensure_property_is_selected(self) -> None:
|
|
@@ -313,8 +332,14 @@ class DataSourceQueryBuilder:
|
|
|
313
332
|
|
|
314
333
|
def _apply_negation_if_needed(
|
|
315
334
|
self,
|
|
316
|
-
operator: StringOperator
|
|
317
|
-
|
|
335
|
+
operator: StringOperator
|
|
336
|
+
| NumberOperator
|
|
337
|
+
| BooleanOperator
|
|
338
|
+
| DateOperator
|
|
339
|
+
| ArrayOperator,
|
|
340
|
+
) -> (
|
|
341
|
+
StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator
|
|
342
|
+
):
|
|
318
343
|
if not self._negate_next:
|
|
319
344
|
return operator
|
|
320
345
|
|
|
@@ -324,7 +349,11 @@ class DataSourceQueryBuilder:
|
|
|
324
349
|
|
|
325
350
|
def _create_filter_condition(
|
|
326
351
|
self,
|
|
327
|
-
operator: StringOperator
|
|
352
|
+
operator: StringOperator
|
|
353
|
+
| NumberOperator
|
|
354
|
+
| BooleanOperator
|
|
355
|
+
| DateOperator
|
|
356
|
+
| ArrayOperator,
|
|
328
357
|
value: str | int | float | list[str | int | float] | None,
|
|
329
358
|
) -> FilterCondition:
|
|
330
359
|
field_type = self._determine_field_type_from_operator(operator)
|
|
@@ -372,13 +401,17 @@ class DataSourceQueryBuilder:
|
|
|
372
401
|
property_filters = [self._build_filter(f) for f in self._filters]
|
|
373
402
|
return CompoundFilter(operator=LogicalOperator.AND, filters=property_filters)
|
|
374
403
|
|
|
375
|
-
def _build_filter(
|
|
404
|
+
def _build_filter(
|
|
405
|
+
self, condition: InternalFilterCondition
|
|
406
|
+
) -> PropertyFilter | CompoundFilter:
|
|
376
407
|
if isinstance(condition, OrGroupMarker):
|
|
377
408
|
return self._build_or_compound_filter(condition)
|
|
378
409
|
return self._build_property_filter(condition)
|
|
379
410
|
|
|
380
411
|
def _build_or_compound_filter(self, or_marker: OrGroupMarker) -> CompoundFilter:
|
|
381
|
-
property_filters = [
|
|
412
|
+
property_filters = [
|
|
413
|
+
self._build_property_filter(c) for c in or_marker.conditions
|
|
414
|
+
]
|
|
382
415
|
return CompoundFilter(operator=LogicalOperator.OR, filters=property_filters)
|
|
383
416
|
|
|
384
417
|
def _build_property_filter(self, condition: FilterCondition) -> PropertyFilter:
|
|
@@ -398,8 +431,14 @@ class DataSourceQueryBuilder:
|
|
|
398
431
|
|
|
399
432
|
def _negate_operator(
|
|
400
433
|
self,
|
|
401
|
-
operator: StringOperator
|
|
402
|
-
|
|
434
|
+
operator: StringOperator
|
|
435
|
+
| NumberOperator
|
|
436
|
+
| BooleanOperator
|
|
437
|
+
| DateOperator
|
|
438
|
+
| ArrayOperator,
|
|
439
|
+
) -> (
|
|
440
|
+
StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator
|
|
441
|
+
):
|
|
403
442
|
negation_map = {
|
|
404
443
|
StringOperator.EQUALS: StringOperator.DOES_NOT_EQUAL,
|
|
405
444
|
StringOperator.DOES_NOT_EQUAL: StringOperator.EQUALS,
|
|
@@ -436,13 +475,23 @@ class DataSourceQueryBuilder:
|
|
|
436
475
|
|
|
437
476
|
def _raise_operator_cannot_be_negated_error(
|
|
438
477
|
self,
|
|
439
|
-
operator: StringOperator
|
|
478
|
+
operator: StringOperator
|
|
479
|
+
| NumberOperator
|
|
480
|
+
| BooleanOperator
|
|
481
|
+
| DateOperator
|
|
482
|
+
| ArrayOperator,
|
|
440
483
|
) -> None:
|
|
441
|
-
raise ValueError(
|
|
484
|
+
raise ValueError(
|
|
485
|
+
f"Operator '{operator}' cannot be negated. This should not happen - please report this issue."
|
|
486
|
+
)
|
|
442
487
|
|
|
443
488
|
def _determine_field_type_from_operator(
|
|
444
489
|
self,
|
|
445
|
-
operator: StringOperator
|
|
490
|
+
operator: StringOperator
|
|
491
|
+
| NumberOperator
|
|
492
|
+
| BooleanOperator
|
|
493
|
+
| DateOperator
|
|
494
|
+
| ArrayOperator,
|
|
446
495
|
) -> FieldType:
|
|
447
496
|
if isinstance(operator, StringOperator):
|
|
448
497
|
return FieldType.STRING
|
|
@@ -14,7 +14,10 @@ from notionary.utils.mixins.logging import LoggingMixin
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class QueryResolver(LoggingMixin):
|
|
17
|
-
UUID_PATTERN = re.compile(
|
|
17
|
+
UUID_PATTERN = re.compile(
|
|
18
|
+
r"^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$",
|
|
19
|
+
re.IGNORECASE,
|
|
20
|
+
)
|
|
18
21
|
|
|
19
22
|
def __init__(
|
|
20
23
|
self,
|
|
@@ -24,7 +27,9 @@ class QueryResolver(LoggingMixin):
|
|
|
24
27
|
self._user_resolver = user_resolver or PersonNameIdResolver()
|
|
25
28
|
self._page_resolver = page_resolver or PageNameIdResolver()
|
|
26
29
|
|
|
27
|
-
async def resolve_params(
|
|
30
|
+
async def resolve_params(
|
|
31
|
+
self, params: DataSourceQueryParams
|
|
32
|
+
) -> DataSourceQueryParams:
|
|
28
33
|
if not params.filter:
|
|
29
34
|
return params
|
|
30
35
|
|
|
@@ -38,7 +43,9 @@ class QueryResolver(LoggingMixin):
|
|
|
38
43
|
return await self._resolve_compound_filter(filter)
|
|
39
44
|
return filter
|
|
40
45
|
|
|
41
|
-
async def _resolve_compound_filter(
|
|
46
|
+
async def _resolve_compound_filter(
|
|
47
|
+
self, compound: CompoundFilter
|
|
48
|
+
) -> CompoundFilter:
|
|
42
49
|
resolved_filters = []
|
|
43
50
|
for filter in compound.filters:
|
|
44
51
|
resolved = await self._resolve_filter(filter)
|
|
@@ -46,7 +53,9 @@ class QueryResolver(LoggingMixin):
|
|
|
46
53
|
|
|
47
54
|
return CompoundFilter(operator=compound.operator, filters=resolved_filters)
|
|
48
55
|
|
|
49
|
-
async def _resolve_property_filter(
|
|
56
|
+
async def _resolve_property_filter(
|
|
57
|
+
self, prop_filter: PropertyFilter
|
|
58
|
+
) -> PropertyFilter:
|
|
50
59
|
if not self._is_resolvable_property_type(prop_filter.property_type):
|
|
51
60
|
return prop_filter
|
|
52
61
|
|
|
@@ -56,7 +65,9 @@ class QueryResolver(LoggingMixin):
|
|
|
56
65
|
if self._is_uuid(prop_filter.value):
|
|
57
66
|
return prop_filter
|
|
58
67
|
|
|
59
|
-
resolved_value = await self._resolve_value(
|
|
68
|
+
resolved_value = await self._resolve_value(
|
|
69
|
+
prop_filter.value, prop_filter.property_type
|
|
70
|
+
)
|
|
60
71
|
|
|
61
72
|
return PropertyFilter(
|
|
62
73
|
property=prop_filter.property,
|
|
@@ -3,7 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from enum import StrEnum
|
|
4
4
|
from typing import Self
|
|
5
5
|
|
|
6
|
-
from pydantic import
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ValidationInfo,
|
|
9
|
+
field_validator,
|
|
10
|
+
model_serializer,
|
|
11
|
+
model_validator,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
from notionary.shared.properties.type import PropertyType
|
|
9
15
|
from notionary.shared.typings import JsonDict
|
|
@@ -93,7 +99,9 @@ class TimeUnit(StrEnum):
|
|
|
93
99
|
YEARS = "years"
|
|
94
100
|
|
|
95
101
|
|
|
96
|
-
type Operator =
|
|
102
|
+
type Operator = (
|
|
103
|
+
StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator
|
|
104
|
+
)
|
|
97
105
|
type FilterValue = str | int | float | bool | list[str | int | float]
|
|
98
106
|
|
|
99
107
|
|
|
@@ -156,7 +164,10 @@ class FilterCondition(BaseModel):
|
|
|
156
164
|
self._ensure_value_is_number()
|
|
157
165
|
elif self.field_type == FieldType.BOOLEAN:
|
|
158
166
|
self._ensure_value_is_boolean()
|
|
159
|
-
elif self.field_type in (
|
|
167
|
+
elif self.field_type in (
|
|
168
|
+
FieldType.DATE,
|
|
169
|
+
FieldType.DATETIME,
|
|
170
|
+
) or self.field_type in (
|
|
160
171
|
FieldType.ARRAY,
|
|
161
172
|
FieldType.RELATION,
|
|
162
173
|
FieldType.PEOPLE,
|
|
@@ -196,7 +207,9 @@ class FilterCondition(BaseModel):
|
|
|
196
207
|
operator_value = value if isinstance(value, str) else value.value
|
|
197
208
|
|
|
198
209
|
if not cls._is_operator_valid_for_field_type(operator_value, field_type):
|
|
199
|
-
raise ValueError(
|
|
210
|
+
raise ValueError(
|
|
211
|
+
f"Operator '{operator_value}' is not valid for field type '{field_type}'"
|
|
212
|
+
)
|
|
200
213
|
|
|
201
214
|
return value
|
|
202
215
|
|
|
@@ -234,7 +247,10 @@ class PropertyFilter(BaseModel):
|
|
|
234
247
|
if self.value is None:
|
|
235
248
|
return self
|
|
236
249
|
|
|
237
|
-
if self.property_type in (
|
|
250
|
+
if self.property_type in (
|
|
251
|
+
PropertyType.PEOPLE,
|
|
252
|
+
PropertyType.RELATION,
|
|
253
|
+
) and not isinstance(self.value, str):
|
|
238
254
|
raise ValueError(
|
|
239
255
|
f"Value for property type '{self.property_type.value}' must be a string, "
|
|
240
256
|
f"got {type(self.value).__name__}"
|
|
@@ -254,7 +270,9 @@ class PropertyFilter(BaseModel):
|
|
|
254
270
|
|
|
255
271
|
return {
|
|
256
272
|
"property": self.property,
|
|
257
|
-
property_type_str: {
|
|
273
|
+
property_type_str: {
|
|
274
|
+
operator_str: filter_value if filter_value is not None else True
|
|
275
|
+
},
|
|
258
276
|
}
|
|
259
277
|
|
|
260
278
|
|
|
@@ -39,7 +39,9 @@ class QueryValidator:
|
|
|
39
39
|
self, property_name: str, property_obj: DataSourceProperty, operator: Operator
|
|
40
40
|
) -> None:
|
|
41
41
|
if not self._is_operator_valid_for_property_type(property_obj.type, operator):
|
|
42
|
-
valid_operators = self._get_valid_operators_for_property_type(
|
|
42
|
+
valid_operators = self._get_valid_operators_for_property_type(
|
|
43
|
+
property_obj.type
|
|
44
|
+
)
|
|
43
45
|
raise InvalidOperatorForPropertyType(
|
|
44
46
|
property_name=property_name,
|
|
45
47
|
property_type=property_obj.type,
|
|
@@ -47,23 +49,33 @@ class QueryValidator:
|
|
|
47
49
|
valid_operators=valid_operators,
|
|
48
50
|
)
|
|
49
51
|
|
|
50
|
-
def _is_operator_valid_for_property_type(
|
|
52
|
+
def _is_operator_valid_for_property_type(
|
|
53
|
+
self, property_type: PropertyType, operator: Operator
|
|
54
|
+
) -> bool:
|
|
51
55
|
allowed_operator_types = self._PROPERTY_TYPE_OPERATORS.get(property_type, [])
|
|
52
|
-
valid_operator_values = self._get_operator_values_from_types(
|
|
56
|
+
valid_operator_values = self._get_operator_values_from_types(
|
|
57
|
+
allowed_operator_types
|
|
58
|
+
)
|
|
53
59
|
return operator.value in valid_operator_values
|
|
54
60
|
|
|
55
|
-
def _get_operator_values_from_types(
|
|
61
|
+
def _get_operator_values_from_types(
|
|
62
|
+
self, operator_types: list[type[Operator]]
|
|
63
|
+
) -> set[str]:
|
|
56
64
|
values: set[str] = set()
|
|
57
65
|
for operator_type in operator_types:
|
|
58
66
|
for operator in operator_type:
|
|
59
67
|
values.add(operator.value)
|
|
60
68
|
return values
|
|
61
69
|
|
|
62
|
-
def _get_valid_operators_for_property_type(
|
|
70
|
+
def _get_valid_operators_for_property_type(
|
|
71
|
+
self, property_type: PropertyType
|
|
72
|
+
) -> list[Operator]:
|
|
63
73
|
allowed_operator_types = self._PROPERTY_TYPE_OPERATORS.get(property_type, [])
|
|
64
74
|
return self._collect_all_operators_from_types(allowed_operator_types)
|
|
65
75
|
|
|
66
|
-
def _collect_all_operators_from_types(
|
|
76
|
+
def _collect_all_operators_from_types(
|
|
77
|
+
self, operator_types: list[type[Operator]]
|
|
78
|
+
) -> list[Operator]:
|
|
67
79
|
operators: list[Operator] = []
|
|
68
80
|
for operator_type in operator_types:
|
|
69
81
|
operators.extend(self._get_all_enum_values(operator_type))
|