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.
Files changed (201) hide show
  1. notionary/__init__.py +49 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/enums.py +0 -6
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  5. notionary/blocks/rich_text/models.py +13 -4
  6. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  7. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  8. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  9. notionary/blocks/schemas.py +33 -78
  10. notionary/comments/client.py +19 -6
  11. notionary/comments/factory.py +10 -3
  12. notionary/comments/schemas.py +10 -31
  13. notionary/comments/service.py +12 -4
  14. notionary/data_source/http/data_source_instance_client.py +59 -17
  15. notionary/data_source/properties/schemas.py +156 -115
  16. notionary/data_source/query/builder.py +67 -18
  17. notionary/data_source/query/resolver.py +16 -5
  18. notionary/data_source/query/schema.py +24 -6
  19. notionary/data_source/query/validator.py +18 -6
  20. notionary/data_source/schema/registry.py +31 -12
  21. notionary/data_source/schema/service.py +66 -20
  22. notionary/data_source/schemas.py +2 -2
  23. notionary/data_source/service.py +103 -43
  24. notionary/database/client.py +27 -9
  25. notionary/database/database_metadata_update_client.py +12 -4
  26. notionary/database/schemas.py +2 -2
  27. notionary/database/service.py +14 -9
  28. notionary/exceptions/__init__.py +20 -4
  29. notionary/exceptions/api.py +2 -2
  30. notionary/exceptions/base.py +1 -1
  31. notionary/exceptions/block_parsing.py +9 -5
  32. notionary/exceptions/data_source/builder.py +13 -7
  33. notionary/exceptions/data_source/properties.py +6 -4
  34. notionary/exceptions/file_upload.py +76 -0
  35. notionary/exceptions/properties.py +7 -5
  36. notionary/exceptions/search.py +10 -6
  37. notionary/file_upload/__init__.py +4 -0
  38. notionary/file_upload/client.py +128 -210
  39. notionary/file_upload/config/__init__.py +17 -0
  40. notionary/file_upload/config/config.py +39 -0
  41. notionary/file_upload/config/constants.py +16 -0
  42. notionary/file_upload/file/reader.py +28 -0
  43. notionary/file_upload/query/__init__.py +7 -0
  44. notionary/file_upload/query/builder.py +58 -0
  45. notionary/file_upload/query/models.py +37 -0
  46. notionary/file_upload/schemas.py +80 -0
  47. notionary/file_upload/service.py +182 -291
  48. notionary/file_upload/validation/factory.py +66 -0
  49. notionary/file_upload/validation/impl/file_name_length.py +25 -0
  50. notionary/file_upload/validation/models.py +134 -0
  51. notionary/file_upload/validation/port.py +7 -0
  52. notionary/file_upload/validation/service.py +17 -0
  53. notionary/file_upload/validation/validators/__init__.py +11 -0
  54. notionary/file_upload/validation/validators/file_exists.py +15 -0
  55. notionary/file_upload/validation/validators/file_extension.py +131 -0
  56. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  57. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  58. notionary/http/client.py +33 -30
  59. notionary/page/content/__init__.py +9 -0
  60. notionary/page/content/factory.py +21 -7
  61. notionary/page/content/markdown/builder.py +85 -23
  62. notionary/page/content/markdown/nodes/audio.py +8 -4
  63. notionary/page/content/markdown/nodes/base.py +3 -3
  64. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  65. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  66. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  67. notionary/page/content/markdown/nodes/callout.py +2 -2
  68. notionary/page/content/markdown/nodes/code.py +5 -3
  69. notionary/page/content/markdown/nodes/columns.py +3 -3
  70. notionary/page/content/markdown/nodes/container.py +9 -5
  71. notionary/page/content/markdown/nodes/divider.py +2 -2
  72. notionary/page/content/markdown/nodes/embed.py +8 -4
  73. notionary/page/content/markdown/nodes/equation.py +4 -2
  74. notionary/page/content/markdown/nodes/file.py +8 -4
  75. notionary/page/content/markdown/nodes/heading.py +2 -2
  76. notionary/page/content/markdown/nodes/image.py +8 -4
  77. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  78. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  79. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  80. notionary/page/content/markdown/nodes/pdf.py +8 -4
  81. notionary/page/content/markdown/nodes/quote.py +2 -2
  82. notionary/page/content/markdown/nodes/space.py +2 -2
  83. notionary/page/content/markdown/nodes/table.py +8 -5
  84. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  85. notionary/page/content/markdown/nodes/todo.py +15 -7
  86. notionary/page/content/markdown/nodes/toggle.py +2 -2
  87. notionary/page/content/markdown/nodes/video.py +8 -4
  88. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  89. notionary/page/content/markdown/structured_output/models.py +391 -0
  90. notionary/page/content/markdown/structured_output/service.py +211 -0
  91. notionary/page/content/parser/context.py +1 -1
  92. notionary/page/content/parser/factory.py +26 -8
  93. notionary/page/content/parser/parsers/audio.py +12 -32
  94. notionary/page/content/parser/parsers/base.py +2 -2
  95. notionary/page/content/parser/parsers/bookmark.py +2 -2
  96. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  97. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  98. notionary/page/content/parser/parsers/callout.py +15 -5
  99. notionary/page/content/parser/parsers/caption.py +9 -3
  100. notionary/page/content/parser/parsers/code.py +21 -7
  101. notionary/page/content/parser/parsers/column.py +8 -4
  102. notionary/page/content/parser/parsers/column_list.py +19 -7
  103. notionary/page/content/parser/parsers/divider.py +2 -2
  104. notionary/page/content/parser/parsers/embed.py +2 -4
  105. notionary/page/content/parser/parsers/equation.py +8 -4
  106. notionary/page/content/parser/parsers/file.py +12 -34
  107. notionary/page/content/parser/parsers/file_like_block.py +109 -0
  108. notionary/page/content/parser/parsers/heading.py +31 -10
  109. notionary/page/content/parser/parsers/image.py +12 -34
  110. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  111. notionary/page/content/parser/parsers/paragraph.py +3 -1
  112. notionary/page/content/parser/parsers/pdf.py +12 -34
  113. notionary/page/content/parser/parsers/quote.py +28 -9
  114. notionary/page/content/parser/parsers/space.py +2 -2
  115. notionary/page/content/parser/parsers/table.py +31 -10
  116. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  117. notionary/page/content/parser/parsers/todo.py +15 -5
  118. notionary/page/content/parser/parsers/toggle.py +15 -5
  119. notionary/page/content/parser/parsers/video.py +12 -34
  120. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  121. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  122. notionary/page/content/parser/post_processing/service.py +3 -1
  123. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  124. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  125. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  126. notionary/page/content/parser/service.py +4 -1
  127. notionary/page/content/renderer/context.py +15 -5
  128. notionary/page/content/renderer/factory.py +12 -6
  129. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  130. notionary/page/content/renderer/renderers/audio.py +20 -23
  131. notionary/page/content/renderer/renderers/base.py +3 -3
  132. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  133. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  134. notionary/page/content/renderer/renderers/callout.py +19 -7
  135. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  136. notionary/page/content/renderer/renderers/code.py +6 -2
  137. notionary/page/content/renderer/renderers/column.py +3 -1
  138. notionary/page/content/renderer/renderers/column_list.py +3 -1
  139. notionary/page/content/renderer/renderers/embed.py +3 -1
  140. notionary/page/content/renderer/renderers/equation.py +3 -1
  141. notionary/page/content/renderer/renderers/file.py +20 -23
  142. notionary/page/content/renderer/renderers/file_like_block.py +47 -0
  143. notionary/page/content/renderer/renderers/heading.py +22 -8
  144. notionary/page/content/renderer/renderers/image.py +20 -23
  145. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  146. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  147. notionary/page/content/renderer/renderers/pdf.py +20 -23
  148. notionary/page/content/renderer/renderers/quote.py +14 -6
  149. notionary/page/content/renderer/renderers/table.py +15 -5
  150. notionary/page/content/renderer/renderers/todo.py +16 -6
  151. notionary/page/content/renderer/renderers/toggle.py +8 -4
  152. notionary/page/content/renderer/renderers/video.py +20 -23
  153. notionary/page/content/renderer/service.py +9 -3
  154. notionary/page/content/service.py +21 -7
  155. notionary/page/content/syntax/definition/__init__.py +11 -0
  156. notionary/page/content/syntax/definition/models.py +57 -0
  157. notionary/page/content/syntax/definition/registry.py +371 -0
  158. notionary/page/content/syntax/prompts/__init__.py +4 -0
  159. notionary/page/content/syntax/prompts/models.py +11 -0
  160. notionary/page/content/syntax/prompts/registry.py +703 -0
  161. notionary/page/page_metadata_update_client.py +12 -4
  162. notionary/page/properties/client.py +46 -16
  163. notionary/page/properties/factory.py +6 -2
  164. notionary/page/properties/{models.py → schemas.py} +93 -107
  165. notionary/page/properties/service.py +111 -37
  166. notionary/page/schemas.py +3 -3
  167. notionary/page/service.py +21 -7
  168. notionary/shared/entity/client.py +6 -2
  169. notionary/shared/entity/dto_parsers.py +4 -37
  170. notionary/shared/entity/entity_metadata_update_client.py +25 -5
  171. notionary/shared/entity/schemas.py +6 -6
  172. notionary/shared/entity/service.py +89 -35
  173. notionary/shared/models/file.py +36 -6
  174. notionary/shared/models/icon.py +5 -12
  175. notionary/user/base.py +6 -2
  176. notionary/user/bot.py +22 -14
  177. notionary/user/client.py +3 -1
  178. notionary/user/person.py +3 -1
  179. notionary/user/schemas.py +3 -1
  180. notionary/user/service.py +6 -2
  181. notionary/utils/decorators.py +13 -9
  182. notionary/utils/fuzzy.py +6 -2
  183. notionary/utils/mixins/logging.py +3 -1
  184. notionary/utils/pagination.py +14 -4
  185. notionary/workspace/__init__.py +6 -2
  186. notionary/workspace/query/__init__.py +2 -1
  187. notionary/workspace/query/service.py +42 -13
  188. notionary/workspace/service.py +74 -46
  189. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
  190. notionary-0.4.1.dist-info/RECORD +236 -0
  191. notionary/file_upload/models.py +0 -69
  192. notionary/page/blocks/client.py +0 -1
  193. notionary/page/content/syntax/__init__.py +0 -4
  194. notionary/page/content/syntax/models.py +0 -66
  195. notionary/page/content/syntax/registry.py +0 -393
  196. notionary/page/page_context.py +0 -50
  197. notionary/shared/models/cover.py +0 -20
  198. notionary-0.3.1.dist-info/RECORD +0 -211
  199. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  200. {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
  201. {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 BaseModel, ValidationInfo, field_validator, model_serializer, model_validator
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 = StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator
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 (FieldType.DATE, FieldType.DATETIME) or 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(f"Operator '{operator_value}' is not valid for field type '{field_type}'")
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 (PropertyType.PEOPLE, PropertyType.RELATION) and not isinstance(self.value, str):
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: {operator_str: filter_value if filter_value is not None else True},
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(property_obj.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(self, property_type: PropertyType, operator: Operator) -> bool:
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(allowed_operator_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(self, operator_types: list[type[Operator]]) -> set[str]:
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(self, property_type: PropertyType) -> list[Operator]:
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(self, operator_types: list[type[Operator]]) -> list[Operator]:
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", description="Required field for the main heading of the entry"
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", description="Free-form text field for additional information"
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(display_name="Date", description="Date or date range field"),
26
- PropertyType.URL: PropertyTypeDescriptor(display_name="URL", description="Web address field"),
27
- PropertyType.EMAIL: PropertyTypeDescriptor(display_name="Email", description="Email address field"),
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(display_name="People", description="Reference to Notion users"),
44
+ PropertyType.PEOPLE: PropertyTypeDescriptor(
45
+ display_name="People", description="Reference to Notion users"
46
+ ),
35
47
  PropertyType.SELECT: PropertyTypeDescriptor(
36
- display_name="Single Select", description="Choose one option from available choices"
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", description="Choose multiple options from available choices"
52
+ display_name="Multi Select",
53
+ description="Choose multiple options from available choices",
40
54
  ),
41
55
  PropertyType.STATUS: PropertyTypeDescriptor(
42
- display_name="Status", description="Track status with predefined options"
56
+ display_name="Status",
57
+ description="Track status with predefined options",
43
58
  ),
44
59
  PropertyType.RELATION: PropertyTypeDescriptor(
45
- display_name="Relation", description="Link to entries in another database"
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(display_name=self._format_unknown_type_name(property_type), description=""),
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 DatabasePropertyTypeDescriptorRegistry, PropertyTypeDescriptor
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[[DataSourceRelationProperty], Awaitable[list[str]]],
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 = type_descriptor_registry or DatabasePropertyTypeDescriptorRegistry()
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 [*self._format_property_description(descriptor), *self._format_custom_description(prop)]
36
-
37
- def _format_property_description(self, descriptor: PropertyTypeDescriptor) -> list[str]:
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(self, prop: DataSourceProperty) -> list[str]:
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("Choose one option from", prop.option_names)
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("Choose multiple options from", prop.option_names)
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("Available statuses", prop.option_names)
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(self, prop: DataSourceRelationProperty) -> list[str]:
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(prop.related_data_source_id)
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(self, prop: DataSourceRelationProperty) -> list[str] | None:
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[[DataSourceRelationProperty], Awaitable[list[str]]] | None = None,
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(self, title: str, description: str | None, properties: dict[str, DataSourceProperty]) -> str:
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(self, properties: dict[str, DataSourceProperty]) -> list[str]:
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(self, properties: dict[str, DataSourceProperty]) -> list[tuple[str, DataSourceProperty]]:
125
- return sorted(properties.items(), key=lambda item: (self._is_not_title_property(item[1]), item[0]))
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(self, index: int, name: str, prop: DataSourceProperty) -> list[str]:
131
- lines = [f"{index}. - Property Name: '{name}'", f" - Property Type: '{prop.type.value}'"]
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("")
@@ -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 DiscriminatedDataSourceProperty
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, DiscriminatedDataSourceProperty]
27
+ properties: dict[str, AnyDataSourceProperty]
@@ -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 RichTextToMarkdownConverter
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 DataSourceInstanceClient
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 DataSourceQueryBuilder, DataSourceQueryParams, QueryResolver
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 DataSourcePropertyNotFound, DataSourcePropertyTypeError
23
- from notionary.page.properties.models import PageTitleProperty
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 EntityMetadataUpdateClient
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__(dto=dto)
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(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(property_name, DataSourceSelectProperty)
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(self, property_name: str) -> list[DataSourcePropertyOption]:
168
- multi_select_prop = self._get_typed_property_or_raise(property_name, DataSourceMultiSelectProperty)
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(property_name, DataSourceStatusProperty)
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(self, property_name: str) -> list[str]:
176
- relation_prop = self._get_typed_property_or_raise(property_name, DataSourceRelationProperty)
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(self, relation_prop: DataSourceRelationProperty) -> list[str]:
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
- (prop for prop in page.properties.values() if isinstance(prop, PageTitleProperty)),
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(self, name: str, property_type: type[DataSourcePropertyT]) -> DataSourcePropertyT:
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, expected_type=property_type.__name__, actual_type=type(prop).__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 filter(self) -> DataSourceQueryBuilder:
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(query_params=resolved_params)
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 get_pages_stream(
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(query_params=resolved_params):
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(relation_options_fetcher=self._get_relation_options)
279
- return await formatter.format(title=self._title, description=self._description, properties=self._properties)
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
+ )