notionary 0.4.0__py3-none-any.whl → 0.4.2__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 (178) hide show
  1. notionary/__init__.py +44 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  4. notionary/blocks/rich_text/models.py +13 -4
  5. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  6. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  7. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  8. notionary/blocks/schemas.py +2 -1
  9. notionary/comments/client.py +19 -6
  10. notionary/comments/factory.py +10 -3
  11. notionary/comments/schemas.py +9 -3
  12. notionary/comments/service.py +12 -4
  13. notionary/data_source/http/data_source_instance_client.py +59 -17
  14. notionary/data_source/properties/schemas.py +30 -10
  15. notionary/data_source/query/builder.py +67 -18
  16. notionary/data_source/query/resolver.py +16 -5
  17. notionary/data_source/query/schema.py +24 -6
  18. notionary/data_source/query/validator.py +18 -6
  19. notionary/data_source/schema/registry.py +31 -12
  20. notionary/data_source/schema/service.py +66 -20
  21. notionary/data_source/service.py +74 -23
  22. notionary/database/client.py +27 -9
  23. notionary/database/database_metadata_update_client.py +12 -4
  24. notionary/database/service.py +11 -4
  25. notionary/exceptions/__init__.py +15 -3
  26. notionary/exceptions/block_parsing.py +6 -2
  27. notionary/exceptions/data_source/builder.py +11 -5
  28. notionary/exceptions/data_source/properties.py +3 -1
  29. notionary/exceptions/file_upload.py +12 -3
  30. notionary/exceptions/properties.py +3 -1
  31. notionary/exceptions/search.py +6 -2
  32. notionary/file_upload/client.py +5 -1
  33. notionary/file_upload/config/config.py +10 -3
  34. notionary/file_upload/query/builder.py +6 -2
  35. notionary/file_upload/schemas.py +3 -1
  36. notionary/file_upload/service.py +42 -14
  37. notionary/file_upload/validation/factory.py +3 -1
  38. notionary/file_upload/validation/impl/file_name_length.py +3 -1
  39. notionary/file_upload/validation/models.py +15 -5
  40. notionary/file_upload/validation/validators/file_extension.py +12 -3
  41. notionary/http/client.py +27 -8
  42. notionary/page/content/__init__.py +9 -0
  43. notionary/page/content/factory.py +21 -7
  44. notionary/page/content/markdown/builder.py +85 -23
  45. notionary/page/content/markdown/nodes/audio.py +8 -4
  46. notionary/page/content/markdown/nodes/base.py +3 -3
  47. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  48. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  49. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  50. notionary/page/content/markdown/nodes/callout.py +2 -2
  51. notionary/page/content/markdown/nodes/code.py +5 -3
  52. notionary/page/content/markdown/nodes/columns.py +3 -3
  53. notionary/page/content/markdown/nodes/container.py +9 -5
  54. notionary/page/content/markdown/nodes/divider.py +2 -2
  55. notionary/page/content/markdown/nodes/embed.py +8 -4
  56. notionary/page/content/markdown/nodes/equation.py +4 -2
  57. notionary/page/content/markdown/nodes/file.py +8 -4
  58. notionary/page/content/markdown/nodes/heading.py +2 -2
  59. notionary/page/content/markdown/nodes/image.py +8 -4
  60. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  61. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  62. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  63. notionary/page/content/markdown/nodes/pdf.py +8 -4
  64. notionary/page/content/markdown/nodes/quote.py +2 -2
  65. notionary/page/content/markdown/nodes/space.py +2 -2
  66. notionary/page/content/markdown/nodes/table.py +8 -5
  67. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  68. notionary/page/content/markdown/nodes/todo.py +15 -7
  69. notionary/page/content/markdown/nodes/toggle.py +2 -2
  70. notionary/page/content/markdown/nodes/video.py +8 -4
  71. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  72. notionary/page/content/markdown/structured_output/models.py +391 -0
  73. notionary/page/content/markdown/structured_output/service.py +211 -0
  74. notionary/page/content/parser/context.py +1 -1
  75. notionary/page/content/parser/factory.py +23 -8
  76. notionary/page/content/parser/parsers/audio.py +7 -2
  77. notionary/page/content/parser/parsers/base.py +2 -2
  78. notionary/page/content/parser/parsers/bookmark.py +2 -2
  79. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  80. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  81. notionary/page/content/parser/parsers/callout.py +15 -5
  82. notionary/page/content/parser/parsers/caption.py +9 -3
  83. notionary/page/content/parser/parsers/code.py +21 -7
  84. notionary/page/content/parser/parsers/column.py +8 -4
  85. notionary/page/content/parser/parsers/column_list.py +19 -7
  86. notionary/page/content/parser/parsers/divider.py +2 -2
  87. notionary/page/content/parser/parsers/embed.py +2 -2
  88. notionary/page/content/parser/parsers/equation.py +8 -4
  89. notionary/page/content/parser/parsers/file.py +7 -2
  90. notionary/page/content/parser/parsers/file_like_block.py +30 -10
  91. notionary/page/content/parser/parsers/heading.py +31 -10
  92. notionary/page/content/parser/parsers/image.py +7 -2
  93. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  94. notionary/page/content/parser/parsers/paragraph.py +3 -1
  95. notionary/page/content/parser/parsers/pdf.py +7 -2
  96. notionary/page/content/parser/parsers/quote.py +28 -9
  97. notionary/page/content/parser/parsers/space.py +2 -2
  98. notionary/page/content/parser/parsers/table.py +31 -10
  99. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  100. notionary/page/content/parser/parsers/todo.py +15 -5
  101. notionary/page/content/parser/parsers/toggle.py +15 -5
  102. notionary/page/content/parser/parsers/video.py +7 -2
  103. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  104. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  105. notionary/page/content/parser/post_processing/service.py +3 -1
  106. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  107. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  108. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  109. notionary/page/content/parser/service.py +4 -1
  110. notionary/page/content/renderer/context.py +15 -5
  111. notionary/page/content/renderer/factory.py +12 -6
  112. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  113. notionary/page/content/renderer/renderers/audio.py +14 -5
  114. notionary/page/content/renderer/renderers/base.py +3 -3
  115. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  116. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  117. notionary/page/content/renderer/renderers/callout.py +19 -7
  118. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  119. notionary/page/content/renderer/renderers/code.py +6 -2
  120. notionary/page/content/renderer/renderers/column.py +3 -1
  121. notionary/page/content/renderer/renderers/column_list.py +3 -1
  122. notionary/page/content/renderer/renderers/embed.py +3 -1
  123. notionary/page/content/renderer/renderers/equation.py +3 -1
  124. notionary/page/content/renderer/renderers/file.py +14 -5
  125. notionary/page/content/renderer/renderers/file_like_block.py +8 -4
  126. notionary/page/content/renderer/renderers/heading.py +22 -8
  127. notionary/page/content/renderer/renderers/image.py +13 -4
  128. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  129. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  130. notionary/page/content/renderer/renderers/pdf.py +14 -5
  131. notionary/page/content/renderer/renderers/quote.py +14 -6
  132. notionary/page/content/renderer/renderers/table.py +15 -5
  133. notionary/page/content/renderer/renderers/todo.py +16 -6
  134. notionary/page/content/renderer/renderers/toggle.py +8 -4
  135. notionary/page/content/renderer/renderers/video.py +14 -5
  136. notionary/page/content/renderer/service.py +9 -3
  137. notionary/page/content/service.py +21 -7
  138. notionary/page/content/syntax/definition/__init__.py +11 -0
  139. notionary/page/content/syntax/definition/models.py +57 -0
  140. notionary/page/content/syntax/definition/registry.py +371 -0
  141. notionary/page/content/syntax/prompts/__init__.py +4 -0
  142. notionary/page/content/syntax/prompts/models.py +11 -0
  143. notionary/page/content/syntax/prompts/registry.py +703 -0
  144. notionary/page/page_metadata_update_client.py +12 -4
  145. notionary/page/properties/client.py +45 -15
  146. notionary/page/properties/factory.py +6 -2
  147. notionary/page/properties/service.py +110 -36
  148. notionary/page/service.py +20 -6
  149. notionary/shared/entity/client.py +6 -2
  150. notionary/shared/entity/dto_parsers.py +3 -1
  151. notionary/shared/entity/entity_metadata_update_client.py +9 -3
  152. notionary/shared/entity/schemas.py +1 -1
  153. notionary/shared/entity/service.py +53 -22
  154. notionary/shared/models/file.py +3 -1
  155. notionary/shared/models/icon.py +6 -4
  156. notionary/user/base.py +6 -2
  157. notionary/user/bot.py +10 -2
  158. notionary/user/client.py +3 -1
  159. notionary/user/person.py +3 -1
  160. notionary/user/schemas.py +3 -1
  161. notionary/user/service.py +6 -2
  162. notionary/utils/decorators.py +6 -2
  163. notionary/utils/fuzzy.py +6 -2
  164. notionary/utils/mixins/logging.py +3 -1
  165. notionary/utils/pagination.py +14 -4
  166. notionary/workspace/__init__.py +5 -1
  167. notionary/workspace/query/service.py +59 -16
  168. notionary/workspace/service.py +39 -11
  169. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/METADATA +1 -1
  170. notionary-0.4.2.dist-info/RECORD +236 -0
  171. notionary/page/blocks/client.py +0 -1
  172. notionary/page/content/syntax/__init__.py +0 -5
  173. notionary/page/content/syntax/models.py +0 -66
  174. notionary/page/content/syntax/registry.py +0 -371
  175. notionary-0.4.0.dist-info/RECORD +0 -230
  176. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  177. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/WHEEL +0 -0
  178. {notionary-0.4.0.dist-info → notionary-0.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,7 +2,12 @@ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
4
  class UnsupportedFileTypeException(NotionaryException):
5
- def __init__(self, extension: str, filename: str, supported_extensions_by_category: dict[str, list[str]]):
5
+ def __init__(
6
+ self,
7
+ extension: str,
8
+ filename: str,
9
+ supported_extensions_by_category: dict[str, list[str]],
10
+ ):
6
11
  supported_exts = []
7
12
  for category, extensions in supported_extensions_by_category.items():
8
13
  supported_exts.append(f"{category}: {', '.join(extensions[:5])}...")
@@ -44,7 +49,9 @@ class FileNotFoundError(NotionaryException):
44
49
 
45
50
  class FilenameTooLongError(NotionaryException):
46
51
  def __init__(self, filename: str, filename_bytes: int, max_filename_bytes: int):
47
- super().__init__(f"Filename too long: {filename_bytes} bytes (max {max_filename_bytes}). Filename: {filename}")
52
+ super().__init__(
53
+ f"Filename too long: {filename_bytes} bytes (max {max_filename_bytes}). Filename: {filename}"
54
+ )
48
55
  self.filename = filename
49
56
  self.filename_bytes = filename_bytes
50
57
  self.max_filename_bytes = max_filename_bytes
@@ -62,6 +69,8 @@ class UploadFailedError(NotionaryException):
62
69
 
63
70
  class UploadTimeoutError(NotionaryException):
64
71
  def __init__(self, file_upload_id: str, timeout_seconds: int):
65
- super().__init__(f"Upload timeout after {timeout_seconds}s for file_upload_id: {file_upload_id}")
72
+ super().__init__(
73
+ f"Upload timeout after {timeout_seconds}s for file_upload_id: {file_upload_id}"
74
+ )
66
75
  self.file_upload_id = file_upload_id
67
76
  self.timeout_seconds = timeout_seconds
@@ -50,7 +50,9 @@ class AccessPagePropertyWithoutDataSourceError(NotionaryException):
50
50
  }
51
51
 
52
52
  def __init__(self, parent_type: ParentType) -> None:
53
- parent_desc = self._PARENT_DESCRIPTIONS.get(parent_type, f"its parent type is '{parent_type}'")
53
+ parent_desc = self._PARENT_DESCRIPTIONS.get(
54
+ parent_type, f"its parent type is '{parent_type}'"
55
+ )
54
56
  message = (
55
57
  f"Cannot access properties other than title because this page's parent is {parent_desc}. "
56
58
  "To use operations like property reading/writing, you need to use a page whose parent is a data source."
@@ -2,7 +2,9 @@ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
4
  class EntityNotFound(NotionaryException):
5
- def __init__(self, entity_type: str, query: str, available_titles: list[str] | None = None) -> None:
5
+ def __init__(
6
+ self, entity_type: str, query: str, available_titles: list[str] | None = None
7
+ ) -> None:
6
8
  self.entity_type = entity_type
7
9
  self.query = query
8
10
  self.available_titles = available_titles or []
@@ -41,7 +43,9 @@ class NoUsersInWorkspace(NotionaryException):
41
43
 
42
44
 
43
45
  class UserNotFound(NotionaryException):
44
- def __init__(self, user_type: str, query: str, available_names: list[str] | None = None) -> None:
46
+ def __init__(
47
+ self, user_type: str, query: str, available_names: list[str] | None = None
48
+ ) -> None:
45
49
  self.user_type = user_type
46
50
  self.query = query
47
51
  self.available_names = available_names or []
@@ -11,7 +11,11 @@ from notionary.file_upload.schemas import (
11
11
  UploadMode,
12
12
  )
13
13
  from notionary.http.client import NotionHttpClient
14
- from notionary.utils.pagination import PaginatedResponse, paginate_notion_api, paginate_notion_api_generator
14
+ from notionary.utils.pagination import (
15
+ PaginatedResponse,
16
+ paginate_notion_api,
17
+ paginate_notion_api_generator,
18
+ )
15
19
 
16
20
 
17
21
  class FileUploadHttpClient(NotionHttpClient):
@@ -22,11 +22,18 @@ class FileUploadConfig(BaseModel):
22
22
  )
23
23
 
24
24
  max_upload_timeout: int = Field(
25
- default=300, gt=0, description="Maximum time in seconds to wait for an upload to complete."
25
+ default=300,
26
+ gt=0,
27
+ description="Maximum time in seconds to wait for an upload to complete.",
26
28
  )
27
29
 
28
- poll_interval: int = Field(default=2, gt=0, description="Interval in seconds for polling the upload status.")
30
+ poll_interval: int = Field(
31
+ default=2,
32
+ gt=0,
33
+ description="Interval in seconds for polling the upload status.",
34
+ )
29
35
 
30
36
  base_upload_path: Path | None = Field(
31
- default=None, description="Optional default base path for resolving relative file uploads."
37
+ default=None,
38
+ description="Optional default base path for resolving relative file uploads.",
32
39
  )
@@ -42,12 +42,16 @@ class FileUploadQueryBuilder:
42
42
  return value
43
43
 
44
44
  def with_total_results_limit(self, total_results_limit: int) -> Self:
45
- self._query.total_results_limit = self._validate_total_results_limit(total_results_limit)
45
+ self._query.total_results_limit = self._validate_total_results_limit(
46
+ total_results_limit
47
+ )
46
48
  return self
47
49
 
48
50
  def _validate_total_results_limit(self, value: int) -> int:
49
51
  if not (1 <= value <= 100):
50
- raise ValueError(f"total_results_limit must be between 1 and 100, got {value}")
52
+ raise ValueError(
53
+ f"total_results_limit must be between 1 and 100, got {value}"
54
+ )
51
55
  return value
52
56
 
53
57
  def build(self) -> FileUploadQuery:
@@ -52,7 +52,9 @@ class FileUploadCreateRequest(BaseModel):
52
52
  if self.mode == UploadMode.MULTI_PART and self.number_of_parts is None:
53
53
  raise ValueError("number_of_parts is required when mode is 'multi_part'")
54
54
  if self.mode == UploadMode.SINGLE_PART and self.number_of_parts is not None:
55
- raise ValueError("number_of_parts should not be provided for 'single_part' mode")
55
+ raise ValueError(
56
+ "number_of_parts should not be provided for 'single_part' mode"
57
+ )
56
58
  return self
57
59
 
58
60
  def model_dump(self, **kwargs):
@@ -27,7 +27,9 @@ class NotionFileUpload(LoggingMixin):
27
27
  self._config = config or FileUploadConfig()
28
28
  self._file_reader = file_reader or FileContentReader(config=self._config)
29
29
 
30
- async def upload_file(self, file_path: Path, filename: str | None = None) -> FileUploadResponse:
30
+ async def upload_file(
31
+ self, file_path: Path, filename: str | None = None
32
+ ) -> FileUploadResponse:
31
33
  file_path = Path(file_path)
32
34
 
33
35
  if not file_path.is_absolute() and self._config.base_upload_path:
@@ -43,10 +45,15 @@ class NotionFileUpload(LoggingMixin):
43
45
 
44
46
  if self._fits_in_single_part(file_size):
45
47
  content = await self._file_reader.read_full_file(file_path)
46
- return await self._upload_single_part_content(content, filename, content_type)
48
+ return await self._upload_single_part_content(
49
+ content, filename, content_type
50
+ )
47
51
  else:
48
52
  return await self._upload_multi_part_content(
49
- filename, content_type, file_size, self._file_reader.read_file_chunks(file_path)
53
+ filename,
54
+ content_type,
55
+ file_size,
56
+ self._file_reader.read_file_chunks(file_path),
50
57
  )
51
58
 
52
59
  async def upload_from_bytes(
@@ -66,10 +73,15 @@ class NotionFileUpload(LoggingMixin):
66
73
  content_type = content_type or self._guess_content_type(filename)
67
74
 
68
75
  if self._fits_in_single_part(file_size):
69
- return await self._upload_single_part_content(file_content, filename, content_type)
76
+ return await self._upload_single_part_content(
77
+ file_content, filename, content_type
78
+ )
70
79
 
71
80
  return await self._upload_multi_part_content(
72
- filename, content_type, file_size, self._file_reader.bytes_to_chunks(file_content)
81
+ filename,
82
+ content_type,
83
+ file_size,
84
+ self._file_reader.bytes_to_chunks(file_content),
73
85
  )
74
86
 
75
87
  async def _upload_single_part_content(
@@ -86,7 +98,10 @@ class NotionFileUpload(LoggingMixin):
86
98
  filename=filename,
87
99
  )
88
100
 
89
- self.logger.info("Single-part content sent, waiting for completion... (ID: %s)", file_upload.id)
101
+ self.logger.info(
102
+ "Single-part content sent, waiting for completion... (ID: %s)",
103
+ file_upload.id,
104
+ )
90
105
  return await self._wait_for_completion(file_upload.id)
91
106
 
92
107
  async def _upload_multi_part_content(
@@ -108,7 +123,10 @@ class NotionFileUpload(LoggingMixin):
108
123
 
109
124
  await self._client.complete_upload(file_upload.id)
110
125
 
111
- self.logger.info("Multi-part content sent, waiting for completion... (ID: %s)", file_upload.id)
126
+ self.logger.info(
127
+ "Multi-part content sent, waiting for completion... (ID: %s)",
128
+ file_upload.id,
129
+ )
112
130
  return await self._wait_for_completion(file_upload.id)
113
131
 
114
132
  async def _send_parts(
@@ -133,7 +151,8 @@ class NotionFileUpload(LoggingMixin):
133
151
 
134
152
  except Exception as e:
135
153
  raise UploadFailedError(
136
- file_upload_id=file_upload_id, reason=f"Failed to upload part {part_number}/{total_parts}: {e}"
154
+ file_upload_id=file_upload_id,
155
+ reason=f"Failed to upload part {part_number}/{total_parts}: {e}",
137
156
  ) from e
138
157
 
139
158
  def _fits_in_single_part(self, file_size: int) -> bool:
@@ -144,7 +163,9 @@ class NotionFileUpload(LoggingMixin):
144
163
  return content_type
145
164
 
146
165
  def _calculate_part_count(self, file_size: int) -> int:
147
- return (file_size + self._config.multi_part_chunk_size - 1) // self._config.multi_part_chunk_size
166
+ return (
167
+ file_size + self._config.multi_part_chunk_size - 1
168
+ ) // self._config.multi_part_chunk_size
148
169
 
149
170
  async def get_upload_status(self, file_upload_id: str) -> str:
150
171
  try:
@@ -161,12 +182,16 @@ class NotionFileUpload(LoggingMixin):
161
182
  timeout = timeout_seconds or self._config.max_upload_timeout
162
183
 
163
184
  try:
164
- return await asyncio.wait_for(self._poll_status_until_complete(file_upload_id), timeout=timeout)
185
+ return await asyncio.wait_for(
186
+ self._poll_status_until_complete(file_upload_id), timeout=timeout
187
+ )
165
188
 
166
189
  except TimeoutError as e:
167
190
  raise UploadTimeoutError(file_upload_id, timeout) from e
168
191
 
169
- async def _poll_status_until_complete(self, file_upload_id: str) -> FileUploadResponse:
192
+ async def _poll_status_until_complete(
193
+ self, file_upload_id: str
194
+ ) -> FileUploadResponse:
170
195
  while True:
171
196
  upload_info = await self._client.get_file_upload(file_upload_id)
172
197
 
@@ -182,7 +207,8 @@ class NotionFileUpload(LoggingMixin):
182
207
  async def get_uploads(
183
208
  self,
184
209
  *,
185
- filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder] | None = None,
210
+ filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
211
+ | None = None,
186
212
  query: FileUploadQuery | None = None,
187
213
  ) -> list[FileUploadResponse]:
188
214
  resolved_query = self._resolve_query(filter_fn=filter_fn, query=query)
@@ -191,7 +217,8 @@ class NotionFileUpload(LoggingMixin):
191
217
  async def iter_uploads(
192
218
  self,
193
219
  *,
194
- filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder] | None = None,
220
+ filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
221
+ | None = None,
195
222
  query: FileUploadQuery | None = None,
196
223
  ) -> AsyncIterator[FileUploadResponse]:
197
224
  resolved_query = self._resolve_query(filter_fn=filter_fn, query=query)
@@ -200,7 +227,8 @@ class NotionFileUpload(LoggingMixin):
200
227
 
201
228
  def _resolve_query(
202
229
  self,
203
- filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder] | None = None,
230
+ filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
231
+ | None = None,
204
232
  query: FileUploadQuery | None = None,
205
233
  ) -> FileUploadQuery:
206
234
  if filter_fn and query:
@@ -60,5 +60,7 @@ def _create_extension_validator(filename: str) -> FileExtensionValidator:
60
60
  return FileExtensionValidator(filename=filename)
61
61
 
62
62
 
63
- def _create_size_validator(filename: str, file_size_bytes: int) -> FileUploadLimitValidator:
63
+ def _create_size_validator(
64
+ filename: str, file_size_bytes: int
65
+ ) -> FileUploadLimitValidator:
64
66
  return FileUploadLimitValidator(filename=filename, file_size_bytes=file_size_bytes)
@@ -6,7 +6,9 @@ from notionary.file_upload.validation.port import FileUploadValidator
6
6
 
7
7
 
8
8
  class FileNameLengthValidator(FileUploadValidator):
9
- def __init__(self, filename: str, file_upload_config: FileUploadConfig | None = None) -> None:
9
+ def __init__(
10
+ self, filename: str, file_upload_config: FileUploadConfig | None = None
11
+ ) -> None:
10
12
  self._filename = filename
11
13
 
12
14
  file_upload_config = file_upload_config or FileUploadConfig()
@@ -53,14 +53,24 @@ class DocumentMimeType(StrEnum):
53
53
  PLAIN_TEXT = "text/plain"
54
54
  JSON = "application/json"
55
55
  MSWORD = "application/msword"
56
- WORD_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
57
- WORD_TEMPLATE = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
56
+ WORD_DOCUMENT = (
57
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
58
+ )
59
+ WORD_TEMPLATE = (
60
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
61
+ )
58
62
  EXCEL = "application/vnd.ms-excel"
59
63
  EXCEL_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
60
- EXCEL_TEMPLATE = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"
64
+ EXCEL_TEMPLATE = (
65
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.template"
66
+ )
61
67
  POWERPOINT = "application/vnd.ms-powerpoint"
62
- POWERPOINT_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
63
- POWERPOINT_TEMPLATE = "application/vnd.openxmlformats-officedocument.presentationml.template"
68
+ POWERPOINT_PRESENTATION = (
69
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
70
+ )
71
+ POWERPOINT_TEMPLATE = (
72
+ "application/vnd.openxmlformats-officedocument.presentationml.template"
73
+ )
64
74
 
65
75
 
66
76
  class ImageExtension(StrEnum):
@@ -1,7 +1,10 @@
1
1
  from pathlib import Path
2
2
  from typing import ClassVar, override
3
3
 
4
- from notionary.exceptions.file_upload import NoFileExtensionException, UnsupportedFileTypeException
4
+ from notionary.exceptions.file_upload import (
5
+ NoFileExtensionException,
6
+ UnsupportedFileTypeException,
7
+ )
5
8
  from notionary.file_upload.validation.models import (
6
9
  AudioExtension,
7
10
  AudioMimeType,
@@ -94,7 +97,9 @@ class FileExtensionValidator(FileUploadValidator):
94
97
 
95
98
  if not self._is_supported(extension):
96
99
  supported_by_category = self._get_supported_extensions_by_category()
97
- raise UnsupportedFileTypeException(extension, self.filename, supported_by_category)
100
+ raise UnsupportedFileTypeException(
101
+ extension, self.filename, supported_by_category
102
+ )
98
103
 
99
104
  @staticmethod
100
105
  def _extract_extension(filename: str) -> str:
@@ -117,6 +122,10 @@ class FileExtensionValidator(FileUploadValidator):
117
122
  def _get_supported_extensions_by_category(self) -> dict[str, list[str]]:
118
123
  result = {}
119
124
  for category in FileCategory:
120
- extensions = [ext for ext, cat in self.EXTENSION_TO_CATEGORY.items() if cat == category]
125
+ extensions = [
126
+ ext
127
+ for ext, cat in self.EXTENSION_TO_CATEGORY.items()
128
+ if cat == category
129
+ ]
121
130
  result[category.value] = extensions
122
131
  return result
notionary/http/client.py CHANGED
@@ -50,13 +50,17 @@ class NotionHttpClient(LoggingMixin):
50
50
  try:
51
51
  loop = asyncio.get_event_loop()
52
52
  if not loop.is_running():
53
- self.logger.warning("Event loop not running, could not auto-close NotionHttpClient")
53
+ self.logger.warning(
54
+ "Event loop not running, could not auto-close NotionHttpClient"
55
+ )
54
56
  return
55
57
 
56
58
  loop.create_task(self.close())
57
59
  self.logger.debug("Created cleanup task for NotionHttpClient")
58
60
  except RuntimeError:
59
- self.logger.warning("No event loop available for auto-closing NotionHttpClient")
61
+ self.logger.warning(
62
+ "No event loop available for auto-closing NotionHttpClient"
63
+ )
60
64
 
61
65
  async def __aenter__(self):
62
66
  await self._ensure_initialized()
@@ -74,13 +78,19 @@ class NotionHttpClient(LoggingMixin):
74
78
  self._is_initialized = False
75
79
  self.logger.debug("NotionHttpClient closed")
76
80
 
77
- async def get(self, endpoint: str, params: JsonDict | None = None) -> JsonDict | None:
81
+ async def get(
82
+ self, endpoint: str, params: JsonDict | None = None
83
+ ) -> JsonDict | None:
78
84
  return await self._make_request(HttpMethod.GET, endpoint, params=params)
79
85
 
80
- async def post(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
86
+ async def post(
87
+ self, endpoint: str, data: JsonDict | None = None
88
+ ) -> JsonDict | None:
81
89
  return await self._make_request(HttpMethod.POST, endpoint, data)
82
90
 
83
- async def patch(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
91
+ async def patch(
92
+ self, endpoint: str, data: JsonDict | None = None
93
+ ) -> JsonDict | None:
84
94
  return await self._make_request(HttpMethod.PATCH, endpoint, data)
85
95
 
86
96
  async def delete(self, endpoint: str) -> JsonDict | None:
@@ -105,10 +115,15 @@ class NotionHttpClient(LoggingMixin):
105
115
  if params:
106
116
  request_kwargs["params"] = params
107
117
 
108
- if method.value in [HttpMethod.POST.value, HttpMethod.PATCH.value] and data is not None:
118
+ if (
119
+ method.value in [HttpMethod.POST.value, HttpMethod.PATCH.value]
120
+ and data is not None
121
+ ):
109
122
  request_kwargs["json"] = data
110
123
 
111
- response: httpx.Response = await getattr(self.client, method.value)(url, **request_kwargs)
124
+ response: httpx.Response = await getattr(self.client, method.value)(
125
+ url, **request_kwargs
126
+ )
112
127
 
113
128
  response.raise_for_status()
114
129
  result_data = response.json()
@@ -173,7 +188,11 @@ class NotionHttpClient(LoggingMixin):
173
188
 
174
189
  def _find_token(self) -> str | None:
175
190
  token = next(
176
- (os.getenv(var) for var in ("NOTION_SECRET", "NOTION_INTEGRATION_KEY", "NOTION_TOKEN") if os.getenv(var)),
191
+ (
192
+ os.getenv(var)
193
+ for var in ("NOTION_SECRET", "NOTION_INTEGRATION_KEY", "NOTION_TOKEN")
194
+ if os.getenv(var)
195
+ ),
177
196
  None,
178
197
  )
179
198
  if token:
@@ -0,0 +1,9 @@
1
+ from .factory import PageContentServiceFactory
2
+ from .service import PageContentService
3
+ from .syntax.prompts import SyntaxPromptRegistry
4
+
5
+ __all__ = [
6
+ "PageContentService",
7
+ "PageContentServiceFactory",
8
+ "SyntaxPromptRegistry",
9
+ ]
@@ -1,6 +1,8 @@
1
1
  from notionary.blocks.client import NotionBlockHttpClient
2
2
  from notionary.page.content.parser.factory import ConverterChainFactory
3
- from notionary.page.content.parser.post_processing.handlers import RichTextLengthTruncationPostProcessor
3
+ from notionary.page.content.parser.post_processing.handlers import (
4
+ RichTextLengthTruncationPostProcessor,
5
+ )
4
6
  from notionary.page.content.parser.post_processing.service import BlockPostProcessor
5
7
  from notionary.page.content.parser.pre_processsing.handlers import (
6
8
  ColumnSyntaxPreProcessor,
@@ -11,8 +13,12 @@ from notionary.page.content.parser.pre_processsing.handlers import (
11
13
  from notionary.page.content.parser.pre_processsing.service import MarkdownPreProcessor
12
14
  from notionary.page.content.parser.service import MarkdownToNotionConverter
13
15
  from notionary.page.content.renderer.factory import RendererChainFactory
14
- from notionary.page.content.renderer.post_processing.handlers import NumberedListPlaceholderReplacerPostProcessor
15
- from notionary.page.content.renderer.post_processing.service import MarkdownRenderingPostProcessor
16
+ from notionary.page.content.renderer.post_processing.handlers import (
17
+ NumberedListPlaceholderReplacerPostProcessor,
18
+ )
19
+ from notionary.page.content.renderer.post_processing.service import (
20
+ MarkdownRenderingPostProcessor,
21
+ )
16
22
  from notionary.page.content.renderer.service import NotionToMarkdownConverter
17
23
  from notionary.page.content.service import PageContentService
18
24
 
@@ -23,10 +29,14 @@ class PageContentServiceFactory:
23
29
  converter_chain_factory: ConverterChainFactory | None = None,
24
30
  renderer_chain_factory: RendererChainFactory | None = None,
25
31
  ) -> None:
26
- self._converter_chain_factory = converter_chain_factory or ConverterChainFactory()
32
+ self._converter_chain_factory = (
33
+ converter_chain_factory or ConverterChainFactory()
34
+ )
27
35
  self._renderer_chain_factory = renderer_chain_factory or RendererChainFactory()
28
36
 
29
- def create(self, page_id: str, block_client: NotionBlockHttpClient) -> PageContentService:
37
+ def create(
38
+ self, page_id: str, block_client: NotionBlockHttpClient
39
+ ) -> PageContentService:
30
40
  markdown_converter = self._create_markdown_to_notion_converter()
31
41
  notion_to_markdown_converter = self._create_notion_to_markdown_converter()
32
42
 
@@ -50,7 +60,9 @@ class PageContentServiceFactory:
50
60
 
51
61
  def _create_notion_to_markdown_converter(self) -> NotionToMarkdownConverter:
52
62
  renderer_chain = self._renderer_chain_factory.create()
53
- markdown_rendering_post_processor = self._create_markdown_rendering_post_processor()
63
+ markdown_rendering_post_processor = (
64
+ self._create_markdown_rendering_post_processor()
65
+ )
54
66
  return NotionToMarkdownConverter(
55
67
  renderer_chain=renderer_chain,
56
68
  post_processor=markdown_rendering_post_processor,
@@ -69,7 +81,9 @@ class PageContentServiceFactory:
69
81
  post_processor.register(RichTextLengthTruncationPostProcessor())
70
82
  return post_processor
71
83
 
72
- def _create_markdown_rendering_post_processor(self) -> MarkdownRenderingPostProcessor:
84
+ def _create_markdown_rendering_post_processor(
85
+ self,
86
+ ) -> MarkdownRenderingPostProcessor:
73
87
  post_processor = MarkdownRenderingPostProcessor()
74
88
  post_processor.register(NumberedListPlaceholderReplacerPostProcessor())
75
89
  return post_processor