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
|
@@ -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/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,10 +20,17 @@ 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
|
|
30
|
+
from notionary.exceptions.data_source.properties import (
|
|
31
|
+
DataSourcePropertyNotFound,
|
|
32
|
+
DataSourcePropertyTypeError,
|
|
33
|
+
)
|
|
23
34
|
from notionary.file_upload.service import NotionFileUpload
|
|
24
35
|
from notionary.page.properties.schemas import PageTitleProperty
|
|
25
36
|
from notionary.page.schemas import NotionPageDto
|
|
@@ -27,7 +38,9 @@ from notionary.shared.entity.dto_parsers import (
|
|
|
27
38
|
extract_description,
|
|
28
39
|
extract_title,
|
|
29
40
|
)
|
|
30
|
-
from notionary.shared.entity.entity_metadata_update_client import
|
|
41
|
+
from notionary.shared.entity.entity_metadata_update_client import (
|
|
42
|
+
EntityMetadataUpdateClient,
|
|
43
|
+
)
|
|
31
44
|
from notionary.shared.entity.service import Entity
|
|
32
45
|
from notionary.user.service import UserService
|
|
33
46
|
from notionary.workspace.query.service import WorkspaceQueryService
|
|
@@ -48,7 +61,9 @@ class NotionDataSource(Entity):
|
|
|
48
61
|
user_service: UserService | None = None,
|
|
49
62
|
file_upload_service: NotionFileUpload | None = None,
|
|
50
63
|
) -> None:
|
|
51
|
-
super().__init__(
|
|
64
|
+
super().__init__(
|
|
65
|
+
dto=dto, user_service=user_service, file_upload_service=file_upload_service
|
|
66
|
+
)
|
|
52
67
|
|
|
53
68
|
self._parent_database: NotionDatabase | None = None
|
|
54
69
|
self._title = title
|
|
@@ -146,7 +161,9 @@ class NotionDataSource(Entity):
|
|
|
146
161
|
self._archived = False
|
|
147
162
|
|
|
148
163
|
async def update_description(self, description: str) -> None:
|
|
149
|
-
self._description = await self._data_source_client.update_description(
|
|
164
|
+
self._description = await self._data_source_client.update_description(
|
|
165
|
+
description
|
|
166
|
+
)
|
|
150
167
|
|
|
151
168
|
async def get_options_for_property_by_name(self, property_name: str) -> list[str]:
|
|
152
169
|
prop = self._properties.get(property_name)
|
|
@@ -169,22 +186,36 @@ class NotionDataSource(Entity):
|
|
|
169
186
|
return []
|
|
170
187
|
|
|
171
188
|
def get_select_options_by_property_name(self, property_name: str) -> list[str]:
|
|
172
|
-
select_prop = self._get_typed_property_or_raise(
|
|
189
|
+
select_prop = self._get_typed_property_or_raise(
|
|
190
|
+
property_name, DataSourceSelectProperty
|
|
191
|
+
)
|
|
173
192
|
return select_prop.option_names
|
|
174
193
|
|
|
175
|
-
def get_multi_select_options_by_property_name(
|
|
176
|
-
|
|
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
|
+
)
|
|
177
200
|
return multi_select_prop.option_names
|
|
178
201
|
|
|
179
202
|
def get_status_options_by_property_name(self, property_name: str) -> list[str]:
|
|
180
|
-
status_prop = self._get_typed_property_or_raise(
|
|
203
|
+
status_prop = self._get_typed_property_or_raise(
|
|
204
|
+
property_name, DataSourceStatusProperty
|
|
205
|
+
)
|
|
181
206
|
return status_prop.option_names
|
|
182
207
|
|
|
183
|
-
async def get_relation_options_by_property_name(
|
|
184
|
-
|
|
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
|
+
)
|
|
185
214
|
return await self._get_relation_options(relation_prop)
|
|
186
215
|
|
|
187
|
-
async def _get_relation_options(
|
|
216
|
+
async def _get_relation_options(
|
|
217
|
+
self, relation_prop: DataSourceRelationProperty
|
|
218
|
+
) -> list[str]:
|
|
188
219
|
related_data_source_id = relation_prop.related_data_source_id
|
|
189
220
|
if not related_data_source_id:
|
|
190
221
|
return []
|
|
@@ -205,7 +236,11 @@ class NotionDataSource(Entity):
|
|
|
205
236
|
return None
|
|
206
237
|
|
|
207
238
|
title_property = next(
|
|
208
|
-
(
|
|
239
|
+
(
|
|
240
|
+
prop
|
|
241
|
+
for prop in page.properties.values()
|
|
242
|
+
if isinstance(prop, PageTitleProperty)
|
|
243
|
+
),
|
|
209
244
|
None,
|
|
210
245
|
)
|
|
211
246
|
|
|
@@ -214,7 +249,9 @@ class NotionDataSource(Entity):
|
|
|
214
249
|
|
|
215
250
|
return "".join(item.plain_text for item in title_property.title)
|
|
216
251
|
|
|
217
|
-
def _get_typed_property_or_raise(
|
|
252
|
+
def _get_typed_property_or_raise(
|
|
253
|
+
self, name: str, property_type: type[DataSourcePropertyT]
|
|
254
|
+
) -> DataSourcePropertyT:
|
|
218
255
|
prop = self._properties.get(name)
|
|
219
256
|
|
|
220
257
|
if prop is None:
|
|
@@ -225,7 +262,9 @@ class NotionDataSource(Entity):
|
|
|
225
262
|
|
|
226
263
|
if not isinstance(prop, property_type):
|
|
227
264
|
raise DataSourcePropertyTypeError(
|
|
228
|
-
property_name=name,
|
|
265
|
+
property_name=name,
|
|
266
|
+
expected_type=property_type.__name__,
|
|
267
|
+
actual_type=type(prop).__name__,
|
|
229
268
|
)
|
|
230
269
|
|
|
231
270
|
return prop
|
|
@@ -236,7 +275,8 @@ class NotionDataSource(Entity):
|
|
|
236
275
|
async def get_pages(
|
|
237
276
|
self,
|
|
238
277
|
*,
|
|
239
|
-
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
278
|
+
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
279
|
+
| None = None,
|
|
240
280
|
query_params: DataSourceQueryParams | None = None,
|
|
241
281
|
) -> list[NotionPage]:
|
|
242
282
|
from notionary import NotionPage
|
|
@@ -250,13 +290,16 @@ class NotionDataSource(Entity):
|
|
|
250
290
|
query_params = configured_builder.build()
|
|
251
291
|
|
|
252
292
|
resolved_params = await self._resolve_query_params_if_needed(query_params)
|
|
253
|
-
query_response = await self._data_source_client.query(
|
|
293
|
+
query_response = await self._data_source_client.query(
|
|
294
|
+
query_params=resolved_params
|
|
295
|
+
)
|
|
254
296
|
return [await NotionPage.from_id(page.id) for page in query_response.results]
|
|
255
297
|
|
|
256
298
|
async def iter_pages(
|
|
257
299
|
self,
|
|
258
300
|
*,
|
|
259
|
-
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
301
|
+
filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
|
|
302
|
+
| None = None,
|
|
260
303
|
query_params: DataSourceQueryParams | None = None,
|
|
261
304
|
) -> AsyncIterator[NotionPage]:
|
|
262
305
|
from notionary import NotionPage
|
|
@@ -271,7 +314,9 @@ class NotionDataSource(Entity):
|
|
|
271
314
|
|
|
272
315
|
resolved_params = await self._resolve_query_params_if_needed(query_params)
|
|
273
316
|
|
|
274
|
-
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
|
+
):
|
|
275
320
|
yield await NotionPage.from_id(page.id)
|
|
276
321
|
|
|
277
322
|
async def _resolve_query_params_if_needed(
|
|
@@ -284,5 +329,11 @@ class NotionDataSource(Entity):
|
|
|
284
329
|
return await self.query_resolver.resolve_params(query_params)
|
|
285
330
|
|
|
286
331
|
async def get_schema_description(self) -> str:
|
|
287
|
-
formatter = DataSourcePropertySchemaFormatter(
|
|
288
|
-
|
|
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
|
+
)
|
notionary/database/client.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from notionary.blocks.rich_text.rich_text_markdown_converter import
|
|
1
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import (
|
|
2
|
+
RichTextToMarkdownConverter,
|
|
3
|
+
)
|
|
2
4
|
from notionary.database.schemas import (
|
|
3
5
|
NotionDatabaseDto,
|
|
4
6
|
NotionDatabaseUpdateDto,
|
|
@@ -15,14 +17,20 @@ class NotionDatabaseHttpClient(NotionHttpClient):
|
|
|
15
17
|
response = await self.get(f"databases/{self._database_id}")
|
|
16
18
|
return NotionDatabaseDto.model_validate(response)
|
|
17
19
|
|
|
18
|
-
async def patch_database(
|
|
20
|
+
async def patch_database(
|
|
21
|
+
self, update_database_dto: NotionDatabaseUpdateDto
|
|
22
|
+
) -> NotionDatabaseDto:
|
|
19
23
|
update_database_dto_dict = update_database_dto.model_dump(exclude_none=True)
|
|
20
24
|
|
|
21
|
-
response = await self.patch(
|
|
25
|
+
response = await self.patch(
|
|
26
|
+
f"databases/{self._database_id}", data=update_database_dto_dict
|
|
27
|
+
)
|
|
22
28
|
return NotionDatabaseDto.model_validate(response)
|
|
23
29
|
|
|
24
30
|
async def update_database_title(self, title: str) -> NotionDatabaseDto:
|
|
25
|
-
from notionary.blocks.rich_text.markdown_rich_text_converter import
|
|
31
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
32
|
+
MarkdownRichTextConverter,
|
|
33
|
+
)
|
|
26
34
|
|
|
27
35
|
markdown_rich_text_formatter = MarkdownRichTextConverter()
|
|
28
36
|
database_rich_text = await markdown_rich_text_formatter.to_rich_text(title)
|
|
@@ -31,13 +39,23 @@ class NotionDatabaseHttpClient(NotionHttpClient):
|
|
|
31
39
|
return await self.patch_database(database_title_update_dto)
|
|
32
40
|
|
|
33
41
|
async def update_database_description(self, description: str) -> str:
|
|
34
|
-
from notionary.blocks.rich_text.markdown_rich_text_converter import
|
|
42
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
43
|
+
MarkdownRichTextConverter,
|
|
44
|
+
)
|
|
35
45
|
|
|
36
46
|
markdown_to_rich_text_converter = MarkdownRichTextConverter()
|
|
37
|
-
rich_text_description = await markdown_to_rich_text_converter.to_rich_text(
|
|
47
|
+
rich_text_description = await markdown_to_rich_text_converter.to_rich_text(
|
|
48
|
+
description
|
|
49
|
+
)
|
|
38
50
|
|
|
39
|
-
database_description_update_dto = NotionDatabaseUpdateDto(
|
|
40
|
-
|
|
51
|
+
database_description_update_dto = NotionDatabaseUpdateDto(
|
|
52
|
+
description=rich_text_description
|
|
53
|
+
)
|
|
54
|
+
update_database_response = await self.patch_database(
|
|
55
|
+
database_description_update_dto
|
|
56
|
+
)
|
|
41
57
|
|
|
42
58
|
rich_text_to_markdown_converter = RichTextToMarkdownConverter()
|
|
43
|
-
return await rich_text_to_markdown_converter.to_markdown(
|
|
59
|
+
return await rich_text_to_markdown_converter.to_markdown(
|
|
60
|
+
update_database_response.description
|
|
61
|
+
)
|
|
@@ -2,7 +2,9 @@ from typing import override
|
|
|
2
2
|
|
|
3
3
|
from notionary.database.schemas import NotionDatabaseDto
|
|
4
4
|
from notionary.http.client import NotionHttpClient
|
|
5
|
-
from notionary.shared.entity.entity_metadata_update_client import
|
|
5
|
+
from notionary.shared.entity.entity_metadata_update_client import (
|
|
6
|
+
EntityMetadataUpdateClient,
|
|
7
|
+
)
|
|
6
8
|
from notionary.shared.entity.schemas import NotionEntityUpdateDto
|
|
7
9
|
|
|
8
10
|
|
|
@@ -12,8 +14,14 @@ class DatabaseMetadataUpdateClient(NotionHttpClient, EntityMetadataUpdateClient)
|
|
|
12
14
|
self._database_id = database_id
|
|
13
15
|
|
|
14
16
|
@override
|
|
15
|
-
async def patch_metadata(
|
|
16
|
-
|
|
17
|
+
async def patch_metadata(
|
|
18
|
+
self, updated_data: NotionEntityUpdateDto
|
|
19
|
+
) -> NotionDatabaseDto:
|
|
20
|
+
updated_data_dict = updated_data.model_dump(
|
|
21
|
+
exclude_unset=True, exclude_none=True
|
|
22
|
+
)
|
|
17
23
|
|
|
18
|
-
response_dict = await self.patch(
|
|
24
|
+
response_dict = await self.patch(
|
|
25
|
+
f"databases/{self._database_id}", data=updated_data_dict
|
|
26
|
+
)
|
|
19
27
|
return NotionDatabaseDto.model_validate(response_dict)
|
notionary/database/service.py
CHANGED
|
@@ -2,10 +2,14 @@ import asyncio
|
|
|
2
2
|
from collections.abc import Awaitable, Callable
|
|
3
3
|
from typing import Self
|
|
4
4
|
|
|
5
|
-
from notionary.blocks.rich_text.rich_text_markdown_converter import
|
|
5
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import (
|
|
6
|
+
RichTextToMarkdownConverter,
|
|
7
|
+
)
|
|
6
8
|
from notionary.data_source.service import NotionDataSource
|
|
7
9
|
from notionary.database.client import NotionDatabaseHttpClient
|
|
8
|
-
from notionary.database.database_metadata_update_client import
|
|
10
|
+
from notionary.database.database_metadata_update_client import (
|
|
11
|
+
DatabaseMetadataUpdateClient,
|
|
12
|
+
)
|
|
9
13
|
from notionary.database.schemas import NotionDatabaseDto
|
|
10
14
|
from notionary.shared.entity.dto_parsers import (
|
|
11
15
|
extract_description,
|
|
@@ -71,7 +75,8 @@ class NotionDatabase(Entity):
|
|
|
71
75
|
client: NotionDatabaseHttpClient,
|
|
72
76
|
) -> Self:
|
|
73
77
|
title, description = await asyncio.gather(
|
|
74
|
-
extract_title(dto, rich_text_converter),
|
|
78
|
+
extract_title(dto, rich_text_converter),
|
|
79
|
+
extract_description(dto, rich_text_converter),
|
|
75
80
|
)
|
|
76
81
|
|
|
77
82
|
metadata_update_client = DatabaseMetadataUpdateClient(database_id=dto.id)
|
|
@@ -120,5 +125,7 @@ class NotionDatabase(Entity):
|
|
|
120
125
|
self._title = result.title[0].plain_text if result.title else ""
|
|
121
126
|
|
|
122
127
|
async def set_description(self, description: str) -> None:
|
|
123
|
-
updated_description = await self.client.update_database_description(
|
|
128
|
+
updated_description = await self.client.update_database_description(
|
|
129
|
+
description=description
|
|
130
|
+
)
|
|
124
131
|
self._description = updated_description
|
notionary/exceptions/__init__.py
CHANGED
|
@@ -8,10 +8,22 @@ from .api import (
|
|
|
8
8
|
NotionValidationError,
|
|
9
9
|
)
|
|
10
10
|
from .base import NotionaryException
|
|
11
|
-
from .block_parsing import
|
|
11
|
+
from .block_parsing import (
|
|
12
|
+
InsufficientColumnsError,
|
|
13
|
+
InvalidColumnRatioSumError,
|
|
14
|
+
UnsupportedVideoFormatError,
|
|
15
|
+
)
|
|
12
16
|
from .data_source import DataSourcePropertyNotFound, DataSourcePropertyTypeError
|
|
13
|
-
from .file_upload import
|
|
14
|
-
|
|
17
|
+
from .file_upload import (
|
|
18
|
+
FileSizeException,
|
|
19
|
+
NoFileExtensionException,
|
|
20
|
+
UnsupportedFileTypeException,
|
|
21
|
+
)
|
|
22
|
+
from .properties import (
|
|
23
|
+
AccessPagePropertyWithoutDataSourceError,
|
|
24
|
+
PagePropertyNotFoundError,
|
|
25
|
+
PagePropertyTypeError,
|
|
26
|
+
)
|
|
15
27
|
from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
|
|
16
28
|
|
|
17
29
|
__all__ = [
|
|
@@ -6,14 +6,18 @@ RATIO_TOLERANCE = 0.0001
|
|
|
6
6
|
class InsufficientColumnsError(NotionaryException):
|
|
7
7
|
def __init__(self, column_count: int) -> None:
|
|
8
8
|
self.column_count = column_count
|
|
9
|
-
super().__init__(
|
|
9
|
+
super().__init__(
|
|
10
|
+
f"Columns container must contain at least 2 column blocks, but only {column_count} found"
|
|
11
|
+
)
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class InvalidColumnRatioSumError(NotionaryException):
|
|
13
15
|
def __init__(self, total: float, tolerance: float = RATIO_TOLERANCE) -> None:
|
|
14
16
|
self.total = total
|
|
15
17
|
self.tolerance = tolerance
|
|
16
|
-
super().__init__(
|
|
18
|
+
super().__init__(
|
|
19
|
+
f"Width ratios must sum to 1.0 (±{tolerance}), but sum is {total}"
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class UnsupportedVideoFormatError(ValueError):
|
|
@@ -67,7 +67,11 @@ class MissingRequiredValue(QueryBuilderError):
|
|
|
67
67
|
def __init__(
|
|
68
68
|
self,
|
|
69
69
|
property_name: str,
|
|
70
|
-
operator: StringOperator
|
|
70
|
+
operator: StringOperator
|
|
71
|
+
| NumberOperator
|
|
72
|
+
| BooleanOperator
|
|
73
|
+
| DateOperator
|
|
74
|
+
| ArrayOperator,
|
|
71
75
|
) -> None:
|
|
72
76
|
self.operator = operator
|
|
73
77
|
|
|
@@ -79,7 +83,11 @@ class ValueNotAllowedForOperator(QueryBuilderError):
|
|
|
79
83
|
def __init__(
|
|
80
84
|
self,
|
|
81
85
|
property_name: str,
|
|
82
|
-
operator: StringOperator
|
|
86
|
+
operator: StringOperator
|
|
87
|
+
| NumberOperator
|
|
88
|
+
| BooleanOperator
|
|
89
|
+
| DateOperator
|
|
90
|
+
| ArrayOperator,
|
|
83
91
|
) -> None:
|
|
84
92
|
self.operator = operator
|
|
85
93
|
|
|
@@ -144,9 +152,7 @@ class NoPropertySelected(QueryBuilderError):
|
|
|
144
152
|
|
|
145
153
|
class EmptyOrGroupError(QueryBuilderError):
|
|
146
154
|
def __init__(self) -> None:
|
|
147
|
-
message = (
|
|
148
|
-
"Cannot create an OR group with no conditions. Add at least one filter condition before using .or_where()"
|
|
149
|
-
)
|
|
155
|
+
message = "Cannot create an OR group with no conditions. Add at least one filter condition before using .or_where()"
|
|
150
156
|
super().__init__(message)
|
|
151
157
|
|
|
152
158
|
|
|
@@ -29,6 +29,8 @@ class DataSourcePropertyNotFound(NotionaryException):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class DataSourcePropertyTypeError(NotionaryException):
|
|
32
|
-
def __init__(
|
|
32
|
+
def __init__(
|
|
33
|
+
self, property_name: str, expected_type: str, actual_type: str
|
|
34
|
+
) -> None:
|
|
33
35
|
message = f"Property '{property_name}' has the wrong type. Expected: '{expected_type}', found: '{actual_type}'."
|
|
34
36
|
super().__init__(message)
|