notionary 0.3.1__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 +49 -1
- notionary/blocks/client.py +37 -11
- notionary/blocks/enums.py +0 -6
- 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 +33 -78
- notionary/comments/client.py +19 -6
- notionary/comments/factory.py +10 -3
- notionary/comments/schemas.py +10 -31
- notionary/comments/service.py +12 -4
- notionary/data_source/http/data_source_instance_client.py +59 -17
- notionary/data_source/properties/schemas.py +156 -115
- 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/schemas.py +2 -2
- notionary/data_source/service.py +103 -43
- notionary/database/client.py +27 -9
- notionary/database/database_metadata_update_client.py +12 -4
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +14 -9
- notionary/exceptions/__init__.py +20 -4
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +9 -5
- notionary/exceptions/data_source/builder.py +13 -7
- notionary/exceptions/data_source/properties.py +6 -4
- notionary/exceptions/file_upload.py +76 -0
- notionary/exceptions/properties.py +7 -5
- notionary/exceptions/search.py +10 -6
- notionary/file_upload/__init__.py +4 -0
- notionary/file_upload/client.py +128 -210
- notionary/file_upload/config/__init__.py +17 -0
- notionary/file_upload/config/config.py +39 -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 +58 -0
- notionary/file_upload/query/models.py +37 -0
- notionary/file_upload/schemas.py +80 -0
- notionary/file_upload/service.py +182 -291
- notionary/file_upload/validation/factory.py +66 -0
- notionary/file_upload/validation/impl/file_name_length.py +25 -0
- notionary/file_upload/validation/models.py +134 -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 +131 -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 +33 -30
- 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 +26 -8
- notionary/page/content/parser/parsers/audio.py +12 -32
- 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 -4
- notionary/page/content/parser/parsers/equation.py +8 -4
- notionary/page/content/parser/parsers/file.py +12 -34
- notionary/page/content/parser/parsers/file_like_block.py +109 -0
- notionary/page/content/parser/parsers/heading.py +31 -10
- notionary/page/content/parser/parsers/image.py +12 -34
- 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 +12 -34
- 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 +12 -34
- 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 +20 -23
- 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 +20 -23
- notionary/page/content/renderer/renderers/file_like_block.py +47 -0
- notionary/page/content/renderer/renderers/heading.py +22 -8
- notionary/page/content/renderer/renderers/image.py +20 -23
- 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 +20 -23
- 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 +20 -23
- 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 +46 -16
- notionary/page/properties/factory.py +6 -2
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +111 -37
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +21 -7
- notionary/shared/entity/client.py +6 -2
- notionary/shared/entity/dto_parsers.py +4 -37
- notionary/shared/entity/entity_metadata_update_client.py +25 -5
- notionary/shared/entity/schemas.py +6 -6
- notionary/shared/entity/service.py +89 -35
- notionary/shared/models/file.py +36 -6
- notionary/shared/models/icon.py +5 -12
- notionary/user/base.py +6 -2
- notionary/user/bot.py +22 -14
- 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 +13 -9
- notionary/utils/fuzzy.py +6 -2
- notionary/utils/mixins/logging.py +3 -1
- notionary/utils/pagination.py +14 -4
- notionary/workspace/__init__.py +6 -2
- notionary/workspace/query/__init__.py +2 -1
- notionary/workspace/query/service.py +42 -13
- notionary/workspace/service.py +74 -46
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
- notionary-0.4.1.dist-info/RECORD +236 -0
- notionary/file_upload/models.py +0 -69
- notionary/page/blocks/client.py +0 -1
- notionary/page/content/syntax/__init__.py +0 -4
- notionary/page/content/syntax/models.py +0 -66
- notionary/page/content/syntax/registry.py +0 -393
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- notionary-0.3.1.dist-info/RECORD +0 -211
- /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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))
|
|
@@ -13,36 +13,52 @@ class DatabasePropertyTypeDescriptorRegistry:
|
|
|
13
13
|
def __init__(self):
|
|
14
14
|
self._DESCRIPTORS = {
|
|
15
15
|
PropertyType.TITLE: PropertyTypeDescriptor(
|
|
16
|
-
display_name="Title",
|
|
16
|
+
display_name="Title",
|
|
17
|
+
description="Required field for the main heading of the entry",
|
|
17
18
|
),
|
|
18
19
|
PropertyType.RICH_TEXT: PropertyTypeDescriptor(
|
|
19
|
-
display_name="Rich Text",
|
|
20
|
+
display_name="Rich Text",
|
|
21
|
+
description="Free-form text field for additional information",
|
|
22
|
+
),
|
|
23
|
+
PropertyType.NUMBER: PropertyTypeDescriptor(
|
|
24
|
+
display_name="Number", description="Numeric value field"
|
|
20
25
|
),
|
|
21
|
-
PropertyType.NUMBER: PropertyTypeDescriptor(display_name="Number", description="Numeric value field"),
|
|
22
26
|
PropertyType.CHECKBOX: PropertyTypeDescriptor(
|
|
23
27
|
display_name="Checkbox", description="Boolean value (true/false)"
|
|
24
28
|
),
|
|
25
|
-
PropertyType.DATE: PropertyTypeDescriptor(
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
PropertyType.DATE: PropertyTypeDescriptor(
|
|
30
|
+
display_name="Date", description="Date or date range field"
|
|
31
|
+
),
|
|
32
|
+
PropertyType.URL: PropertyTypeDescriptor(
|
|
33
|
+
display_name="URL", description="Web address field"
|
|
34
|
+
),
|
|
35
|
+
PropertyType.EMAIL: PropertyTypeDescriptor(
|
|
36
|
+
display_name="Email", description="Email address field"
|
|
37
|
+
),
|
|
28
38
|
PropertyType.PHONE_NUMBER: PropertyTypeDescriptor(
|
|
29
39
|
display_name="Phone Number", description="Phone number field"
|
|
30
40
|
),
|
|
31
41
|
PropertyType.FILES: PropertyTypeDescriptor(
|
|
32
42
|
display_name="Files & Media", description="Upload or link to files"
|
|
33
43
|
),
|
|
34
|
-
PropertyType.PEOPLE: PropertyTypeDescriptor(
|
|
44
|
+
PropertyType.PEOPLE: PropertyTypeDescriptor(
|
|
45
|
+
display_name="People", description="Reference to Notion users"
|
|
46
|
+
),
|
|
35
47
|
PropertyType.SELECT: PropertyTypeDescriptor(
|
|
36
|
-
display_name="Single Select",
|
|
48
|
+
display_name="Single Select",
|
|
49
|
+
description="Choose one option from available choices",
|
|
37
50
|
),
|
|
38
51
|
PropertyType.MULTI_SELECT: PropertyTypeDescriptor(
|
|
39
|
-
display_name="Multi Select",
|
|
52
|
+
display_name="Multi Select",
|
|
53
|
+
description="Choose multiple options from available choices",
|
|
40
54
|
),
|
|
41
55
|
PropertyType.STATUS: PropertyTypeDescriptor(
|
|
42
|
-
display_name="Status",
|
|
56
|
+
display_name="Status",
|
|
57
|
+
description="Track status with predefined options",
|
|
43
58
|
),
|
|
44
59
|
PropertyType.RELATION: PropertyTypeDescriptor(
|
|
45
|
-
display_name="Relation",
|
|
60
|
+
display_name="Relation",
|
|
61
|
+
description="Link to entries in another database",
|
|
46
62
|
),
|
|
47
63
|
PropertyType.CREATED_TIME: PropertyTypeDescriptor(
|
|
48
64
|
display_name="Created Time",
|
|
@@ -97,7 +113,10 @@ class DatabasePropertyTypeDescriptorRegistry:
|
|
|
97
113
|
def get_descriptor(self, property_type: PropertyType) -> PropertyTypeDescriptor:
|
|
98
114
|
return self._DESCRIPTORS.get(
|
|
99
115
|
property_type,
|
|
100
|
-
PropertyTypeDescriptor(
|
|
116
|
+
PropertyTypeDescriptor(
|
|
117
|
+
display_name=self._format_unknown_type_name(property_type),
|
|
118
|
+
description="",
|
|
119
|
+
),
|
|
101
120
|
)
|
|
102
121
|
|
|
103
122
|
def _format_unknown_type_name(self, property_type: PropertyType) -> str:
|
|
@@ -8,7 +8,10 @@ from notionary.data_source.properties.schemas import (
|
|
|
8
8
|
DataSourceSelectProperty,
|
|
9
9
|
DataSourceStatusProperty,
|
|
10
10
|
)
|
|
11
|
-
from notionary.data_source.schema.registry import
|
|
11
|
+
from notionary.data_source.schema.registry import (
|
|
12
|
+
DatabasePropertyTypeDescriptorRegistry,
|
|
13
|
+
PropertyTypeDescriptor,
|
|
14
|
+
)
|
|
12
15
|
from notionary.shared.properties.type import PropertyType
|
|
13
16
|
|
|
14
17
|
|
|
@@ -17,12 +20,16 @@ class PropertyFormatter:
|
|
|
17
20
|
|
|
18
21
|
def __init__(
|
|
19
22
|
self,
|
|
20
|
-
relation_options_fetcher: Callable[
|
|
23
|
+
relation_options_fetcher: Callable[
|
|
24
|
+
[DataSourceRelationProperty], Awaitable[list[str]]
|
|
25
|
+
],
|
|
21
26
|
type_descriptor_registry: DatabasePropertyTypeDescriptorRegistry | None = None,
|
|
22
27
|
data_source_resolver: DataSourceNameIdResolver | None = None,
|
|
23
28
|
) -> None:
|
|
24
29
|
self._relation_options_fetcher = relation_options_fetcher
|
|
25
|
-
self._type_descriptor_registry =
|
|
30
|
+
self._type_descriptor_registry = (
|
|
31
|
+
type_descriptor_registry or DatabasePropertyTypeDescriptorRegistry()
|
|
32
|
+
)
|
|
26
33
|
self._data_source_resolver = data_source_resolver or DataSourceNameIdResolver()
|
|
27
34
|
|
|
28
35
|
async def format_property(self, prop: DataSourceProperty) -> list[str]:
|
|
@@ -32,22 +39,35 @@ class PropertyFormatter:
|
|
|
32
39
|
return [*specific_details, *self._format_custom_description(prop)]
|
|
33
40
|
|
|
34
41
|
descriptor = self._type_descriptor_registry.get_descriptor(prop.type)
|
|
35
|
-
return [
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
return [
|
|
43
|
+
*self._format_property_description(descriptor),
|
|
44
|
+
*self._format_custom_description(prop),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def _format_property_description(
|
|
48
|
+
self, descriptor: PropertyTypeDescriptor
|
|
49
|
+
) -> list[str]:
|
|
38
50
|
if not descriptor.description:
|
|
39
51
|
return []
|
|
40
52
|
return [f"{self.INDENTATION}{descriptor.description}"]
|
|
41
53
|
|
|
42
|
-
async def _format_property_specific_details(
|
|
54
|
+
async def _format_property_specific_details(
|
|
55
|
+
self, prop: DataSourceProperty
|
|
56
|
+
) -> list[str]:
|
|
43
57
|
if isinstance(prop, DataSourceSelectProperty):
|
|
44
|
-
return self._format_available_options(
|
|
58
|
+
return self._format_available_options(
|
|
59
|
+
"Choose one option from", prop.option_names
|
|
60
|
+
)
|
|
45
61
|
|
|
46
62
|
if isinstance(prop, DataSourceMultiSelectProperty):
|
|
47
|
-
return self._format_available_options(
|
|
63
|
+
return self._format_available_options(
|
|
64
|
+
"Choose multiple options from", prop.option_names
|
|
65
|
+
)
|
|
48
66
|
|
|
49
67
|
if isinstance(prop, DataSourceStatusProperty):
|
|
50
|
-
return self._format_available_options(
|
|
68
|
+
return self._format_available_options(
|
|
69
|
+
"Available statuses", prop.option_names
|
|
70
|
+
)
|
|
51
71
|
|
|
52
72
|
if isinstance(prop, DataSourceRelationProperty):
|
|
53
73
|
return await self._format_relation_details(prop)
|
|
@@ -63,11 +83,15 @@ class PropertyFormatter:
|
|
|
63
83
|
options_text = ", ".join(options)
|
|
64
84
|
return [f"{self.INDENTATION}{label}: {options_text}"]
|
|
65
85
|
|
|
66
|
-
async def _format_relation_details(
|
|
86
|
+
async def _format_relation_details(
|
|
87
|
+
self, prop: DataSourceRelationProperty
|
|
88
|
+
) -> list[str]:
|
|
67
89
|
if not prop.related_data_source_id:
|
|
68
90
|
return []
|
|
69
91
|
|
|
70
|
-
data_source_name = await self._data_source_resolver.resolve_id_to_name(
|
|
92
|
+
data_source_name = await self._data_source_resolver.resolve_id_to_name(
|
|
93
|
+
prop.related_data_source_id
|
|
94
|
+
)
|
|
71
95
|
data_source_display = data_source_name or prop.related_data_source_id
|
|
72
96
|
lines = [f"{self.INDENTATION}Links to datasource: {data_source_display}"]
|
|
73
97
|
|
|
@@ -78,7 +102,9 @@ class PropertyFormatter:
|
|
|
78
102
|
|
|
79
103
|
return lines
|
|
80
104
|
|
|
81
|
-
async def _fetch_relation_entries(
|
|
105
|
+
async def _fetch_relation_entries(
|
|
106
|
+
self, prop: DataSourceRelationProperty
|
|
107
|
+
) -> list[str] | None:
|
|
82
108
|
try:
|
|
83
109
|
return await self._relation_options_fetcher(prop)
|
|
84
110
|
except Exception:
|
|
@@ -88,14 +114,22 @@ class PropertyFormatter:
|
|
|
88
114
|
class DataSourcePropertySchemaFormatter:
|
|
89
115
|
def __init__(
|
|
90
116
|
self,
|
|
91
|
-
relation_options_fetcher: Callable[
|
|
117
|
+
relation_options_fetcher: Callable[
|
|
118
|
+
[DataSourceRelationProperty], Awaitable[list[str]]
|
|
119
|
+
]
|
|
120
|
+
| None = None,
|
|
92
121
|
data_source_resolver: DataSourceNameIdResolver | None = None,
|
|
93
122
|
) -> None:
|
|
94
123
|
self._property_formatter = PropertyFormatter(
|
|
95
124
|
relation_options_fetcher, data_source_resolver=data_source_resolver
|
|
96
125
|
)
|
|
97
126
|
|
|
98
|
-
async def format(
|
|
127
|
+
async def format(
|
|
128
|
+
self,
|
|
129
|
+
title: str,
|
|
130
|
+
description: str | None,
|
|
131
|
+
properties: dict[str, DataSourceProperty],
|
|
132
|
+
) -> str:
|
|
99
133
|
lines = self._format_header(title, description)
|
|
100
134
|
lines.append("Properties:")
|
|
101
135
|
lines.append("")
|
|
@@ -112,7 +146,9 @@ class DataSourcePropertySchemaFormatter:
|
|
|
112
146
|
|
|
113
147
|
return lines
|
|
114
148
|
|
|
115
|
-
async def _format_properties(
|
|
149
|
+
async def _format_properties(
|
|
150
|
+
self, properties: dict[str, DataSourceProperty]
|
|
151
|
+
) -> list[str]:
|
|
116
152
|
lines = []
|
|
117
153
|
sorted_properties = self._sort_with_title_first(properties)
|
|
118
154
|
|
|
@@ -121,14 +157,24 @@ class DataSourcePropertySchemaFormatter:
|
|
|
121
157
|
|
|
122
158
|
return lines
|
|
123
159
|
|
|
124
|
-
def _sort_with_title_first(
|
|
125
|
-
|
|
160
|
+
def _sort_with_title_first(
|
|
161
|
+
self, properties: dict[str, DataSourceProperty]
|
|
162
|
+
) -> list[tuple[str, DataSourceProperty]]:
|
|
163
|
+
return sorted(
|
|
164
|
+
properties.items(),
|
|
165
|
+
key=lambda item: (self._is_not_title_property(item[1]), item[0]),
|
|
166
|
+
)
|
|
126
167
|
|
|
127
168
|
def _is_not_title_property(self, prop: DataSourceProperty) -> bool:
|
|
128
169
|
return prop.type != PropertyType.TITLE
|
|
129
170
|
|
|
130
|
-
async def _format_single_property(
|
|
131
|
-
|
|
171
|
+
async def _format_single_property(
|
|
172
|
+
self, index: int, name: str, prop: DataSourceProperty
|
|
173
|
+
) -> list[str]:
|
|
174
|
+
lines = [
|
|
175
|
+
f"{index}. - Property Name: '{name}'",
|
|
176
|
+
f" - Property Type: '{prop.type.value}'",
|
|
177
|
+
]
|
|
132
178
|
|
|
133
179
|
lines.extend(await self._property_formatter.format_property(prop))
|
|
134
180
|
lines.append("")
|
notionary/data_source/schemas.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
|
|
3
3
|
from notionary.blocks.rich_text.models import RichText
|
|
4
|
-
from notionary.data_source.properties.schemas import
|
|
4
|
+
from notionary.data_source.properties.schemas import AnyDataSourceProperty
|
|
5
5
|
from notionary.page.schemas import NotionPageDto
|
|
6
6
|
from notionary.shared.entity.schemas import EntityResponseDto, NotionEntityUpdateDto
|
|
7
7
|
from notionary.shared.models.parent import Parent
|
|
@@ -24,4 +24,4 @@ class DataSourceDto(EntityResponseDto):
|
|
|
24
24
|
title: list[RichText]
|
|
25
25
|
description: list[RichText]
|
|
26
26
|
archived: bool
|
|
27
|
-
properties: dict[str,
|
|
27
|
+
properties: dict[str, AnyDataSourceProperty]
|
notionary/data_source/service.py
CHANGED
|
@@ -4,9 +4,13 @@ import asyncio
|
|
|
4
4
|
from collections.abc import AsyncIterator, Callable
|
|
5
5
|
from typing import TYPE_CHECKING, Self
|
|
6
6
|
|
|
7
|
-
from notionary.blocks.rich_text.rich_text_markdown_converter import
|
|
7
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import (
|
|
8
|
+
RichTextToMarkdownConverter,
|
|
9
|
+
)
|
|
8
10
|
from notionary.data_source.http.client import DataSourceClient
|
|
9
|
-
from notionary.data_source.http.data_source_instance_client import
|
|
11
|
+
from notionary.data_source.http.data_source_instance_client import (
|
|
12
|
+
DataSourceInstanceClient,
|
|
13
|
+
)
|
|
10
14
|
from notionary.data_source.properties.schemas import (
|
|
11
15
|
DataSourceMultiSelectProperty,
|
|
12
16
|
DataSourceProperty,
|
|
@@ -16,18 +20,29 @@ from notionary.data_source.properties.schemas import (
|
|
|
16
20
|
DataSourceSelectProperty,
|
|
17
21
|
DataSourceStatusProperty,
|
|
18
22
|
)
|
|
19
|
-
from notionary.data_source.query import
|
|
23
|
+
from notionary.data_source.query import (
|
|
24
|
+
DataSourceQueryBuilder,
|
|
25
|
+
DataSourceQueryParams,
|
|
26
|
+
QueryResolver,
|
|
27
|
+
)
|
|
20
28
|
from notionary.data_source.schema.service import DataSourcePropertySchemaFormatter
|
|
21
29
|
from notionary.data_source.schemas import DataSourceDto
|
|
22
|
-
from notionary.exceptions.data_source.properties import
|
|
23
|
-
|
|
30
|
+
from notionary.exceptions.data_source.properties import (
|
|
31
|
+
DataSourcePropertyNotFound,
|
|
32
|
+
DataSourcePropertyTypeError,
|
|
33
|
+
)
|
|
34
|
+
from notionary.file_upload.service import NotionFileUpload
|
|
35
|
+
from notionary.page.properties.schemas import PageTitleProperty
|
|
24
36
|
from notionary.page.schemas import NotionPageDto
|
|
25
37
|
from notionary.shared.entity.dto_parsers import (
|
|
26
38
|
extract_description,
|
|
27
39
|
extract_title,
|
|
28
40
|
)
|
|
29
|
-
from notionary.shared.entity.entity_metadata_update_client import
|
|
41
|
+
from notionary.shared.entity.entity_metadata_update_client import (
|
|
42
|
+
EntityMetadataUpdateClient,
|
|
43
|
+
)
|
|
30
44
|
from notionary.shared.entity.service import Entity
|
|
45
|
+
from notionary.user.service import UserService
|
|
31
46
|
from notionary.workspace.query.service import WorkspaceQueryService
|
|
32
47
|
|
|
33
48
|
if TYPE_CHECKING:
|
|
@@ -43,8 +58,12 @@ class NotionDataSource(Entity):
|
|
|
43
58
|
properties: dict[str, DataSourceProperty],
|
|
44
59
|
data_source_instance_client: DataSourceInstanceClient,
|
|
45
60
|
query_resolver: QueryResolver | None = None,
|
|
61
|
+
user_service: UserService | None = None,
|
|
62
|
+
file_upload_service: NotionFileUpload | None = None,
|
|
46
63
|
) -> None:
|
|
47
|
-
super().__init__(
|
|
64
|
+
super().__init__(
|
|
65
|
+
dto=dto, user_service=user_service, file_upload_service=file_upload_service
|
|
66
|
+
)
|
|
48
67
|
|
|
49
68
|
self._parent_database: NotionDatabase | None = None
|
|
50
69
|
self._title = title
|
|
@@ -116,6 +135,10 @@ class NotionDataSource(Entity):
|
|
|
116
135
|
def properties(self) -> dict[str, DataSourceProperty]:
|
|
117
136
|
return self._properties
|
|
118
137
|
|
|
138
|
+
@property
|
|
139
|
+
def data_source_query_builder(self) -> DataSourceQueryBuilder:
|
|
140
|
+
return DataSourceQueryBuilder(properties=self._properties)
|
|
141
|
+
|
|
119
142
|
async def create_blank_page(self, title: str | None = None) -> NotionPage:
|
|
120
143
|
return await self._data_source_client.create_blank_page(title=title)
|
|
121
144
|
|
|
@@ -138,7 +161,9 @@ class NotionDataSource(Entity):
|
|
|
138
161
|
self._archived = False
|
|
139
162
|
|
|
140
163
|
async def update_description(self, description: str) -> None:
|
|
141
|
-
self._description = await self._data_source_client.update_description(
|
|
164
|
+
self._description = await self._data_source_client.update_description(
|
|
165
|
+
description
|
|
166
|
+
)
|
|
142
167
|
|
|
143
168
|
async def get_options_for_property_by_name(self, property_name: str) -> list[str]:
|
|
144
169
|
prop = self._properties.get(property_name)
|
|
@@ -161,22 +186,36 @@ class NotionDataSource(Entity):
|
|
|
161
186
|
return []
|
|
162
187
|
|
|
163
188
|
def get_select_options_by_property_name(self, property_name: str) -> list[str]:
|
|
164
|
-
select_prop = self._get_typed_property_or_raise(
|
|
189
|
+
select_prop = self._get_typed_property_or_raise(
|
|
190
|
+
property_name, DataSourceSelectProperty
|
|
191
|
+
)
|
|
165
192
|
return select_prop.option_names
|
|
166
193
|
|
|
167
|
-
def get_multi_select_options_by_property_name(
|
|
168
|
-
|
|
194
|
+
def get_multi_select_options_by_property_name(
|
|
195
|
+
self, property_name: str
|
|
196
|
+
) -> list[DataSourcePropertyOption]:
|
|
197
|
+
multi_select_prop = self._get_typed_property_or_raise(
|
|
198
|
+
property_name, DataSourceMultiSelectProperty
|
|
199
|
+
)
|
|
169
200
|
return multi_select_prop.option_names
|
|
170
201
|
|
|
171
202
|
def get_status_options_by_property_name(self, property_name: str) -> list[str]:
|
|
172
|
-
status_prop = self._get_typed_property_or_raise(
|
|
203
|
+
status_prop = self._get_typed_property_or_raise(
|
|
204
|
+
property_name, DataSourceStatusProperty
|
|
205
|
+
)
|
|
173
206
|
return status_prop.option_names
|
|
174
207
|
|
|
175
|
-
async def get_relation_options_by_property_name(
|
|
176
|
-
|
|
208
|
+
async def get_relation_options_by_property_name(
|
|
209
|
+
self, property_name: str
|
|
210
|
+
) -> list[str]:
|
|
211
|
+
relation_prop = self._get_typed_property_or_raise(
|
|
212
|
+
property_name, DataSourceRelationProperty
|
|
213
|
+
)
|
|
177
214
|
return await self._get_relation_options(relation_prop)
|
|
178
215
|
|
|
179
|
-
async def _get_relation_options(
|
|
216
|
+
async def _get_relation_options(
|
|
217
|
+
self, relation_prop: DataSourceRelationProperty
|
|
218
|
+
) -> list[str]:
|
|
180
219
|
related_data_source_id = relation_prop.related_data_source_id
|
|
181
220
|
if not related_data_source_id:
|
|
182
221
|
return []
|
|
@@ -197,7 +236,11 @@ class NotionDataSource(Entity):
|
|
|
197
236
|
return None
|
|
198
237
|
|
|
199
238
|
title_property = next(
|
|
200
|
-
(
|
|
239
|
+
(
|
|
240
|
+
prop
|
|
241
|
+
for prop in page.properties.values()
|
|
242
|
+
if isinstance(prop, PageTitleProperty)
|
|
243
|
+
),
|
|
201
244
|
None,
|
|
202
245
|
)
|
|
203
246
|
|
|
@@ -206,7 +249,9 @@ class NotionDataSource(Entity):
|
|
|
206
249
|
|
|
207
250
|
return "".join(item.plain_text for item in title_property.title)
|
|
208
251
|
|
|
209
|
-
def _get_typed_property_or_raise(
|
|
252
|
+
def _get_typed_property_or_raise(
|
|
253
|
+
self, name: str, property_type: type[DataSourcePropertyT]
|
|
254
|
+
) -> DataSourcePropertyT:
|
|
210
255
|
prop = self._properties.get(name)
|
|
211
256
|
|
|
212
257
|
if prop is None:
|
|
@@ -217,52 +262,61 @@ class NotionDataSource(Entity):
|
|
|
217
262
|
|
|
218
263
|
if not isinstance(prop, property_type):
|
|
219
264
|
raise DataSourcePropertyTypeError(
|
|
220
|
-
property_name=name,
|
|
265
|
+
property_name=name,
|
|
266
|
+
expected_type=property_type.__name__,
|
|
267
|
+
actual_type=type(prop).__name__,
|
|
221
268
|
)
|
|
222
269
|
|
|
223
270
|
return prop
|
|
224
271
|
|
|
225
|
-
def
|
|
272
|
+
def get_query_builder(self) -> DataSourceQueryBuilder:
|
|
226
273
|
return DataSourceQueryBuilder(properties=self._properties)
|
|
227
274
|
|
|
228
|
-
async def query_pages(
|
|
229
|
-
self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
230
|
-
) -> list[NotionPage]:
|
|
231
|
-
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
232
|
-
configured_builder = filter_fn(builder)
|
|
233
|
-
query_params = configured_builder.build()
|
|
234
|
-
|
|
235
|
-
return await self.get_pages(query_params)
|
|
236
|
-
|
|
237
|
-
async def query_pages_stream(
|
|
238
|
-
self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
239
|
-
) -> AsyncIterator[NotionPage]:
|
|
240
|
-
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
241
|
-
configured_builder = filter_fn(builder)
|
|
242
|
-
query_params = configured_builder.build()
|
|
243
|
-
|
|
244
|
-
async for page in self.get_pages_stream(query_params):
|
|
245
|
-
yield page
|
|
246
|
-
|
|
247
275
|
async def get_pages(
|
|
248
276
|
self,
|
|
277
|
+
*,
|
|
278
|
+
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
279
|
+
| None = None,
|
|
249
280
|
query_params: DataSourceQueryParams | None = None,
|
|
250
281
|
) -> list[NotionPage]:
|
|
251
282
|
from notionary import NotionPage
|
|
252
283
|
|
|
284
|
+
if filter_fn is not None and query_params is not None:
|
|
285
|
+
raise ValueError("Use either filter_fn OR query_params, not both")
|
|
286
|
+
|
|
287
|
+
if filter_fn is not None:
|
|
288
|
+
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
289
|
+
configured_builder = filter_fn(builder)
|
|
290
|
+
query_params = configured_builder.build()
|
|
291
|
+
|
|
253
292
|
resolved_params = await self._resolve_query_params_if_needed(query_params)
|
|
254
|
-
query_response = await self._data_source_client.query(
|
|
293
|
+
query_response = await self._data_source_client.query(
|
|
294
|
+
query_params=resolved_params
|
|
295
|
+
)
|
|
255
296
|
return [await NotionPage.from_id(page.id) for page in query_response.results]
|
|
256
297
|
|
|
257
|
-
async def
|
|
298
|
+
async def iter_pages(
|
|
258
299
|
self,
|
|
300
|
+
*,
|
|
301
|
+
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
302
|
+
| None = None,
|
|
259
303
|
query_params: DataSourceQueryParams | None = None,
|
|
260
304
|
) -> AsyncIterator[NotionPage]:
|
|
261
305
|
from notionary import NotionPage
|
|
262
306
|
|
|
307
|
+
if filter_fn is not None and query_params is not None:
|
|
308
|
+
raise ValueError("Use either filter_fn OR query_params, not both")
|
|
309
|
+
|
|
310
|
+
if filter_fn is not None:
|
|
311
|
+
builder = DataSourceQueryBuilder(properties=self._properties)
|
|
312
|
+
configured_builder = filter_fn(builder)
|
|
313
|
+
query_params = configured_builder.build()
|
|
314
|
+
|
|
263
315
|
resolved_params = await self._resolve_query_params_if_needed(query_params)
|
|
264
316
|
|
|
265
|
-
async for page in self._data_source_client.query_stream(
|
|
317
|
+
async for page in self._data_source_client.query_stream(
|
|
318
|
+
query_params=resolved_params
|
|
319
|
+
):
|
|
266
320
|
yield await NotionPage.from_id(page.id)
|
|
267
321
|
|
|
268
322
|
async def _resolve_query_params_if_needed(
|
|
@@ -275,5 +329,11 @@ class NotionDataSource(Entity):
|
|
|
275
329
|
return await self.query_resolver.resolve_params(query_params)
|
|
276
330
|
|
|
277
331
|
async def get_schema_description(self) -> str:
|
|
278
|
-
formatter = DataSourcePropertySchemaFormatter(
|
|
279
|
-
|
|
332
|
+
formatter = DataSourcePropertySchemaFormatter(
|
|
333
|
+
relation_options_fetcher=self._get_relation_options
|
|
334
|
+
)
|
|
335
|
+
return await formatter.format(
|
|
336
|
+
title=self._title,
|
|
337
|
+
description=self._description,
|
|
338
|
+
properties=self._properties,
|
|
339
|
+
)
|