notionary 0.2.28__py3-none-any.whl → 0.3.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 (149) hide show
  1. notionary/__init__.py +9 -2
  2. notionary/blocks/__init__.py +5 -0
  3. notionary/blocks/client.py +6 -4
  4. notionary/blocks/enums.py +28 -1
  5. notionary/blocks/rich_text/markdown_rich_text_converter.py +14 -0
  6. notionary/blocks/rich_text/models.py +14 -0
  7. notionary/blocks/rich_text/name_id_resolver/__init__.py +2 -0
  8. notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
  9. notionary/blocks/rich_text/rich_text_markdown_converter.py +12 -0
  10. notionary/blocks/rich_text/rich_text_patterns.py +3 -0
  11. notionary/blocks/schemas.py +42 -10
  12. notionary/comments/__init__.py +5 -0
  13. notionary/comments/client.py +7 -10
  14. notionary/comments/factory.py +4 -6
  15. notionary/data_source/http/data_source_instance_client.py +14 -4
  16. notionary/data_source/properties/{models.py → schemas.py} +4 -8
  17. notionary/data_source/query/__init__.py +9 -0
  18. notionary/data_source/query/builder.py +38 -10
  19. notionary/data_source/query/schema.py +13 -10
  20. notionary/data_source/query/validator.py +11 -11
  21. notionary/data_source/schema/registry.py +104 -0
  22. notionary/data_source/schema/service.py +136 -0
  23. notionary/data_source/schemas.py +1 -1
  24. notionary/data_source/service.py +29 -103
  25. notionary/database/service.py +17 -60
  26. notionary/exceptions/__init__.py +5 -1
  27. notionary/exceptions/block_parsing.py +21 -0
  28. notionary/exceptions/search.py +24 -0
  29. notionary/http/client.py +9 -10
  30. notionary/http/models.py +5 -4
  31. notionary/page/content/factory.py +10 -3
  32. notionary/page/content/markdown/builder.py +76 -154
  33. notionary/page/content/markdown/nodes/__init__.py +0 -2
  34. notionary/page/content/markdown/nodes/audio.py +1 -1
  35. notionary/page/content/markdown/nodes/base.py +1 -1
  36. notionary/page/content/markdown/nodes/bookmark.py +1 -1
  37. notionary/page/content/markdown/nodes/breadcrumb.py +1 -1
  38. notionary/page/content/markdown/nodes/bulleted_list.py +31 -8
  39. notionary/page/content/markdown/nodes/callout.py +12 -10
  40. notionary/page/content/markdown/nodes/code.py +3 -5
  41. notionary/page/content/markdown/nodes/columns.py +39 -21
  42. notionary/page/content/markdown/nodes/container.py +64 -0
  43. notionary/page/content/markdown/nodes/divider.py +1 -1
  44. notionary/page/content/markdown/nodes/embed.py +1 -1
  45. notionary/page/content/markdown/nodes/equation.py +1 -1
  46. notionary/page/content/markdown/nodes/file.py +1 -1
  47. notionary/page/content/markdown/nodes/heading.py +26 -6
  48. notionary/page/content/markdown/nodes/image.py +1 -1
  49. notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
  50. notionary/page/content/markdown/nodes/mixins/caption.py +1 -1
  51. notionary/page/content/markdown/nodes/numbered_list.py +28 -5
  52. notionary/page/content/markdown/nodes/paragraph.py +1 -1
  53. notionary/page/content/markdown/nodes/pdf.py +1 -1
  54. notionary/page/content/markdown/nodes/quote.py +17 -5
  55. notionary/page/content/markdown/nodes/space.py +1 -1
  56. notionary/page/content/markdown/nodes/table.py +1 -1
  57. notionary/page/content/markdown/nodes/table_of_contents.py +1 -1
  58. notionary/page/content/markdown/nodes/todo.py +23 -7
  59. notionary/page/content/markdown/nodes/toggle.py +13 -14
  60. notionary/page/content/markdown/nodes/video.py +1 -1
  61. notionary/page/content/parser/context.py +98 -21
  62. notionary/page/content/parser/factory.py +1 -10
  63. notionary/page/content/parser/parsers/__init__.py +0 -2
  64. notionary/page/content/parser/parsers/audio.py +1 -1
  65. notionary/page/content/parser/parsers/base.py +1 -1
  66. notionary/page/content/parser/parsers/bookmark.py +1 -1
  67. notionary/page/content/parser/parsers/breadcrumb.py +1 -1
  68. notionary/page/content/parser/parsers/bulleted_list.py +52 -8
  69. notionary/page/content/parser/parsers/callout.py +55 -84
  70. notionary/page/content/parser/parsers/caption.py +1 -1
  71. notionary/page/content/parser/parsers/code.py +5 -5
  72. notionary/page/content/parser/parsers/column.py +23 -64
  73. notionary/page/content/parser/parsers/column_list.py +45 -45
  74. notionary/page/content/parser/parsers/divider.py +1 -1
  75. notionary/page/content/parser/parsers/embed.py +1 -1
  76. notionary/page/content/parser/parsers/equation.py +1 -1
  77. notionary/page/content/parser/parsers/file.py +1 -1
  78. notionary/page/content/parser/parsers/heading.py +65 -8
  79. notionary/page/content/parser/parsers/image.py +1 -1
  80. notionary/page/content/parser/parsers/numbered_list.py +52 -8
  81. notionary/page/content/parser/parsers/paragraph.py +3 -2
  82. notionary/page/content/parser/parsers/pdf.py +1 -1
  83. notionary/page/content/parser/parsers/quote.py +75 -15
  84. notionary/page/content/parser/parsers/space.py +14 -8
  85. notionary/page/content/parser/parsers/table.py +1 -1
  86. notionary/page/content/parser/parsers/table_of_contents.py +1 -1
  87. notionary/page/content/parser/parsers/todo.py +57 -19
  88. notionary/page/content/parser/parsers/toggle.py +17 -74
  89. notionary/page/content/parser/parsers/video.py +1 -1
  90. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +6 -4
  91. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +43 -22
  92. notionary/page/content/parser/pre_processsing/handlers/__init__.py +4 -0
  93. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +108 -54
  94. notionary/page/content/parser/pre_processsing/handlers/indentation.py +86 -0
  95. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
  96. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +14 -7
  97. notionary/page/content/parser/service.py +9 -0
  98. notionary/page/content/renderer/context.py +5 -2
  99. notionary/page/content/renderer/factory.py +2 -11
  100. notionary/page/content/renderer/post_processing/handlers/__init__.py +2 -2
  101. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
  102. notionary/page/content/renderer/renderers/__init__.py +0 -2
  103. notionary/page/content/renderer/renderers/base.py +1 -1
  104. notionary/page/content/renderer/renderers/bulleted_list.py +1 -1
  105. notionary/page/content/renderer/renderers/callout.py +6 -21
  106. notionary/page/content/renderer/renderers/captioned_block.py +1 -1
  107. notionary/page/content/renderer/renderers/column.py +28 -19
  108. notionary/page/content/renderer/renderers/column_list.py +24 -11
  109. notionary/page/content/renderer/renderers/heading.py +53 -27
  110. notionary/page/content/renderer/renderers/numbered_list.py +6 -5
  111. notionary/page/content/renderer/renderers/quote.py +1 -1
  112. notionary/page/content/renderer/renderers/todo.py +1 -1
  113. notionary/page/content/renderer/renderers/toggle.py +6 -7
  114. notionary/page/content/service.py +4 -1
  115. notionary/page/content/syntax/__init__.py +4 -0
  116. notionary/page/content/syntax/grammar.py +10 -0
  117. notionary/page/content/syntax/models.py +0 -2
  118. notionary/page/content/syntax/{service.py → registry.py} +31 -91
  119. notionary/page/properties/client.py +3 -3
  120. notionary/page/properties/models.py +3 -2
  121. notionary/page/properties/service.py +18 -3
  122. notionary/page/service.py +22 -80
  123. notionary/shared/entity/service.py +94 -36
  124. notionary/shared/models/cover.py +1 -1
  125. notionary/shared/typings.py +3 -0
  126. notionary/user/base.py +60 -11
  127. notionary/user/factory.py +0 -0
  128. notionary/utils/decorators.py +122 -0
  129. notionary/utils/fuzzy.py +18 -6
  130. notionary/utils/mixins/logging.py +38 -27
  131. notionary/utils/pagination.py +70 -16
  132. notionary/workspace/__init__.py +2 -1
  133. notionary/workspace/client.py +4 -2
  134. notionary/workspace/query/__init__.py +3 -0
  135. notionary/workspace/query/builder.py +25 -1
  136. notionary/workspace/query/models.py +12 -3
  137. notionary/workspace/query/service.py +57 -32
  138. notionary/workspace/service.py +31 -21
  139. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/METADATA +35 -105
  140. notionary-0.3.1.dist-info/RECORD +211 -0
  141. notionary/page/content/markdown/nodes/toggleable_heading.py +0 -35
  142. notionary/page/content/parser/parsers/toggleable_heading.py +0 -150
  143. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +0 -62
  144. notionary/page/content/renderer/renderers/toggleable_heading.py +0 -78
  145. notionary/utils/async_retry.py +0 -39
  146. notionary/utils/singleton.py +0 -13
  147. notionary-0.2.28.dist-info/RECORD +0 -200
  148. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/WHEEL +0 -0
  149. {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Self
7
7
  from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
8
8
  from notionary.data_source.http.client import DataSourceClient
9
9
  from notionary.data_source.http.data_source_instance_client import DataSourceInstanceClient
10
- from notionary.data_source.properties.models import (
10
+ from notionary.data_source.properties.schemas import (
11
11
  DataSourceMultiSelectProperty,
12
12
  DataSourceProperty,
13
13
  DataSourcePropertyOption,
@@ -16,26 +16,18 @@ from notionary.data_source.properties.models import (
16
16
  DataSourceSelectProperty,
17
17
  DataSourceStatusProperty,
18
18
  )
19
- from notionary.data_source.query.builder import DataSourceQueryBuilder
20
- from notionary.data_source.query.resolver import QueryResolver
21
- from notionary.data_source.query.schema import (
22
- DataSourceQueryParams,
23
- )
19
+ from notionary.data_source.query import DataSourceQueryBuilder, DataSourceQueryParams, QueryResolver
20
+ from notionary.data_source.schema.service import DataSourcePropertySchemaFormatter
24
21
  from notionary.data_source.schemas import DataSourceDto
25
22
  from notionary.exceptions.data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
26
23
  from notionary.page.properties.models import PageTitleProperty
27
24
  from notionary.page.schemas import NotionPageDto
28
25
  from notionary.shared.entity.dto_parsers import (
29
- extract_cover_image_url_from_dto,
30
- extract_database_id,
31
26
  extract_description,
32
- extract_emoji_icon_from_dto,
33
- extract_external_icon_url_from_dto,
34
27
  extract_title,
35
28
  )
36
29
  from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
37
30
  from notionary.shared.entity.service import Entity
38
- from notionary.user.schemas import PartialUserDto
39
31
  from notionary.workspace.query.service import WorkspaceQueryService
40
32
 
41
33
  if TYPE_CHECKING:
@@ -45,41 +37,21 @@ if TYPE_CHECKING:
45
37
  class NotionDataSource(Entity):
46
38
  def __init__(
47
39
  self,
48
- id: str,
40
+ dto: DataSourceDto,
49
41
  title: str,
50
- created_time: str,
51
- created_by: PartialUserDto,
52
- last_edited_time: str,
53
- last_edited_by: PartialUserDto,
54
- archived: bool,
55
- in_trash: bool,
42
+ description: str | None,
56
43
  properties: dict[str, DataSourceProperty],
57
- parent_database_id: str | None,
58
- emoji_icon: str | None = None,
59
- external_icon_url: str | None = None,
60
- cover_image_url: str | None = None,
61
- description: str | None = None,
62
- data_source_instance_client: DataSourceInstanceClient | None = None,
44
+ data_source_instance_client: DataSourceInstanceClient,
63
45
  query_resolver: QueryResolver | None = None,
64
46
  ) -> None:
65
- super().__init__(
66
- id=id,
67
- created_time=created_time,
68
- created_by=created_by,
69
- last_edited_time=last_edited_time,
70
- last_edited_by=last_edited_by,
71
- in_trash=in_trash,
72
- emoji_icon=emoji_icon,
73
- external_icon_url=external_icon_url,
74
- cover_image_url=cover_image_url,
75
- )
76
- self._parent_database_id = parent_database_id
47
+ super().__init__(dto=dto)
48
+
77
49
  self._parent_database: NotionDatabase | None = None
78
50
  self._title = title
79
- self._archived = archived
51
+ self._archived = dto.archived
80
52
  self._description = description
81
53
  self._properties = properties or {}
82
- self._data_source_client = data_source_instance_client or DataSourceInstanceClient(data_source_id=id)
54
+ self._data_source_client = data_source_instance_client
83
55
  self.query_resolver = query_resolver or QueryResolver()
84
56
 
85
57
  @classmethod
@@ -98,76 +70,29 @@ class NotionDataSource(Entity):
98
70
  async def from_title(
99
71
  cls,
100
72
  data_source_title: str,
101
- min_similarity: float = 0.6,
102
73
  search_service: WorkspaceQueryService | None = None,
103
74
  ) -> Self:
104
75
  service = search_service or WorkspaceQueryService()
105
- return await service.find_data_source(data_source_title, min_similarity=min_similarity)
76
+ return await service.find_data_source(data_source_title)
106
77
 
107
78
  @classmethod
108
79
  async def _create_from_dto(
109
80
  cls,
110
- response: DataSourceDto,
81
+ dto: DataSourceDto,
111
82
  rich_text_converter: RichTextToMarkdownConverter,
112
83
  ) -> Self:
113
84
  title, description = await asyncio.gather(
114
- extract_title(response, rich_text_converter),
115
- extract_description(response, rich_text_converter),
85
+ extract_title(dto, rich_text_converter),
86
+ extract_description(dto, rich_text_converter),
116
87
  )
117
88
 
118
- parent_database_id = extract_database_id(response)
119
-
120
- return cls._create(
121
- id=response.id,
122
- title=title,
123
- description=description,
124
- created_time=response.created_time,
125
- created_by=response.created_by,
126
- last_edited_time=response.last_edited_time,
127
- last_edited_by=response.last_edited_by,
128
- archived=response.archived,
129
- in_trash=response.in_trash,
130
- properties=response.properties,
131
- parent_database_id=parent_database_id,
132
- emoji_icon=extract_emoji_icon_from_dto(response),
133
- external_icon_url=extract_external_icon_url_from_dto(response),
134
- cover_image_url=extract_cover_image_url_from_dto(response),
135
- )
89
+ data_source_instance_client = DataSourceInstanceClient(data_source_id=dto.id)
136
90
 
137
- @classmethod
138
- def _create(
139
- cls,
140
- id: str,
141
- title: str,
142
- created_time: str,
143
- created_by: PartialUserDto,
144
- last_edited_time: str,
145
- last_edited_by: PartialUserDto,
146
- archived: bool,
147
- in_trash: bool,
148
- properties: dict[str, DataSourceProperty],
149
- parent_database_id: str | None,
150
- emoji_icon: str | None = None,
151
- external_icon_url: str | None = None,
152
- cover_image_url: str | None = None,
153
- description: str | None = None,
154
- ) -> Self:
155
- data_source_instance_client = DataSourceInstanceClient(data_source_id=id)
156
91
  return cls(
157
- id=id,
92
+ dto=dto,
158
93
  title=title,
159
- created_time=created_time,
160
- created_by=created_by,
161
- last_edited_time=last_edited_time,
162
- last_edited_by=last_edited_by,
163
- archived=archived,
164
- in_trash=in_trash,
165
- parent_database_id=parent_database_id,
166
- emoji_icon=emoji_icon,
167
- external_icon_url=external_icon_url,
168
- cover_image_url=cover_image_url,
169
94
  description=description,
170
- properties=properties,
95
+ properties=dto.properties,
171
96
  data_source_instance_client=data_source_instance_client,
172
97
  )
173
98
 
@@ -191,11 +116,6 @@ class NotionDataSource(Entity):
191
116
  def properties(self) -> dict[str, DataSourceProperty]:
192
117
  return self._properties
193
118
 
194
- async def get_parent_database(self) -> NotionDatabase | None:
195
- if self._parent_database is None and self._parent_database_id:
196
- self._parent_database = await NotionDatabase.from_id(self._parent_database_id)
197
- return self._parent_database
198
-
199
119
  async def create_blank_page(self, title: str | None = None) -> NotionPage:
200
120
  return await self._data_source_client.create_blank_page(title=title)
201
121
 
@@ -305,19 +225,21 @@ class NotionDataSource(Entity):
305
225
  def filter(self) -> DataSourceQueryBuilder:
306
226
  return DataSourceQueryBuilder(properties=self._properties)
307
227
 
308
- async def query(self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]) -> list[NotionPage]:
228
+ async def query_pages(
229
+ self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
230
+ ) -> list[NotionPage]:
309
231
  builder = DataSourceQueryBuilder(properties=self._properties)
310
- filter_fn(builder)
311
- query_params = builder.build()
232
+ configured_builder = filter_fn(builder)
233
+ query_params = configured_builder.build()
312
234
 
313
235
  return await self.get_pages(query_params)
314
236
 
315
- async def query_stream(
237
+ async def query_pages_stream(
316
238
  self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
317
239
  ) -> AsyncIterator[NotionPage]:
318
240
  builder = DataSourceQueryBuilder(properties=self._properties)
319
- filter_fn(builder)
320
- query_params = builder.build()
241
+ configured_builder = filter_fn(builder)
242
+ query_params = configured_builder.build()
321
243
 
322
244
  async for page in self.get_pages_stream(query_params):
323
245
  yield page
@@ -351,3 +273,7 @@ class NotionDataSource(Entity):
351
273
  return None
352
274
 
353
275
  return await self.query_resolver.resolve_params(query_params)
276
+
277
+ 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)
@@ -10,14 +10,10 @@ from notionary.database.client import NotionDatabaseHttpClient
10
10
  from notionary.database.database_metadata_update_client import DatabaseMetadataUpdateClient
11
11
  from notionary.database.schemas import NotionDatabaseDto
12
12
  from notionary.shared.entity.dto_parsers import (
13
- extract_cover_image_url_from_dto,
14
13
  extract_description,
15
- extract_emoji_icon_from_dto,
16
- extract_external_icon_url_from_dto,
17
14
  extract_title,
18
15
  )
19
16
  from notionary.shared.entity.service import Entity
20
- from notionary.user.schemas import PartialUserDto
21
17
  from notionary.workspace.query.service import WorkspaceQueryService
22
18
 
23
19
  type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
@@ -26,46 +22,24 @@ type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
26
22
  class NotionDatabase(Entity):
27
23
  def __init__(
28
24
  self,
29
- id: str,
25
+ dto: NotionDatabaseDto,
30
26
  title: str,
31
- created_time: str,
32
- created_by: PartialUserDto,
33
- last_edited_time: str,
34
- last_edited_by: PartialUserDto,
35
- url: str,
36
- in_trash: bool,
37
- is_inline: bool,
27
+ description: str | None,
38
28
  data_source_ids: list[str],
39
- public_url: str | None = None,
40
- emoji_icon: str | None = None,
41
- external_icon_url: str | None = None,
42
- cover_image_url: str | None = None,
43
- description: str | None = None,
44
- client: NotionDatabaseHttpClient | None = None,
45
- metadata_update_client: DatabaseMetadataUpdateClient | None = None,
29
+ client: NotionDatabaseHttpClient,
30
+ metadata_update_client: DatabaseMetadataUpdateClient,
46
31
  ) -> None:
47
- super().__init__(
48
- id=id,
49
- created_time=created_time,
50
- created_by=created_by,
51
- last_edited_time=last_edited_time,
52
- last_edited_by=last_edited_by,
53
- in_trash=in_trash,
54
- emoji_icon=emoji_icon,
55
- external_icon_url=external_icon_url,
56
- cover_image_url=cover_image_url,
57
- )
32
+ super().__init__(dto=dto)
33
+
58
34
  self._title = title
59
- self._url = url
60
- self._public_url = public_url
61
35
  self._description = description
62
- self._is_inline = is_inline
36
+ self._is_inline = dto.is_inline
63
37
 
64
38
  self._data_sources: list[NotionDataSource] | None = None
65
39
  self._data_source_ids = data_source_ids
66
40
 
67
- self.client = client or NotionDatabaseHttpClient(database_id=id)
68
- self._metadata_update_client = metadata_update_client or DatabaseMetadataUpdateClient(database_id=id)
41
+ self.client = client
42
+ self._metadata_update_client = metadata_update_client
69
43
 
70
44
  @classmethod
71
45
  async def from_id(
@@ -86,40 +60,31 @@ class NotionDatabase(Entity):
86
60
  async def from_title(
87
61
  cls,
88
62
  database_title: str,
89
- min_similarity: float = 0.6,
90
63
  search_service: WorkspaceQueryService | None = None,
91
64
  ) -> Self:
92
65
  service = search_service or WorkspaceQueryService()
93
- return await service.find_database(database_title, min_similarity=min_similarity)
66
+ return await service.find_database(database_title)
94
67
 
95
68
  @classmethod
96
69
  async def _create_from_dto(
97
70
  cls,
98
- response: NotionDatabaseDto,
71
+ dto: NotionDatabaseDto,
99
72
  rich_text_converter: RichTextToMarkdownConverter,
100
73
  client: NotionDatabaseHttpClient,
101
74
  ) -> Self:
102
75
  title, description = await asyncio.gather(
103
- extract_title(response, rich_text_converter), extract_description(response, rich_text_converter)
76
+ extract_title(dto, rich_text_converter), extract_description(dto, rich_text_converter)
104
77
  )
105
78
 
79
+ metadata_update_client = DatabaseMetadataUpdateClient(database_id=dto.id)
80
+
106
81
  return cls(
107
- id=response.id,
82
+ dto=dto,
108
83
  title=title,
109
84
  description=description,
110
- created_time=response.created_time,
111
- created_by=response.created_by,
112
- last_edited_time=response.last_edited_time,
113
- last_edited_by=response.last_edited_by,
114
- in_trash=response.in_trash,
115
- is_inline=response.is_inline,
116
- url=response.url,
117
- public_url=response.public_url,
118
- emoji_icon=extract_emoji_icon_from_dto(response),
119
- external_icon_url=extract_external_icon_url_from_dto(response),
120
- cover_image_url=extract_cover_image_url_from_dto(response),
121
- data_source_ids=[ds.id for ds in response.data_sources],
85
+ data_source_ids=[ds.id for ds in dto.data_sources],
122
86
  client=client,
87
+ metadata_update_client=metadata_update_client,
123
88
  )
124
89
 
125
90
  @property
@@ -130,14 +95,6 @@ class NotionDatabase(Entity):
130
95
  def title(self) -> str:
131
96
  return self._title
132
97
 
133
- @property
134
- def url(self) -> str:
135
- return self._url
136
-
137
- @property
138
- def public_url(self) -> str | None:
139
- return self._public_url
140
-
141
98
  @property
142
99
  def is_inline(self) -> bool:
143
100
  return self._is_inline
@@ -8,7 +8,8 @@ from .api import (
8
8
  NotionValidationError,
9
9
  )
10
10
  from .base import NotionaryError
11
- from .data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
11
+ from .block_parsing import InsufficientColumnsError, InvalidColumnRatioSumError, UnsupportedVideoFormatError
12
+ from .data_source import DataSourcePropertyNotFound, DataSourcePropertyTypeError
12
13
  from .properties import AccessPagePropertyWithoutDataSourceError, PagePropertyNotFoundError, PagePropertyTypeError
13
14
  from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
14
15
 
@@ -19,6 +20,8 @@ __all__ = [
19
20
  "DataSourcePropertyTypeError",
20
21
  "DatabaseNotFound",
21
22
  "EntityNotFound",
23
+ "InsufficientColumnsError",
24
+ "InvalidColumnRatioSumError",
22
25
  "NotionApiError",
23
26
  "NotionAuthenticationError",
24
27
  "NotionConnectionError",
@@ -30,4 +33,5 @@ __all__ = [
30
33
  "PageNotFound",
31
34
  "PagePropertyNotFoundError",
32
35
  "PagePropertyTypeError",
36
+ "UnsupportedVideoFormatError",
33
37
  ]
@@ -14,3 +14,24 @@ class InvalidColumnRatioSumError(NotionaryError):
14
14
  self.total = total
15
15
  self.tolerance = tolerance
16
16
  super().__init__(f"Width ratios must sum to 1.0 (±{tolerance}), but sum is {total}")
17
+
18
+
19
+ class UnsupportedVideoFormatError(ValueError):
20
+ def __init__(self, url: str, supported_formats: list[str]) -> None:
21
+ self.url = url
22
+ self.supported_formats = supported_formats
23
+ super().__init__(self._create_user_friendly_message())
24
+
25
+ def _create_user_friendly_message(self) -> str:
26
+ formats = ", ".join(self.supported_formats[:5])
27
+ remaining = len(self.supported_formats) - 5
28
+
29
+ if remaining > 0:
30
+ formats += f" and {remaining} more"
31
+
32
+ return (
33
+ f"The video URL '{self.url}' uses an unsupported format.\n"
34
+ f"Supported formats include: {formats}.\n"
35
+ f"YouTube embed and watch URLs are also supported."
36
+ f"Also see https://developers.notion.com/reference/block#video for more information."
37
+ )
@@ -31,3 +31,27 @@ class DataSourceNotFound(EntityNotFound):
31
31
  class DatabaseNotFound(EntityNotFound):
32
32
  def __init__(self, query: str, available_titles: list[str] | None = None) -> None:
33
33
  super().__init__("database", query, available_titles)
34
+
35
+
36
+ class NoUsersInWorkspace(NotionaryError):
37
+ def __init__(self, user_type: str) -> None:
38
+ self.user_type = user_type
39
+ message = f"No '{user_type}' users found in the workspace."
40
+ super().__init__(message)
41
+
42
+
43
+ class UserNotFound(NotionaryError):
44
+ def __init__(self, user_type: str, query: str, available_names: list[str] | None = None) -> None:
45
+ self.user_type = user_type
46
+ self.query = query
47
+ self.available_names = available_names or []
48
+
49
+ if self.available_names:
50
+ message = (
51
+ f"No '{user_type}' user found with exact name '{query}'. "
52
+ f"Did you mean one of these? {self.available_names}"
53
+ )
54
+ else:
55
+ message = f"No '{user_type}' user found with name '{query}'."
56
+
57
+ super().__init__(message)
notionary/http/client.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import os
3
- from typing import Any
4
3
 
5
4
  import httpx
6
5
  from dotenv import load_dotenv
@@ -16,6 +15,7 @@ from notionary.exceptions.api import (
16
15
  NotionValidationError,
17
16
  )
18
17
  from notionary.http.models import HttpMethod
18
+ from notionary.shared.typings import JsonDict
19
19
  from notionary.utils.mixins.logging import LoggingMixin
20
20
 
21
21
  load_dotenv()
@@ -80,25 +80,25 @@ class NotionHttpClient(LoggingMixin):
80
80
  self._is_initialized = False
81
81
  self.logger.debug("NotionHttpClient closed")
82
82
 
83
- async def get(self, endpoint: str, params: dict[str, Any] | None = None) -> dict[str, Any] | None:
83
+ async def get(self, endpoint: str, params: JsonDict | None = None) -> JsonDict | None:
84
84
  return await self.make_request(HttpMethod.GET, endpoint, params=params)
85
85
 
86
- async def post(self, endpoint: str, data: dict[str, Any] | None = None) -> dict[str, Any] | None:
86
+ async def post(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
87
87
  return await self.make_request(HttpMethod.POST, endpoint, data)
88
88
 
89
- async def patch(self, endpoint: str, data: dict[str, Any] | None = None) -> dict[str, Any] | None:
89
+ async def patch(self, endpoint: str, data: JsonDict | None = None) -> JsonDict | None:
90
90
  return await self.make_request(HttpMethod.PATCH, endpoint, data)
91
91
 
92
- async def delete(self, endpoint: str) -> dict[str, Any] | None:
92
+ async def delete(self, endpoint: str) -> JsonDict | None:
93
93
  return await self.make_request(HttpMethod.DELETE, endpoint)
94
94
 
95
95
  async def make_request(
96
96
  self,
97
97
  method: HttpMethod,
98
98
  endpoint: str,
99
- data: dict[str, Any] | None = None,
100
- params: dict[str, Any] | None = None,
101
- ) -> dict[str, Any] | None:
99
+ data: JsonDict | None = None,
100
+ params: JsonDict | None = None,
101
+ ) -> JsonDict | None:
102
102
  """
103
103
  Executes an HTTP request and returns the data or None on error.
104
104
 
@@ -158,7 +158,7 @@ class NotionHttpClient(LoggingMixin):
158
158
  )
159
159
  if status_code == 404:
160
160
  raise NotionResourceNotFoundError(
161
- "The requested resource was not found. Please verify the page/database ID.",
161
+ "The requested resource was not found. Please verify the page/database/datasource ID.",
162
162
  status_code=status_code,
163
163
  response_text=response_text,
164
164
  )
@@ -193,7 +193,6 @@ class NotionHttpClient(LoggingMixin):
193
193
  None,
194
194
  )
195
195
  if token:
196
- self.logger.debug("Found token in environment variable.")
197
196
  return token
198
197
  self.logger.warning("No Notion API token found in environment variables")
199
198
  return None
notionary/http/models.py CHANGED
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import time
4
4
  from dataclasses import dataclass, field
5
5
  from enum import StrEnum
6
- from typing import Any
6
+
7
+ from notionary.shared.typings import JsonDict
7
8
 
8
9
 
9
10
  class HttpMethod(StrEnum):
@@ -17,8 +18,8 @@ class HttpMethod(StrEnum):
17
18
  class HttpRequest:
18
19
  method: HttpMethod
19
20
  endpoint: str
20
- data: dict[str, Any] | None = None
21
- params: dict[str, Any] | None = None
21
+ data: JsonDict | None = None
22
+ params: JsonDict | None = None
22
23
  timestamp: float = field(default_factory=time.time)
23
24
  cached_response: HttpResponse | None = None
24
25
 
@@ -38,7 +39,7 @@ class HttpRequest:
38
39
 
39
40
  @dataclass
40
41
  class HttpResponse:
41
- data: dict[str, Any] | None
42
+ data: JsonDict | None
42
43
  status_code: int = 200
43
44
  headers: dict[str, str] = field(default_factory=dict)
44
45
  timestamp: float = field(default_factory=time.time)
@@ -2,11 +2,16 @@ from notionary.blocks.client import NotionBlockHttpClient
2
2
  from notionary.page.content.parser.factory import ConverterChainFactory
3
3
  from notionary.page.content.parser.post_processing.handlers import RichTextLengthTruncationPostProcessor
4
4
  from notionary.page.content.parser.post_processing.service import BlockPostProcessor
5
- from notionary.page.content.parser.pre_processsing.handlers import ColumnSyntaxPreProcessor, WhitespacePreProcessor
5
+ from notionary.page.content.parser.pre_processsing.handlers import (
6
+ ColumnSyntaxPreProcessor,
7
+ IndentationNormalizer,
8
+ VideoFormatPreProcessor,
9
+ WhitespacePreProcessor,
10
+ )
6
11
  from notionary.page.content.parser.pre_processsing.service import MarkdownPreProcessor
7
12
  from notionary.page.content.parser.service import MarkdownToNotionConverter
8
13
  from notionary.page.content.renderer.factory import RendererChainFactory
9
- from notionary.page.content.renderer.post_processing.handlers import NumberedListPlaceholderReplaceerPostProcessor
14
+ from notionary.page.content.renderer.post_processing.handlers import NumberedListPlaceholderReplacerPostProcessor
10
15
  from notionary.page.content.renderer.post_processing.service import MarkdownRenderingPostProcessor
11
16
  from notionary.page.content.renderer.service import NotionToMarkdownConverter
12
17
  from notionary.page.content.service import PageContentService
@@ -55,6 +60,8 @@ class PageContentServiceFactory:
55
60
  pre_processor = MarkdownPreProcessor()
56
61
  pre_processor.register(ColumnSyntaxPreProcessor())
57
62
  pre_processor.register(WhitespacePreProcessor())
63
+ pre_processor.register(IndentationNormalizer())
64
+ pre_processor.register(VideoFormatPreProcessor())
58
65
  return pre_processor
59
66
 
60
67
  def _create_post_processor(self) -> BlockPostProcessor:
@@ -64,5 +71,5 @@ class PageContentServiceFactory:
64
71
 
65
72
  def _create_markdown_rendering_post_processor(self) -> MarkdownRenderingPostProcessor:
66
73
  post_processor = MarkdownRenderingPostProcessor()
67
- post_processor.register(NumberedListPlaceholderReplaceerPostProcessor())
74
+ post_processor.register(NumberedListPlaceholderReplacerPostProcessor())
68
75
  return post_processor