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.
- notionary/__init__.py +9 -2
- notionary/blocks/__init__.py +5 -0
- notionary/blocks/client.py +6 -4
- notionary/blocks/enums.py +28 -1
- notionary/blocks/rich_text/markdown_rich_text_converter.py +14 -0
- notionary/blocks/rich_text/models.py +14 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +2 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +12 -0
- notionary/blocks/rich_text/rich_text_patterns.py +3 -0
- notionary/blocks/schemas.py +42 -10
- notionary/comments/__init__.py +5 -0
- notionary/comments/client.py +7 -10
- notionary/comments/factory.py +4 -6
- notionary/data_source/http/data_source_instance_client.py +14 -4
- notionary/data_source/properties/{models.py → schemas.py} +4 -8
- notionary/data_source/query/__init__.py +9 -0
- notionary/data_source/query/builder.py +38 -10
- notionary/data_source/query/schema.py +13 -10
- notionary/data_source/query/validator.py +11 -11
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +1 -1
- notionary/data_source/service.py +29 -103
- notionary/database/service.py +17 -60
- notionary/exceptions/__init__.py +5 -1
- notionary/exceptions/block_parsing.py +21 -0
- notionary/exceptions/search.py +24 -0
- notionary/http/client.py +9 -10
- notionary/http/models.py +5 -4
- notionary/page/content/factory.py +10 -3
- notionary/page/content/markdown/builder.py +76 -154
- notionary/page/content/markdown/nodes/__init__.py +0 -2
- notionary/page/content/markdown/nodes/audio.py +1 -1
- notionary/page/content/markdown/nodes/base.py +1 -1
- notionary/page/content/markdown/nodes/bookmark.py +1 -1
- notionary/page/content/markdown/nodes/breadcrumb.py +1 -1
- notionary/page/content/markdown/nodes/bulleted_list.py +31 -8
- notionary/page/content/markdown/nodes/callout.py +12 -10
- notionary/page/content/markdown/nodes/code.py +3 -5
- notionary/page/content/markdown/nodes/columns.py +39 -21
- notionary/page/content/markdown/nodes/container.py +64 -0
- notionary/page/content/markdown/nodes/divider.py +1 -1
- notionary/page/content/markdown/nodes/embed.py +1 -1
- notionary/page/content/markdown/nodes/equation.py +1 -1
- notionary/page/content/markdown/nodes/file.py +1 -1
- notionary/page/content/markdown/nodes/heading.py +26 -6
- notionary/page/content/markdown/nodes/image.py +1 -1
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +1 -1
- notionary/page/content/markdown/nodes/numbered_list.py +28 -5
- notionary/page/content/markdown/nodes/paragraph.py +1 -1
- notionary/page/content/markdown/nodes/pdf.py +1 -1
- notionary/page/content/markdown/nodes/quote.py +17 -5
- notionary/page/content/markdown/nodes/space.py +1 -1
- notionary/page/content/markdown/nodes/table.py +1 -1
- notionary/page/content/markdown/nodes/table_of_contents.py +1 -1
- notionary/page/content/markdown/nodes/todo.py +23 -7
- notionary/page/content/markdown/nodes/toggle.py +13 -14
- notionary/page/content/markdown/nodes/video.py +1 -1
- notionary/page/content/parser/context.py +98 -21
- notionary/page/content/parser/factory.py +1 -10
- notionary/page/content/parser/parsers/__init__.py +0 -2
- notionary/page/content/parser/parsers/audio.py +1 -1
- notionary/page/content/parser/parsers/base.py +1 -1
- notionary/page/content/parser/parsers/bookmark.py +1 -1
- notionary/page/content/parser/parsers/breadcrumb.py +1 -1
- notionary/page/content/parser/parsers/bulleted_list.py +52 -8
- notionary/page/content/parser/parsers/callout.py +55 -84
- notionary/page/content/parser/parsers/caption.py +1 -1
- notionary/page/content/parser/parsers/code.py +5 -5
- notionary/page/content/parser/parsers/column.py +23 -64
- notionary/page/content/parser/parsers/column_list.py +45 -45
- notionary/page/content/parser/parsers/divider.py +1 -1
- notionary/page/content/parser/parsers/embed.py +1 -1
- notionary/page/content/parser/parsers/equation.py +1 -1
- notionary/page/content/parser/parsers/file.py +1 -1
- notionary/page/content/parser/parsers/heading.py +65 -8
- notionary/page/content/parser/parsers/image.py +1 -1
- notionary/page/content/parser/parsers/numbered_list.py +52 -8
- notionary/page/content/parser/parsers/paragraph.py +3 -2
- notionary/page/content/parser/parsers/pdf.py +1 -1
- notionary/page/content/parser/parsers/quote.py +75 -15
- notionary/page/content/parser/parsers/space.py +14 -8
- notionary/page/content/parser/parsers/table.py +1 -1
- notionary/page/content/parser/parsers/table_of_contents.py +1 -1
- notionary/page/content/parser/parsers/todo.py +57 -19
- notionary/page/content/parser/parsers/toggle.py +17 -74
- notionary/page/content/parser/parsers/video.py +1 -1
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +6 -4
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +43 -22
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +4 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +108 -54
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +86 -0
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +14 -7
- notionary/page/content/parser/service.py +9 -0
- notionary/page/content/renderer/context.py +5 -2
- notionary/page/content/renderer/factory.py +2 -11
- notionary/page/content/renderer/post_processing/handlers/__init__.py +2 -2
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
- notionary/page/content/renderer/renderers/__init__.py +0 -2
- notionary/page/content/renderer/renderers/base.py +1 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +1 -1
- notionary/page/content/renderer/renderers/callout.py +6 -21
- notionary/page/content/renderer/renderers/captioned_block.py +1 -1
- notionary/page/content/renderer/renderers/column.py +28 -19
- notionary/page/content/renderer/renderers/column_list.py +24 -11
- notionary/page/content/renderer/renderers/heading.py +53 -27
- notionary/page/content/renderer/renderers/numbered_list.py +6 -5
- notionary/page/content/renderer/renderers/quote.py +1 -1
- notionary/page/content/renderer/renderers/todo.py +1 -1
- notionary/page/content/renderer/renderers/toggle.py +6 -7
- notionary/page/content/service.py +4 -1
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +0 -2
- notionary/page/content/syntax/{service.py → registry.py} +31 -91
- notionary/page/properties/client.py +3 -3
- notionary/page/properties/models.py +3 -2
- notionary/page/properties/service.py +18 -3
- notionary/page/service.py +22 -80
- notionary/shared/entity/service.py +94 -36
- notionary/shared/models/cover.py +1 -1
- notionary/shared/typings.py +3 -0
- notionary/user/base.py +60 -11
- notionary/user/factory.py +0 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +18 -6
- notionary/utils/mixins/logging.py +38 -27
- notionary/utils/pagination.py +70 -16
- notionary/workspace/__init__.py +2 -1
- notionary/workspace/client.py +4 -2
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +25 -1
- notionary/workspace/query/models.py +12 -3
- notionary/workspace/query/service.py +57 -32
- notionary/workspace/service.py +31 -21
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/METADATA +35 -105
- notionary-0.3.1.dist-info/RECORD +211 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +0 -35
- notionary/page/content/parser/parsers/toggleable_heading.py +0 -150
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +0 -62
- notionary/page/content/renderer/renderers/toggleable_heading.py +0 -78
- notionary/utils/async_retry.py +0 -39
- notionary/utils/singleton.py +0 -13
- notionary-0.2.28.dist-info/RECORD +0 -200
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/WHEEL +0 -0
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/licenses/LICENSE +0 -0
notionary/__init__.py
CHANGED
|
@@ -2,6 +2,13 @@ from .data_source.service import NotionDataSource
|
|
|
2
2
|
from .database.service import NotionDatabase
|
|
3
3
|
from .page.content.markdown.builder import MarkdownBuilder
|
|
4
4
|
from .page.service import NotionPage
|
|
5
|
-
from .workspace import NotionWorkspace
|
|
5
|
+
from .workspace import NotionWorkspace, NotionWorkspaceQueryConfigBuilder
|
|
6
6
|
|
|
7
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"MarkdownBuilder",
|
|
9
|
+
"NotionDataSource",
|
|
10
|
+
"NotionDatabase",
|
|
11
|
+
"NotionPage",
|
|
12
|
+
"NotionWorkspace",
|
|
13
|
+
"NotionWorkspaceQueryConfigBuilder",
|
|
14
|
+
]
|
notionary/blocks/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
1
|
from notionary.blocks.schemas import Block, BlockChildrenResponse, BlockCreatePayload
|
|
4
2
|
from notionary.http.client import NotionHttpClient
|
|
3
|
+
from notionary.shared.typings import JsonDict
|
|
4
|
+
from notionary.utils.decorators import time_execution_async
|
|
5
5
|
from notionary.utils.pagination import paginate_notion_api
|
|
6
6
|
|
|
7
7
|
|
|
@@ -16,6 +16,7 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
16
16
|
self.logger.debug("Deleting block: %s", block_id)
|
|
17
17
|
await self.delete(f"blocks/{block_id}")
|
|
18
18
|
|
|
19
|
+
@time_execution_async()
|
|
19
20
|
async def get_block_tree(self, parent_block_id: str) -> list[Block]:
|
|
20
21
|
blocks_at_this_level = await self.get_all_block_children(parent_block_id)
|
|
21
22
|
|
|
@@ -26,6 +27,7 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
26
27
|
|
|
27
28
|
return blocks_at_this_level
|
|
28
29
|
|
|
30
|
+
@time_execution_async()
|
|
29
31
|
async def get_all_block_children(self, parent_block_id: str) -> list[Block]:
|
|
30
32
|
self.logger.debug("Retrieving all children for block: %s", parent_block_id)
|
|
31
33
|
|
|
@@ -73,11 +75,11 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
73
75
|
batches.append(batch)
|
|
74
76
|
return batches
|
|
75
77
|
|
|
76
|
-
def _serialize_blocks(self, blocks: list[BlockCreatePayload]) -> list[
|
|
78
|
+
def _serialize_blocks(self, blocks: list[BlockCreatePayload]) -> list[JsonDict]:
|
|
77
79
|
return [block.model_dump(exclude_none=True) for block in blocks]
|
|
78
80
|
|
|
79
81
|
async def _send_append_request(
|
|
80
|
-
self, block_id: str, children: list[
|
|
82
|
+
self, block_id: str, children: list[JsonDict], after_block_id: str | None = None
|
|
81
83
|
) -> BlockChildrenResponse:
|
|
82
84
|
payload = {"children": children}
|
|
83
85
|
if after_block_id:
|
notionary/blocks/enums.py
CHANGED
|
@@ -68,7 +68,7 @@ class FileType(StrEnum):
|
|
|
68
68
|
FILE_UPLOAD = "file_upload"
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
class
|
|
71
|
+
class CodingLanguage(StrEnum):
|
|
72
72
|
ABAP = "abap"
|
|
73
73
|
ARDUINO = "arduino"
|
|
74
74
|
BASH = "bash"
|
|
@@ -165,3 +165,30 @@ class CodeLanguage(StrEnum):
|
|
|
165
165
|
return member
|
|
166
166
|
|
|
167
167
|
return default if default is not None else cls.PLAIN_TEXT
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class VideoFileType(StrEnum):
|
|
171
|
+
AMV = ".amv"
|
|
172
|
+
ASF = ".asf"
|
|
173
|
+
AVI = ".avi"
|
|
174
|
+
F4V = ".f4v"
|
|
175
|
+
FLV = ".flv"
|
|
176
|
+
GIFV = ".gifv"
|
|
177
|
+
MKV = ".mkv"
|
|
178
|
+
MOV = ".mov"
|
|
179
|
+
MPG = ".mpg"
|
|
180
|
+
MPEG = ".mpeg"
|
|
181
|
+
MPV = ".mpv"
|
|
182
|
+
MP4 = ".mp4"
|
|
183
|
+
M4V = ".m4v"
|
|
184
|
+
QT = ".qt"
|
|
185
|
+
WMV = ".wmv"
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def get_all_extensions(cls) -> set[str]:
|
|
189
|
+
return {ext.value for ext in cls}
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def is_valid_extension(cls, filename: str) -> bool:
|
|
193
|
+
lower_filename = filename.lower()
|
|
194
|
+
return any(lower_filename.endswith(ext.value) for ext in cls)
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from notionary.blocks.rich_text.models import MentionType, RichText, RichTextType, TextAnnotations
|
|
8
8
|
from notionary.blocks.rich_text.name_id_resolver import (
|
|
9
9
|
DatabaseNameIdResolver,
|
|
10
|
+
DataSourceNameIdResolver,
|
|
10
11
|
NameIdResolver,
|
|
11
12
|
PageNameIdResolver,
|
|
12
13
|
PersonNameIdResolver,
|
|
@@ -44,10 +45,12 @@ class MarkdownRichTextConverter:
|
|
|
44
45
|
*,
|
|
45
46
|
page_resolver: NameIdResolver | None = None,
|
|
46
47
|
database_resolver: NameIdResolver | None = None,
|
|
48
|
+
data_source_resolver: NameIdResolver | None = None,
|
|
47
49
|
person_resolver: NameIdResolver | None = None,
|
|
48
50
|
):
|
|
49
51
|
self.page_resolver = page_resolver or PageNameIdResolver()
|
|
50
52
|
self.database_resolver = database_resolver or DatabaseNameIdResolver()
|
|
53
|
+
self.data_source_resolver = data_source_resolver or DataSourceNameIdResolver()
|
|
51
54
|
self.person_resolver = person_resolver or PersonNameIdResolver()
|
|
52
55
|
self.format_handlers = self._setup_format_handlers()
|
|
53
56
|
|
|
@@ -64,6 +67,7 @@ class MarkdownRichTextConverter:
|
|
|
64
67
|
PatternHandler(RichTextPatterns.COLOR, self._handle_color_pattern),
|
|
65
68
|
PatternHandler(RichTextPatterns.PAGE_MENTION, self._handle_page_mention_pattern),
|
|
66
69
|
PatternHandler(RichTextPatterns.DATABASE_MENTION, self._handle_database_mention_pattern),
|
|
70
|
+
PatternHandler(RichTextPatterns.DATASOURCE_MENTION, self._handle_data_source_mention_pattern),
|
|
67
71
|
PatternHandler(RichTextPatterns.USER_MENTION, self._handle_user_mention_pattern),
|
|
68
72
|
]
|
|
69
73
|
|
|
@@ -119,6 +123,7 @@ class MarkdownRichTextConverter:
|
|
|
119
123
|
async_handlers = {
|
|
120
124
|
self._handle_page_mention_pattern,
|
|
121
125
|
self._handle_database_mention_pattern,
|
|
126
|
+
self._handle_data_source_mention_pattern,
|
|
122
127
|
self._handle_color_pattern, # Color pattern needs async for recursive parsing
|
|
123
128
|
self._handle_user_mention_pattern,
|
|
124
129
|
}
|
|
@@ -210,6 +215,15 @@ class MarkdownRichTextConverter:
|
|
|
210
215
|
mention_type=MentionType.DATABASE,
|
|
211
216
|
)
|
|
212
217
|
|
|
218
|
+
async def _handle_data_source_mention_pattern(self, match: Match) -> RichText:
|
|
219
|
+
identifier = match.group(1)
|
|
220
|
+
return await self._create_mention_or_fallback(
|
|
221
|
+
identifier=identifier,
|
|
222
|
+
resolve_func=self.data_source_resolver.resolve_name_to_id,
|
|
223
|
+
create_mention_func=RichText.mention_data_source,
|
|
224
|
+
mention_type=MentionType.DATASOURCE,
|
|
225
|
+
)
|
|
226
|
+
|
|
213
227
|
async def _handle_user_mention_pattern(self, match: Match) -> RichText:
|
|
214
228
|
identifier = match.group(1)
|
|
215
229
|
return await self._create_mention_or_fallback(
|
|
@@ -16,6 +16,7 @@ class MentionType(StrEnum):
|
|
|
16
16
|
USER = "user"
|
|
17
17
|
PAGE = "page"
|
|
18
18
|
DATABASE = "database"
|
|
19
|
+
DATASOURCE = "data_source"
|
|
19
20
|
DATE = "date"
|
|
20
21
|
LINK_PREVIEW = "link_preview"
|
|
21
22
|
TEMPLATE_MENTION = "template_mention"
|
|
@@ -60,6 +61,10 @@ class MentionDatabaseRef(BaseModel):
|
|
|
60
61
|
id: str
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
class MentionDataSourceRef(BaseModel):
|
|
65
|
+
id: str
|
|
66
|
+
|
|
67
|
+
|
|
63
68
|
class MentionLinkPreview(BaseModel):
|
|
64
69
|
url: str
|
|
65
70
|
|
|
@@ -81,6 +86,7 @@ class MentionObject(BaseModel):
|
|
|
81
86
|
user: MentionUserRef | None = None
|
|
82
87
|
page: MentionPageRef | None = None
|
|
83
88
|
database: MentionDatabaseRef | None = None
|
|
89
|
+
data_source: MentionDataSourceRef | None = None
|
|
84
90
|
date: MentionDate | None = None
|
|
85
91
|
link_preview: MentionLinkPreview | None = None
|
|
86
92
|
template_mention: MentionTemplateMention | None = None
|
|
@@ -154,6 +160,14 @@ class RichText(BaseModel):
|
|
|
154
160
|
annotations=TextAnnotations(),
|
|
155
161
|
)
|
|
156
162
|
|
|
163
|
+
@classmethod
|
|
164
|
+
def mention_data_source(cls, data_source_id: str) -> Self:
|
|
165
|
+
return cls(
|
|
166
|
+
type=RichTextType.MENTION,
|
|
167
|
+
mention=MentionObject(type=MentionType.DATASOURCE, data_source=MentionDataSourceRef(id=data_source_id)),
|
|
168
|
+
annotations=TextAnnotations(),
|
|
169
|
+
)
|
|
170
|
+
|
|
157
171
|
@classmethod
|
|
158
172
|
def equation_inline(cls, expression: str) -> Self:
|
|
159
173
|
return cls(
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from .data_source import DataSourceNameIdResolver
|
|
1
2
|
from .database import DatabaseNameIdResolver
|
|
2
3
|
from .page import PageNameIdResolver
|
|
3
4
|
from .person import PersonNameIdResolver
|
|
4
5
|
from .port import NameIdResolver
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
8
|
+
"DataSourceNameIdResolver",
|
|
7
9
|
"DatabaseNameIdResolver",
|
|
8
10
|
"NameIdResolver",
|
|
9
11
|
"PageNameIdResolver",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.name_id_resolver.port import NameIdResolver
|
|
4
|
+
from notionary.workspace.query.service import WorkspaceQueryService
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# !!! in the notion api mentions that reference datasources are not provided yet (it's a limiation of the API as of now)
|
|
8
|
+
class DataSourceNameIdResolver(NameIdResolver):
|
|
9
|
+
def __init__(self, workspace_query_service: WorkspaceQueryService | None = None) -> None:
|
|
10
|
+
self._workspace_query_service = workspace_query_service or WorkspaceQueryService()
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
async def resolve_name_to_id(self, name: str) -> str | None:
|
|
14
|
+
if not name:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
cleaned_name = name.strip()
|
|
18
|
+
data_source = await self._workspace_query_service.find_data_source(query=cleaned_name)
|
|
19
|
+
return data_source.id if data_source else None
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
async def resolve_id_to_name(self, data_source_id: str) -> str | None:
|
|
23
|
+
if not data_source_id:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from notionary import NotionDataSource
|
|
28
|
+
|
|
29
|
+
data_source = await NotionDataSource.from_id(data_source_id)
|
|
30
|
+
return data_source.title if data_source else None
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
@@ -8,6 +8,7 @@ from notionary.blocks.rich_text.models import (
|
|
|
8
8
|
)
|
|
9
9
|
from notionary.blocks.rich_text.name_id_resolver import (
|
|
10
10
|
DatabaseNameIdResolver,
|
|
11
|
+
DataSourceNameIdResolver,
|
|
11
12
|
NameIdResolver,
|
|
12
13
|
PageNameIdResolver,
|
|
13
14
|
PersonNameIdResolver,
|
|
@@ -23,10 +24,12 @@ class RichTextToMarkdownConverter:
|
|
|
23
24
|
*,
|
|
24
25
|
page_resolver: NameIdResolver | None = None,
|
|
25
26
|
database_resolver: NameIdResolver | None = None,
|
|
27
|
+
data_source_resolver: NameIdResolver | None = None,
|
|
26
28
|
person_resolver: NameIdResolver | None = None,
|
|
27
29
|
) -> None:
|
|
28
30
|
self.page_resolver = page_resolver or PageNameIdResolver()
|
|
29
31
|
self.database_resolver = database_resolver or DatabaseNameIdResolver()
|
|
32
|
+
self.data_source_resolver = data_source_resolver or DataSourceNameIdResolver()
|
|
30
33
|
self.person_resolver = person_resolver or PersonNameIdResolver()
|
|
31
34
|
|
|
32
35
|
async def to_markdown(self, rich_text: list[RichText]) -> str:
|
|
@@ -65,6 +68,9 @@ class RichTextToMarkdownConverter:
|
|
|
65
68
|
elif mention.type == MentionType.DATABASE and mention.database:
|
|
66
69
|
return await self._extract_database_mention_markdown(mention.database.id)
|
|
67
70
|
|
|
71
|
+
elif mention.type == MentionType.DATASOURCE and mention.data_source:
|
|
72
|
+
return await self._extract_data_source_mention_markdown(mention.data_source.id)
|
|
73
|
+
|
|
68
74
|
elif mention.type == MentionType.USER and mention.user:
|
|
69
75
|
return await self._extract_user_mention_markdown(mention.user.id)
|
|
70
76
|
|
|
@@ -81,6 +87,10 @@ class RichTextToMarkdownConverter:
|
|
|
81
87
|
database_name = await self.database_resolver.resolve_id_to_name(database_id)
|
|
82
88
|
return f"@database[{database_name or database_id}]"
|
|
83
89
|
|
|
90
|
+
async def _extract_data_source_mention_markdown(self, data_source_id: str) -> str:
|
|
91
|
+
data_source_name = await self.data_source_resolver.resolve_id_to_name(data_source_id)
|
|
92
|
+
return f"@datasource[{data_source_name or data_source_id}]"
|
|
93
|
+
|
|
84
94
|
async def _extract_user_mention_markdown(self, user_id: str) -> str:
|
|
85
95
|
user_name = await self.person_resolver.resolve_id_to_name(user_id)
|
|
86
96
|
return f"@user[{user_name or user_id}]"
|
|
@@ -122,11 +132,13 @@ async def convert_rich_text_to_markdown(
|
|
|
122
132
|
*,
|
|
123
133
|
page_resolver: NameIdResolver | None = None,
|
|
124
134
|
database_resolver: NameIdResolver | None = None,
|
|
135
|
+
data_source_resolver: NameIdResolver | None = None,
|
|
125
136
|
person_resolver: NameIdResolver | None = None,
|
|
126
137
|
) -> str:
|
|
127
138
|
converter = RichTextToMarkdownConverter(
|
|
128
139
|
page_resolver=page_resolver,
|
|
129
140
|
database_resolver=database_resolver,
|
|
141
|
+
data_source_resolver=data_source_resolver,
|
|
130
142
|
person_resolver=person_resolver,
|
|
131
143
|
)
|
|
132
144
|
return await converter.to_markdown(rich_text)
|
|
@@ -35,5 +35,8 @@ class RichTextPatterns(StrEnum):
|
|
|
35
35
|
DATABASE_MENTION = r"@database\[([^\]]+)\]"
|
|
36
36
|
"""Matches a Notion database mention by name or ID. Example: `@database[Tasks]`."""
|
|
37
37
|
|
|
38
|
+
DATASOURCE_MENTION = r"@datasource\[([^\]]+)\]"
|
|
39
|
+
"""Matches a Notion data source mention by name or ID. Example: `@datasource[My Data]`."""
|
|
40
|
+
|
|
38
41
|
USER_MENTION = r"@user\[([^\]]+)\]"
|
|
39
42
|
"""Matches a Notion user mention by name or ID. Example: `@user[Some Person]`."""
|
notionary/blocks/schemas.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Annotated, Literal
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
|
-
from notionary.blocks.enums import BlockColor, BlockType,
|
|
7
|
+
from notionary.blocks.enums import BlockColor, BlockType, CodingLanguage, FileType
|
|
8
8
|
from notionary.blocks.rich_text.models import RichText
|
|
9
9
|
from notionary.shared.models.icon import Icon
|
|
10
10
|
from notionary.shared.models.parent import Parent
|
|
@@ -129,19 +129,27 @@ class CreateBreadcrumbBlock(BaseModel):
|
|
|
129
129
|
# ============================================================================
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
class
|
|
132
|
+
class BaseBulletedListItemData(BaseModel):
|
|
133
133
|
rich_text: list[RichText]
|
|
134
134
|
color: BlockColor = BlockColor.DEFAULT
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
class BulletedListItemData(BaseBulletedListItemData):
|
|
138
|
+
children: list[Block] | None = None
|
|
139
|
+
|
|
140
|
+
|
|
137
141
|
class BulletedListItemBlock(BaseBlock):
|
|
138
142
|
type: Literal[BlockType.BULLETED_LIST_ITEM] = BlockType.BULLETED_LIST_ITEM
|
|
139
143
|
bulleted_list_item: BulletedListItemData
|
|
140
144
|
|
|
141
145
|
|
|
146
|
+
class CreateBulletedListItemData(BaseBulletedListItemData):
|
|
147
|
+
children: list[BlockCreatePayload] | None = None
|
|
148
|
+
|
|
149
|
+
|
|
142
150
|
class CreateBulletedListItemBlock(BaseModel):
|
|
143
151
|
type: Literal[BlockType.BULLETED_LIST_ITEM] = BlockType.BULLETED_LIST_ITEM
|
|
144
|
-
bulleted_list_item:
|
|
152
|
+
bulleted_list_item: CreateBulletedListItemData
|
|
145
153
|
|
|
146
154
|
|
|
147
155
|
# ============================================================================
|
|
@@ -219,7 +227,7 @@ class CreateChildDatabaseBlock(BaseModel):
|
|
|
219
227
|
class CodeData(BaseModel):
|
|
220
228
|
caption: list[RichText] = Field(default_factory=list)
|
|
221
229
|
rich_text: list[RichText]
|
|
222
|
-
language:
|
|
230
|
+
language: CodingLanguage = CodingLanguage.PLAIN_TEXT
|
|
223
231
|
|
|
224
232
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
225
233
|
|
|
@@ -433,19 +441,27 @@ class CreateImageBlock(BaseModel):
|
|
|
433
441
|
# ============================================================================
|
|
434
442
|
|
|
435
443
|
|
|
436
|
-
class
|
|
444
|
+
class BaseNumberedListItemData(BaseModel):
|
|
437
445
|
rich_text: list[RichText]
|
|
438
446
|
color: BlockColor = BlockColor.DEFAULT
|
|
439
447
|
|
|
440
448
|
|
|
449
|
+
class NumberedListItemData(BaseNumberedListItemData):
|
|
450
|
+
children: list[Block] | None = None
|
|
451
|
+
|
|
452
|
+
|
|
441
453
|
class NumberedListItemBlock(BaseBlock):
|
|
442
454
|
type: Literal[BlockType.NUMBERED_LIST_ITEM] = BlockType.NUMBERED_LIST_ITEM
|
|
443
455
|
numbered_list_item: NumberedListItemData
|
|
444
456
|
|
|
445
457
|
|
|
458
|
+
class CreateNumberedListItemData(BaseNumberedListItemData):
|
|
459
|
+
children: list[BlockCreatePayload] | None = None
|
|
460
|
+
|
|
461
|
+
|
|
446
462
|
class CreateNumberedListItemBlock(BaseModel):
|
|
447
463
|
type: Literal[BlockType.NUMBERED_LIST_ITEM] = BlockType.NUMBERED_LIST_ITEM
|
|
448
|
-
numbered_list_item:
|
|
464
|
+
numbered_list_item: CreateNumberedListItemData
|
|
449
465
|
|
|
450
466
|
|
|
451
467
|
# ============================================================================
|
|
@@ -453,19 +469,27 @@ class CreateNumberedListItemBlock(BaseModel):
|
|
|
453
469
|
# ============================================================================
|
|
454
470
|
|
|
455
471
|
|
|
456
|
-
class
|
|
472
|
+
class BaseParagraphData(BaseModel):
|
|
457
473
|
rich_text: list[RichText]
|
|
458
474
|
color: BlockColor = BlockColor.DEFAULT
|
|
459
475
|
|
|
460
476
|
|
|
477
|
+
class ParagraphData(BaseParagraphData):
|
|
478
|
+
children: list[Block] | None = None
|
|
479
|
+
|
|
480
|
+
|
|
461
481
|
class ParagraphBlock(BaseBlock):
|
|
462
482
|
type: Literal[BlockType.PARAGRAPH] = BlockType.PARAGRAPH
|
|
463
483
|
paragraph: ParagraphData
|
|
464
484
|
|
|
465
485
|
|
|
486
|
+
class CreateParagraphData(BaseParagraphData):
|
|
487
|
+
children: list[BlockCreatePayload] | None = None
|
|
488
|
+
|
|
489
|
+
|
|
466
490
|
class CreateParagraphBlock(BaseModel):
|
|
467
491
|
type: Literal[BlockType.PARAGRAPH] = BlockType.PARAGRAPH
|
|
468
|
-
paragraph:
|
|
492
|
+
paragraph: CreateParagraphData
|
|
469
493
|
|
|
470
494
|
|
|
471
495
|
# ============================================================================
|
|
@@ -588,20 +612,28 @@ class CreateTableOfContentsBlock(BaseModel):
|
|
|
588
612
|
# ============================================================================
|
|
589
613
|
|
|
590
614
|
|
|
591
|
-
class
|
|
615
|
+
class BaseToDoData(BaseModel):
|
|
592
616
|
rich_text: list[RichText]
|
|
593
617
|
checked: bool = False
|
|
594
618
|
color: BlockColor = BlockColor.DEFAULT
|
|
595
619
|
|
|
596
620
|
|
|
621
|
+
class ToDoData(BaseToDoData):
|
|
622
|
+
children: list[Block] | None = None
|
|
623
|
+
|
|
624
|
+
|
|
597
625
|
class ToDoBlock(BaseBlock):
|
|
598
626
|
type: Literal[BlockType.TO_DO] = BlockType.TO_DO
|
|
599
627
|
to_do: ToDoData
|
|
600
628
|
|
|
601
629
|
|
|
630
|
+
class CreateToDoData(BaseToDoData):
|
|
631
|
+
children: list[BlockCreatePayload] | None = None
|
|
632
|
+
|
|
633
|
+
|
|
602
634
|
class CreateToDoBlock(BaseModel):
|
|
603
635
|
type: Literal[BlockType.TO_DO] = BlockType.TO_DO
|
|
604
|
-
to_do:
|
|
636
|
+
to_do: CreateToDoData
|
|
605
637
|
|
|
606
638
|
|
|
607
639
|
# ============================================================================
|
notionary/comments/client.py
CHANGED
|
@@ -18,20 +18,17 @@ class CommentClient(NotionHttpClient):
|
|
|
18
18
|
async def iter_comments(
|
|
19
19
|
self,
|
|
20
20
|
block_id: str,
|
|
21
|
-
|
|
22
|
-
page_size: int = 100,
|
|
21
|
+
total_results_limit: int | None = None,
|
|
23
22
|
) -> AsyncGenerator[CommentDto]:
|
|
24
|
-
"""
|
|
25
|
-
Iterates through all comments for a block, yielding each comment individually.
|
|
26
|
-
Uses pagination to handle large result sets efficiently without loading everything into memory.
|
|
27
|
-
"""
|
|
28
23
|
async for comment in paginate_notion_api_generator(
|
|
29
|
-
self._list_comments_page, block_id=block_id,
|
|
24
|
+
self._list_comments_page, block_id=block_id, total_results_limit=total_results_limit
|
|
30
25
|
):
|
|
31
26
|
yield comment
|
|
32
27
|
|
|
33
|
-
async def get_all_comments(self, block_id: str, *,
|
|
34
|
-
all_comments = await paginate_notion_api(
|
|
28
|
+
async def get_all_comments(self, block_id: str, *, total_results_limit: int | None = None) -> list[CommentDto]:
|
|
29
|
+
all_comments = await paginate_notion_api(
|
|
30
|
+
self._list_comments_page, block_id=block_id, total_results_limit=total_results_limit
|
|
31
|
+
)
|
|
35
32
|
|
|
36
33
|
self.logger.debug("Retrieved %d total comments for block %s", len(all_comments), block_id)
|
|
37
34
|
return all_comments
|
|
@@ -48,7 +45,7 @@ class CommentClient(NotionHttpClient):
|
|
|
48
45
|
start_cursor=start_cursor,
|
|
49
46
|
page_size=page_size,
|
|
50
47
|
)
|
|
51
|
-
resp = await self.get("comments", params=request.model_dump())
|
|
48
|
+
resp = await self.get("comments", params=request.model_dump(exclude_none=True))
|
|
52
49
|
return CommentListResponse.model_validate(resp)
|
|
53
50
|
|
|
54
51
|
async def create_comment_for_page(
|
notionary/comments/factory.py
CHANGED
|
@@ -3,8 +3,8 @@ import asyncio
|
|
|
3
3
|
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
4
4
|
from notionary.comments.models import Comment
|
|
5
5
|
from notionary.comments.schemas import CommentDto
|
|
6
|
+
from notionary.user.base import BaseUser
|
|
6
7
|
from notionary.user.client import UserHttpClient
|
|
7
|
-
from notionary.user.person import PersonUser
|
|
8
8
|
from notionary.utils.mixins.logging import LoggingMixin
|
|
9
9
|
|
|
10
10
|
|
|
@@ -25,14 +25,12 @@ class CommentFactory(LoggingMixin):
|
|
|
25
25
|
return Comment(author_name=author_name, content=content)
|
|
26
26
|
|
|
27
27
|
async def _resolve_user_name(self, dto: CommentDto) -> str:
|
|
28
|
-
|
|
28
|
+
created_by_id = dto.created_by.id
|
|
29
29
|
|
|
30
30
|
try:
|
|
31
|
-
|
|
32
|
-
if person and person.name:
|
|
33
|
-
return person.name
|
|
31
|
+
return await BaseUser.from_id_auto(created_by_id, self.http_client)
|
|
34
32
|
except Exception:
|
|
35
|
-
self.logger.warning(f"Failed to resolve user name for user_id: {
|
|
33
|
+
self.logger.warning(f"Failed to resolve user name for user_id: {created_by_id}", exc_info=True)
|
|
36
34
|
|
|
37
35
|
return self.UNKNOWN_AUTHOR
|
|
38
36
|
|
|
@@ -9,6 +9,7 @@ from notionary.data_source.schemas import DataSourceDto, QueryDataSourceResponse
|
|
|
9
9
|
from notionary.http.client import NotionHttpClient
|
|
10
10
|
from notionary.page.schemas import NotionPageDto
|
|
11
11
|
from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
|
|
12
|
+
from notionary.shared.typings import JsonDict
|
|
12
13
|
from notionary.utils.pagination import paginate_notion_api, paginate_notion_api_generator
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
@@ -56,8 +57,12 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
56
57
|
return updated_markdown_description
|
|
57
58
|
|
|
58
59
|
async def query(self, query_params: DataSourceQueryParams | None = None) -> QueryDataSourceResponse:
|
|
59
|
-
query_params_dict = query_params.
|
|
60
|
-
|
|
60
|
+
query_params_dict = query_params.to_api_params() if query_params else {}
|
|
61
|
+
total_result_limit = query_params.total_results_limit if query_params else None
|
|
62
|
+
|
|
63
|
+
all_results = await paginate_notion_api(
|
|
64
|
+
self._make_query_request, query_data=query_params_dict or {}, total_result_limit=total_result_limit
|
|
65
|
+
)
|
|
61
66
|
|
|
62
67
|
return QueryDataSourceResponse(
|
|
63
68
|
results=all_results,
|
|
@@ -67,16 +72,21 @@ class DataSourceInstanceClient(NotionHttpClient, EntityMetadataUpdateClient):
|
|
|
67
72
|
|
|
68
73
|
async def query_stream(self, query_params: DataSourceQueryParams | None = None) -> AsyncIterator[Any]:
|
|
69
74
|
query_params_dict = query_params.model_dump() if query_params else {}
|
|
75
|
+
total_result_limit = query_params.total_results_limit if query_params else None
|
|
70
76
|
|
|
71
|
-
async for result in paginate_notion_api_generator(
|
|
77
|
+
async for result in paginate_notion_api_generator(
|
|
78
|
+
self._make_query_request, query_data=query_params_dict or {}, total_results_limit=total_result_limit
|
|
79
|
+
):
|
|
72
80
|
yield result
|
|
73
81
|
|
|
74
82
|
async def _make_query_request(
|
|
75
|
-
self, query_data:
|
|
83
|
+
self, query_data: JsonDict, start_cursor: str | None = None, page_size: int | None = None
|
|
76
84
|
) -> QueryDataSourceResponse:
|
|
77
85
|
current_query_data = query_data.copy()
|
|
78
86
|
if start_cursor:
|
|
79
87
|
current_query_data["start_cursor"] = start_cursor
|
|
88
|
+
if page_size:
|
|
89
|
+
current_query_data["page_size"] = page_size
|
|
80
90
|
|
|
81
91
|
response = await self.post(f"data_sources/{self._data_source_id}/query", data=current_query_data)
|
|
82
92
|
return QueryDataSourceResponse.model_validate(response)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
-
from typing import Annotated,
|
|
2
|
+
from typing import Annotated, Literal, TypeVar
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
from notionary.shared.properties.type import PropertyType
|
|
7
|
+
from notionary.shared.typings import JsonDict
|
|
7
8
|
|
|
8
9
|
# ============================================================================
|
|
9
10
|
# Base Model
|
|
@@ -144,10 +145,9 @@ class DataSourceMultiSelectConfig(BaseModel):
|
|
|
144
145
|
|
|
145
146
|
|
|
146
147
|
class DataSourceRelationConfig(BaseModel):
|
|
147
|
-
|
|
148
|
-
data_source_id: str | None = None
|
|
148
|
+
data_source_id: str
|
|
149
149
|
type: RelationType = RelationType.SINGLE_PROPERTY
|
|
150
|
-
single_property:
|
|
150
|
+
single_property: JsonDict = Field(default_factory=dict)
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
class DataSourceNumberConfig(BaseModel):
|
|
@@ -264,10 +264,6 @@ class DataSourceRelationProperty(DataSourceProperty):
|
|
|
264
264
|
type: Literal[PropertyType.RELATION] = PropertyType.RELATION
|
|
265
265
|
relation: DataSourceRelationConfig = Field(default_factory=DataSourceRelationConfig)
|
|
266
266
|
|
|
267
|
-
@property
|
|
268
|
-
def related_database_id(self) -> str | None:
|
|
269
|
-
return self.relation.database_id
|
|
270
|
-
|
|
271
267
|
@property
|
|
272
268
|
def related_data_source_id(self) -> str | None:
|
|
273
269
|
return self.relation.data_source_id
|