notionary 0.3.1__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +49 -1
- notionary/blocks/client.py +37 -11
- notionary/blocks/enums.py +0 -6
- notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
- notionary/blocks/rich_text/models.py +13 -4
- notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
- notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
- notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
- notionary/blocks/schemas.py +33 -78
- notionary/comments/client.py +19 -6
- notionary/comments/factory.py +10 -3
- notionary/comments/schemas.py +10 -31
- notionary/comments/service.py +12 -4
- notionary/data_source/http/data_source_instance_client.py +59 -17
- notionary/data_source/properties/schemas.py +156 -115
- notionary/data_source/query/builder.py +67 -18
- notionary/data_source/query/resolver.py +16 -5
- notionary/data_source/query/schema.py +24 -6
- notionary/data_source/query/validator.py +18 -6
- notionary/data_source/schema/registry.py +31 -12
- notionary/data_source/schema/service.py +66 -20
- notionary/data_source/schemas.py +2 -2
- notionary/data_source/service.py +103 -43
- notionary/database/client.py +27 -9
- notionary/database/database_metadata_update_client.py +12 -4
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +14 -9
- notionary/exceptions/__init__.py +20 -4
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +9 -5
- notionary/exceptions/data_source/builder.py +13 -7
- notionary/exceptions/data_source/properties.py +6 -4
- notionary/exceptions/file_upload.py +76 -0
- notionary/exceptions/properties.py +7 -5
- notionary/exceptions/search.py +10 -6
- notionary/file_upload/__init__.py +4 -0
- notionary/file_upload/client.py +128 -210
- notionary/file_upload/config/__init__.py +17 -0
- notionary/file_upload/config/config.py +39 -0
- notionary/file_upload/config/constants.py +16 -0
- notionary/file_upload/file/reader.py +28 -0
- notionary/file_upload/query/__init__.py +7 -0
- notionary/file_upload/query/builder.py +58 -0
- notionary/file_upload/query/models.py +37 -0
- notionary/file_upload/schemas.py +80 -0
- notionary/file_upload/service.py +182 -291
- notionary/file_upload/validation/factory.py +66 -0
- notionary/file_upload/validation/impl/file_name_length.py +25 -0
- notionary/file_upload/validation/models.py +134 -0
- notionary/file_upload/validation/port.py +7 -0
- notionary/file_upload/validation/service.py +17 -0
- notionary/file_upload/validation/validators/__init__.py +11 -0
- notionary/file_upload/validation/validators/file_exists.py +15 -0
- notionary/file_upload/validation/validators/file_extension.py +131 -0
- notionary/file_upload/validation/validators/file_name_length.py +21 -0
- notionary/file_upload/validation/validators/upload_limit.py +31 -0
- notionary/http/client.py +33 -30
- notionary/page/content/__init__.py +9 -0
- notionary/page/content/factory.py +21 -7
- notionary/page/content/markdown/builder.py +85 -23
- notionary/page/content/markdown/nodes/audio.py +8 -4
- notionary/page/content/markdown/nodes/base.py +3 -3
- notionary/page/content/markdown/nodes/bookmark.py +5 -3
- notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
- notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
- notionary/page/content/markdown/nodes/callout.py +2 -2
- notionary/page/content/markdown/nodes/code.py +5 -3
- notionary/page/content/markdown/nodes/columns.py +3 -3
- notionary/page/content/markdown/nodes/container.py +9 -5
- notionary/page/content/markdown/nodes/divider.py +2 -2
- notionary/page/content/markdown/nodes/embed.py +8 -4
- notionary/page/content/markdown/nodes/equation.py +4 -2
- notionary/page/content/markdown/nodes/file.py +8 -4
- notionary/page/content/markdown/nodes/heading.py +2 -2
- notionary/page/content/markdown/nodes/image.py +8 -4
- notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
- notionary/page/content/markdown/nodes/numbered_list.py +5 -3
- notionary/page/content/markdown/nodes/paragraph.py +4 -2
- notionary/page/content/markdown/nodes/pdf.py +8 -4
- notionary/page/content/markdown/nodes/quote.py +2 -2
- notionary/page/content/markdown/nodes/space.py +2 -2
- notionary/page/content/markdown/nodes/table.py +8 -5
- notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
- notionary/page/content/markdown/nodes/todo.py +15 -7
- notionary/page/content/markdown/nodes/toggle.py +2 -2
- notionary/page/content/markdown/nodes/video.py +8 -4
- notionary/page/content/markdown/structured_output/__init__.py +73 -0
- notionary/page/content/markdown/structured_output/models.py +391 -0
- notionary/page/content/markdown/structured_output/service.py +211 -0
- notionary/page/content/parser/context.py +1 -1
- notionary/page/content/parser/factory.py +26 -8
- notionary/page/content/parser/parsers/audio.py +12 -32
- notionary/page/content/parser/parsers/base.py +2 -2
- notionary/page/content/parser/parsers/bookmark.py +2 -2
- notionary/page/content/parser/parsers/breadcrumb.py +2 -2
- notionary/page/content/parser/parsers/bulleted_list.py +19 -6
- notionary/page/content/parser/parsers/callout.py +15 -5
- notionary/page/content/parser/parsers/caption.py +9 -3
- notionary/page/content/parser/parsers/code.py +21 -7
- notionary/page/content/parser/parsers/column.py +8 -4
- notionary/page/content/parser/parsers/column_list.py +19 -7
- notionary/page/content/parser/parsers/divider.py +2 -2
- notionary/page/content/parser/parsers/embed.py +2 -4
- notionary/page/content/parser/parsers/equation.py +8 -4
- notionary/page/content/parser/parsers/file.py +12 -34
- notionary/page/content/parser/parsers/file_like_block.py +109 -0
- notionary/page/content/parser/parsers/heading.py +31 -10
- notionary/page/content/parser/parsers/image.py +12 -34
- notionary/page/content/parser/parsers/numbered_list.py +18 -6
- notionary/page/content/parser/parsers/paragraph.py +3 -1
- notionary/page/content/parser/parsers/pdf.py +12 -34
- notionary/page/content/parser/parsers/quote.py +28 -9
- notionary/page/content/parser/parsers/space.py +2 -2
- notionary/page/content/parser/parsers/table.py +31 -10
- notionary/page/content/parser/parsers/table_of_contents.py +7 -3
- notionary/page/content/parser/parsers/todo.py +15 -5
- notionary/page/content/parser/parsers/toggle.py +15 -5
- notionary/page/content/parser/parsers/video.py +12 -34
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
- notionary/page/content/parser/post_processing/service.py +3 -1
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
- notionary/page/content/parser/service.py +4 -1
- notionary/page/content/renderer/context.py +15 -5
- notionary/page/content/renderer/factory.py +12 -6
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
- notionary/page/content/renderer/renderers/audio.py +20 -23
- notionary/page/content/renderer/renderers/base.py +3 -3
- notionary/page/content/renderer/renderers/bookmark.py +3 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
- notionary/page/content/renderer/renderers/callout.py +19 -7
- notionary/page/content/renderer/renderers/captioned_block.py +11 -5
- notionary/page/content/renderer/renderers/code.py +6 -2
- notionary/page/content/renderer/renderers/column.py +3 -1
- notionary/page/content/renderer/renderers/column_list.py +3 -1
- notionary/page/content/renderer/renderers/embed.py +3 -1
- notionary/page/content/renderer/renderers/equation.py +3 -1
- notionary/page/content/renderer/renderers/file.py +20 -23
- notionary/page/content/renderer/renderers/file_like_block.py +47 -0
- notionary/page/content/renderer/renderers/heading.py +22 -8
- notionary/page/content/renderer/renderers/image.py +20 -23
- notionary/page/content/renderer/renderers/numbered_list.py +8 -3
- notionary/page/content/renderer/renderers/paragraph.py +12 -4
- notionary/page/content/renderer/renderers/pdf.py +20 -23
- notionary/page/content/renderer/renderers/quote.py +14 -6
- notionary/page/content/renderer/renderers/table.py +15 -5
- notionary/page/content/renderer/renderers/todo.py +16 -6
- notionary/page/content/renderer/renderers/toggle.py +8 -4
- notionary/page/content/renderer/renderers/video.py +20 -23
- notionary/page/content/renderer/service.py +9 -3
- notionary/page/content/service.py +21 -7
- notionary/page/content/syntax/definition/__init__.py +11 -0
- notionary/page/content/syntax/definition/models.py +57 -0
- notionary/page/content/syntax/definition/registry.py +371 -0
- notionary/page/content/syntax/prompts/__init__.py +4 -0
- notionary/page/content/syntax/prompts/models.py +11 -0
- notionary/page/content/syntax/prompts/registry.py +703 -0
- notionary/page/page_metadata_update_client.py +12 -4
- notionary/page/properties/client.py +46 -16
- notionary/page/properties/factory.py +6 -2
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +111 -37
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +21 -7
- notionary/shared/entity/client.py +6 -2
- notionary/shared/entity/dto_parsers.py +4 -37
- notionary/shared/entity/entity_metadata_update_client.py +25 -5
- notionary/shared/entity/schemas.py +6 -6
- notionary/shared/entity/service.py +89 -35
- notionary/shared/models/file.py +36 -6
- notionary/shared/models/icon.py +5 -12
- notionary/user/base.py +6 -2
- notionary/user/bot.py +22 -14
- notionary/user/client.py +3 -1
- notionary/user/person.py +3 -1
- notionary/user/schemas.py +3 -1
- notionary/user/service.py +6 -2
- notionary/utils/decorators.py +13 -9
- notionary/utils/fuzzy.py +6 -2
- notionary/utils/mixins/logging.py +3 -1
- notionary/utils/pagination.py +14 -4
- notionary/workspace/__init__.py +6 -2
- notionary/workspace/query/__init__.py +2 -1
- notionary/workspace/query/service.py +42 -13
- notionary/workspace/service.py +74 -46
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
- notionary-0.4.1.dist-info/RECORD +236 -0
- notionary/file_upload/models.py +0 -69
- notionary/page/blocks/client.py +0 -1
- notionary/page/content/syntax/__init__.py +0 -4
- notionary/page/content/syntax/models.py +0 -66
- notionary/page/content/syntax/registry.py +0 -393
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- notionary-0.3.1.dist-info/RECORD +0 -211
- /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
notionary/__init__.py
CHANGED
|
@@ -1,14 +1,62 @@
|
|
|
1
1
|
from .data_source.service import NotionDataSource
|
|
2
2
|
from .database.service import NotionDatabase
|
|
3
|
+
from .file_upload import FileUploadQuery, FileUploadQueryBuilder, NotionFileUpload
|
|
4
|
+
from .page.content import SyntaxPromptRegistry
|
|
3
5
|
from .page.content.markdown.builder import MarkdownBuilder
|
|
6
|
+
from .page.content.markdown.structured_output import (
|
|
7
|
+
MarkdownDocumentSchema,
|
|
8
|
+
StructuredOutputMarkdownConverter,
|
|
9
|
+
)
|
|
4
10
|
from .page.service import NotionPage
|
|
5
|
-
from .workspace import
|
|
11
|
+
from .workspace import (
|
|
12
|
+
NotionWorkspace,
|
|
13
|
+
NotionWorkspaceQueryConfigBuilder,
|
|
14
|
+
WorkspaceQueryConfig,
|
|
15
|
+
)
|
|
6
16
|
|
|
7
17
|
__all__ = [
|
|
18
|
+
"AudioSchema",
|
|
19
|
+
"BookmarkSchema",
|
|
20
|
+
"BreadcrumbSchema",
|
|
21
|
+
"BulletedListItemSchema",
|
|
22
|
+
"BulletedListSchema",
|
|
23
|
+
"CalloutSchema",
|
|
24
|
+
"CodeSchema",
|
|
25
|
+
"ColumnSchema",
|
|
26
|
+
"ColumnsSchema",
|
|
27
|
+
"DividerSchema",
|
|
28
|
+
"EmbedSchema",
|
|
29
|
+
"EquationSchema",
|
|
30
|
+
"FileSchema",
|
|
31
|
+
"FileUploadQuery",
|
|
32
|
+
"FileUploadQueryBuilder",
|
|
33
|
+
"Heading1Schema",
|
|
34
|
+
"Heading2Schema",
|
|
35
|
+
"Heading3Schema",
|
|
36
|
+
"ImageSchema",
|
|
8
37
|
"MarkdownBuilder",
|
|
38
|
+
"MarkdownDocumentSchema",
|
|
39
|
+
"MarkdownNodeSchema",
|
|
40
|
+
"MermaidSchema",
|
|
9
41
|
"NotionDataSource",
|
|
10
42
|
"NotionDatabase",
|
|
43
|
+
"NotionFileUpload",
|
|
11
44
|
"NotionPage",
|
|
12
45
|
"NotionWorkspace",
|
|
13
46
|
"NotionWorkspaceQueryConfigBuilder",
|
|
47
|
+
"NumberedListItemSchema",
|
|
48
|
+
"NumberedListSchema",
|
|
49
|
+
"ParagraphSchema",
|
|
50
|
+
"PdfSchema",
|
|
51
|
+
"QuoteSchema",
|
|
52
|
+
"SpaceSchema",
|
|
53
|
+
"StructuredOutputMarkdownConverter",
|
|
54
|
+
"SyntaxPromptRegistry",
|
|
55
|
+
"TableOfContentsSchema",
|
|
56
|
+
"TableSchema",
|
|
57
|
+
"TodoListSchema",
|
|
58
|
+
"TodoSchema",
|
|
59
|
+
"ToggleSchema",
|
|
60
|
+
"VideoSchema",
|
|
61
|
+
"WorkspaceQueryConfig",
|
|
14
62
|
]
|
notionary/blocks/client.py
CHANGED
|
@@ -31,9 +31,13 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
31
31
|
async def get_all_block_children(self, parent_block_id: str) -> list[Block]:
|
|
32
32
|
self.logger.debug("Retrieving all children for block: %s", parent_block_id)
|
|
33
33
|
|
|
34
|
-
all_blocks = await paginate_notion_api(
|
|
34
|
+
all_blocks = await paginate_notion_api(
|
|
35
|
+
self.get_block_children, block_id=parent_block_id
|
|
36
|
+
)
|
|
35
37
|
|
|
36
|
-
self.logger.debug(
|
|
38
|
+
self.logger.debug(
|
|
39
|
+
"Retrieved %d total children for block %s", len(all_blocks), parent_block_id
|
|
40
|
+
)
|
|
37
41
|
return all_blocks
|
|
38
42
|
|
|
39
43
|
async def get_block_children(
|
|
@@ -64,11 +68,17 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
64
68
|
|
|
65
69
|
if len(batches) == 1:
|
|
66
70
|
children_dicts = self._serialize_blocks(batches[0])
|
|
67
|
-
return await self._send_append_request(
|
|
71
|
+
return await self._send_append_request(
|
|
72
|
+
block_id, children_dicts, insert_after_block_id
|
|
73
|
+
)
|
|
68
74
|
|
|
69
|
-
return await self._send_batched_append_requests(
|
|
75
|
+
return await self._send_batched_append_requests(
|
|
76
|
+
block_id, batches, insert_after_block_id
|
|
77
|
+
)
|
|
70
78
|
|
|
71
|
-
def _split_into_batches(
|
|
79
|
+
def _split_into_batches(
|
|
80
|
+
self, blocks: list[BlockCreatePayload]
|
|
81
|
+
) -> list[list[BlockCreatePayload]]:
|
|
72
82
|
batches = []
|
|
73
83
|
for i in range(0, len(blocks), self.BATCH_SIZE):
|
|
74
84
|
batch = blocks[i : i + self.BATCH_SIZE]
|
|
@@ -89,19 +99,31 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
89
99
|
return BlockChildrenResponse.model_validate(response)
|
|
90
100
|
|
|
91
101
|
async def _send_batched_append_requests(
|
|
92
|
-
self,
|
|
102
|
+
self,
|
|
103
|
+
block_id: str,
|
|
104
|
+
batches: list[list[BlockCreatePayload]],
|
|
105
|
+
initial_after_block_id: str | None = None,
|
|
93
106
|
) -> BlockChildrenResponse:
|
|
94
107
|
total_blocks = sum(len(batch) for batch in batches)
|
|
95
|
-
self.logger.info(
|
|
108
|
+
self.logger.info(
|
|
109
|
+
"Appending %d blocks in %d batches", total_blocks, len(batches)
|
|
110
|
+
)
|
|
96
111
|
|
|
97
112
|
all_responses = []
|
|
98
113
|
after_block_id = initial_after_block_id
|
|
99
114
|
|
|
100
115
|
for batch_index, batch in enumerate(batches, start=1):
|
|
101
|
-
self.logger.debug(
|
|
116
|
+
self.logger.debug(
|
|
117
|
+
"Processing batch %d/%d (%d blocks)",
|
|
118
|
+
batch_index,
|
|
119
|
+
len(batches),
|
|
120
|
+
len(batch),
|
|
121
|
+
)
|
|
102
122
|
|
|
103
123
|
children_dicts = self._serialize_blocks(batch)
|
|
104
|
-
response = await self._send_append_request(
|
|
124
|
+
response = await self._send_append_request(
|
|
125
|
+
block_id, children_dicts, after_block_id
|
|
126
|
+
)
|
|
105
127
|
all_responses.append(response)
|
|
106
128
|
|
|
107
129
|
if response.results:
|
|
@@ -112,9 +134,13 @@ class NotionBlockHttpClient(NotionHttpClient):
|
|
|
112
134
|
self.logger.info("Successfully appended all blocks in %d batches", len(batches))
|
|
113
135
|
return self._merge_responses(all_responses)
|
|
114
136
|
|
|
115
|
-
def _merge_responses(
|
|
137
|
+
def _merge_responses(
|
|
138
|
+
self, responses: list[BlockChildrenResponse]
|
|
139
|
+
) -> BlockChildrenResponse:
|
|
116
140
|
if not responses:
|
|
117
|
-
raise ValueError(
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"Cannot merge empty response list - this should never happen"
|
|
143
|
+
)
|
|
118
144
|
|
|
119
145
|
first_response = responses[0]
|
|
120
146
|
all_results = [block for response in responses for block in response.results]
|
notionary/blocks/enums.py
CHANGED
|
@@ -4,7 +4,12 @@ from dataclasses import dataclass
|
|
|
4
4
|
from re import Match
|
|
5
5
|
from typing import ClassVar
|
|
6
6
|
|
|
7
|
-
from notionary.blocks.rich_text.models import
|
|
7
|
+
from notionary.blocks.rich_text.models import (
|
|
8
|
+
MentionType,
|
|
9
|
+
RichText,
|
|
10
|
+
RichTextType,
|
|
11
|
+
TextAnnotations,
|
|
12
|
+
)
|
|
8
13
|
from notionary.blocks.rich_text.name_id_resolver import (
|
|
9
14
|
DatabaseNameIdResolver,
|
|
10
15
|
DataSourceNameIdResolver,
|
|
@@ -58,17 +63,32 @@ class MarkdownRichTextConverter:
|
|
|
58
63
|
return [
|
|
59
64
|
PatternHandler(RichTextPatterns.BOLD, self._handle_bold_pattern),
|
|
60
65
|
PatternHandler(RichTextPatterns.ITALIC, self._handle_italic_pattern),
|
|
61
|
-
PatternHandler(
|
|
66
|
+
PatternHandler(
|
|
67
|
+
RichTextPatterns.ITALIC_UNDERSCORE, self._handle_italic_pattern
|
|
68
|
+
),
|
|
62
69
|
PatternHandler(RichTextPatterns.UNDERLINE, self._handle_underline_pattern),
|
|
63
|
-
PatternHandler(
|
|
70
|
+
PatternHandler(
|
|
71
|
+
RichTextPatterns.STRIKETHROUGH, self._handle_strikethrough_pattern
|
|
72
|
+
),
|
|
64
73
|
PatternHandler(RichTextPatterns.CODE, self._handle_code_pattern),
|
|
65
74
|
PatternHandler(RichTextPatterns.LINK, self._handle_link_pattern),
|
|
66
|
-
PatternHandler(
|
|
75
|
+
PatternHandler(
|
|
76
|
+
RichTextPatterns.INLINE_EQUATION, self._handle_equation_pattern
|
|
77
|
+
),
|
|
67
78
|
PatternHandler(RichTextPatterns.COLOR, self._handle_color_pattern),
|
|
68
|
-
PatternHandler(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
PatternHandler(
|
|
79
|
+
PatternHandler(
|
|
80
|
+
RichTextPatterns.PAGE_MENTION, self._handle_page_mention_pattern
|
|
81
|
+
),
|
|
82
|
+
PatternHandler(
|
|
83
|
+
RichTextPatterns.DATABASE_MENTION, self._handle_database_mention_pattern
|
|
84
|
+
),
|
|
85
|
+
PatternHandler(
|
|
86
|
+
RichTextPatterns.DATASOURCE_MENTION,
|
|
87
|
+
self._handle_data_source_mention_pattern,
|
|
88
|
+
),
|
|
89
|
+
PatternHandler(
|
|
90
|
+
RichTextPatterns.USER_MENTION, self._handle_user_mention_pattern
|
|
91
|
+
),
|
|
72
92
|
]
|
|
73
93
|
|
|
74
94
|
async def to_rich_text(self, text: str) -> list[RichText]:
|
|
@@ -106,12 +126,16 @@ class MarkdownRichTextConverter:
|
|
|
106
126
|
for pattern_handler in self.format_handlers:
|
|
107
127
|
match = re.search(pattern_handler.pattern, text)
|
|
108
128
|
if match and match.start() < earliest_position:
|
|
109
|
-
earliest_match = PatternMatch(
|
|
129
|
+
earliest_match = PatternMatch(
|
|
130
|
+
match=match, handler=pattern_handler.handler, position=match.start()
|
|
131
|
+
)
|
|
110
132
|
earliest_position = match.start()
|
|
111
133
|
|
|
112
134
|
return earliest_match
|
|
113
135
|
|
|
114
|
-
async def _process_pattern_match(
|
|
136
|
+
async def _process_pattern_match(
|
|
137
|
+
self, pattern_match: PatternMatch
|
|
138
|
+
) -> RichText | list[RichText]:
|
|
115
139
|
handler_method = pattern_match.handler
|
|
116
140
|
|
|
117
141
|
if self._is_async_handler(handler_method):
|
|
@@ -169,9 +193,13 @@ class MarkdownRichTextConverter:
|
|
|
169
193
|
def _apply_color_to_link_segment(self, segment: RichText, color: str) -> RichText:
|
|
170
194
|
formatting = self._extract_formatting_attributes(segment.annotations)
|
|
171
195
|
|
|
172
|
-
return RichText.for_link(
|
|
196
|
+
return RichText.for_link(
|
|
197
|
+
segment.plain_text, segment.text.link.url, color=color, **formatting
|
|
198
|
+
)
|
|
173
199
|
|
|
174
|
-
def _apply_color_to_plain_text_segment(
|
|
200
|
+
def _apply_color_to_plain_text_segment(
|
|
201
|
+
self, segment: RichText, color: str
|
|
202
|
+
) -> RichText:
|
|
175
203
|
if segment.type != RichTextType.TEXT:
|
|
176
204
|
return segment
|
|
177
205
|
|
|
@@ -179,7 +207,9 @@ class MarkdownRichTextConverter:
|
|
|
179
207
|
|
|
180
208
|
return RichText.from_plain_text(segment.plain_text, color=color, **formatting)
|
|
181
209
|
|
|
182
|
-
def _extract_formatting_attributes(
|
|
210
|
+
def _extract_formatting_attributes(
|
|
211
|
+
self, annotations: TextAnnotations
|
|
212
|
+
) -> dict[str, bool]:
|
|
183
213
|
if not annotations:
|
|
184
214
|
return {
|
|
185
215
|
"bold": False,
|
|
@@ -246,13 +276,17 @@ class MarkdownRichTextConverter:
|
|
|
246
276
|
if resolved_id:
|
|
247
277
|
return create_mention_func(resolved_id)
|
|
248
278
|
else:
|
|
249
|
-
return self._create_unresolved_mention_fallback(
|
|
279
|
+
return self._create_unresolved_mention_fallback(
|
|
280
|
+
identifier, mention_type
|
|
281
|
+
)
|
|
250
282
|
|
|
251
283
|
except Exception:
|
|
252
284
|
# If resolution throws an error, fallback to plain text
|
|
253
285
|
return self._create_unresolved_mention_fallback(identifier, mention_type)
|
|
254
286
|
|
|
255
|
-
def _create_unresolved_mention_fallback(
|
|
287
|
+
def _create_unresolved_mention_fallback(
|
|
288
|
+
self, identifier: str, mention_type: MentionType
|
|
289
|
+
) -> RichText:
|
|
256
290
|
fallback_text = f"@{mention_type.value}[{identifier}]"
|
|
257
291
|
return RichText.for_caption(fallback_text)
|
|
258
292
|
|
|
@@ -140,7 +140,9 @@ class RichText(BaseModel):
|
|
|
140
140
|
def mention_user(cls, user_id: str) -> Self:
|
|
141
141
|
return cls(
|
|
142
142
|
type=RichTextType.MENTION,
|
|
143
|
-
mention=MentionObject(
|
|
143
|
+
mention=MentionObject(
|
|
144
|
+
type=MentionType.USER, user=MentionUserRef(id=user_id)
|
|
145
|
+
),
|
|
144
146
|
annotations=TextAnnotations(),
|
|
145
147
|
)
|
|
146
148
|
|
|
@@ -148,7 +150,9 @@ class RichText(BaseModel):
|
|
|
148
150
|
def mention_page(cls, page_id: str) -> Self:
|
|
149
151
|
return cls(
|
|
150
152
|
type=RichTextType.MENTION,
|
|
151
|
-
mention=MentionObject(
|
|
153
|
+
mention=MentionObject(
|
|
154
|
+
type=MentionType.PAGE, page=MentionPageRef(id=page_id)
|
|
155
|
+
),
|
|
152
156
|
annotations=TextAnnotations(),
|
|
153
157
|
)
|
|
154
158
|
|
|
@@ -156,7 +160,9 @@ class RichText(BaseModel):
|
|
|
156
160
|
def mention_database(cls, database_id: str) -> Self:
|
|
157
161
|
return cls(
|
|
158
162
|
type=RichTextType.MENTION,
|
|
159
|
-
mention=MentionObject(
|
|
163
|
+
mention=MentionObject(
|
|
164
|
+
type=MentionType.DATABASE, database=MentionDatabaseRef(id=database_id)
|
|
165
|
+
),
|
|
160
166
|
annotations=TextAnnotations(),
|
|
161
167
|
)
|
|
162
168
|
|
|
@@ -164,7 +170,10 @@ class RichText(BaseModel):
|
|
|
164
170
|
def mention_data_source(cls, data_source_id: str) -> Self:
|
|
165
171
|
return cls(
|
|
166
172
|
type=RichTextType.MENTION,
|
|
167
|
-
mention=MentionObject(
|
|
173
|
+
mention=MentionObject(
|
|
174
|
+
type=MentionType.DATASOURCE,
|
|
175
|
+
data_source=MentionDataSourceRef(id=data_source_id),
|
|
176
|
+
),
|
|
168
177
|
annotations=TextAnnotations(),
|
|
169
178
|
)
|
|
170
179
|
|
|
@@ -6,8 +6,12 @@ from notionary.workspace.query.service import WorkspaceQueryService
|
|
|
6
6
|
|
|
7
7
|
# !!! in the notion api mentions that reference datasources are not provided yet (it's a limiation of the API as of now)
|
|
8
8
|
class DataSourceNameIdResolver(NameIdResolver):
|
|
9
|
-
def __init__(
|
|
10
|
-
self
|
|
9
|
+
def __init__(
|
|
10
|
+
self, workspace_query_service: WorkspaceQueryService | None = None
|
|
11
|
+
) -> None:
|
|
12
|
+
self._workspace_query_service = (
|
|
13
|
+
workspace_query_service or WorkspaceQueryService()
|
|
14
|
+
)
|
|
11
15
|
|
|
12
16
|
@override
|
|
13
17
|
async def resolve_name_to_id(self, name: str) -> str | None:
|
|
@@ -15,7 +19,9 @@ class DataSourceNameIdResolver(NameIdResolver):
|
|
|
15
19
|
return None
|
|
16
20
|
|
|
17
21
|
cleaned_name = name.strip()
|
|
18
|
-
data_source = await self._workspace_query_service.find_data_source(
|
|
22
|
+
data_source = await self._workspace_query_service.find_data_source(
|
|
23
|
+
query=cleaned_name
|
|
24
|
+
)
|
|
19
25
|
return data_source.id if data_source else None
|
|
20
26
|
|
|
21
27
|
@override
|
|
@@ -6,7 +6,9 @@ from notionary.user.person import PersonUser
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class PersonNameIdResolver(NameIdResolver):
|
|
9
|
-
def __init__(
|
|
9
|
+
def __init__(
|
|
10
|
+
self, person_user_factory=None, http_client: UserHttpClient | None = None
|
|
11
|
+
) -> None:
|
|
10
12
|
if person_user_factory is None:
|
|
11
13
|
person_user_factory = PersonUser
|
|
12
14
|
self.person_user_factory = person_user_factory
|
|
@@ -31,7 +33,9 @@ class PersonNameIdResolver(NameIdResolver):
|
|
|
31
33
|
return None
|
|
32
34
|
|
|
33
35
|
try:
|
|
34
|
-
user = await self.person_user_factory.from_id(
|
|
36
|
+
user = await self.person_user_factory.from_id(
|
|
37
|
+
user_id.strip(), self.http_client
|
|
38
|
+
)
|
|
35
39
|
return user.name if user else None
|
|
36
40
|
except Exception:
|
|
37
41
|
return None
|
|
@@ -69,7 +69,9 @@ class RichTextToMarkdownConverter:
|
|
|
69
69
|
return await self._extract_database_mention_markdown(mention.database.id)
|
|
70
70
|
|
|
71
71
|
elif mention.type == MentionType.DATASOURCE and mention.data_source:
|
|
72
|
-
return await self._extract_data_source_mention_markdown(
|
|
72
|
+
return await self._extract_data_source_mention_markdown(
|
|
73
|
+
mention.data_source.id
|
|
74
|
+
)
|
|
73
75
|
|
|
74
76
|
elif mention.type == MentionType.USER and mention.user:
|
|
75
77
|
return await self._extract_user_mention_markdown(mention.user.id)
|
|
@@ -88,7 +90,9 @@ class RichTextToMarkdownConverter:
|
|
|
88
90
|
return f"@database[{database_name or database_id}]"
|
|
89
91
|
|
|
90
92
|
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(
|
|
93
|
+
data_source_name = await self.data_source_resolver.resolve_id_to_name(
|
|
94
|
+
data_source_id
|
|
95
|
+
)
|
|
92
96
|
return f"@datasource[{data_source_name or data_source_id}]"
|
|
93
97
|
|
|
94
98
|
async def _extract_user_mention_markdown(self, user_id: str) -> str:
|
|
@@ -121,7 +125,10 @@ class RichTextToMarkdownConverter:
|
|
|
121
125
|
if annotations.bold:
|
|
122
126
|
content = f"**{content}**"
|
|
123
127
|
|
|
124
|
-
if
|
|
128
|
+
if (
|
|
129
|
+
annotations.color != BlockColor.DEFAULT
|
|
130
|
+
and annotations.color in self.VALID_COLORS
|
|
131
|
+
):
|
|
125
132
|
content = f"({annotations.color}:{content})"
|
|
126
133
|
|
|
127
134
|
return content
|
notionary/blocks/schemas.py
CHANGED
|
@@ -4,41 +4,39 @@ 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, CodingLanguage
|
|
7
|
+
from notionary.blocks.enums import BlockColor, BlockType, CodingLanguage
|
|
8
8
|
from notionary.blocks.rich_text.models import RichText
|
|
9
|
+
from notionary.shared.models.file import ExternalFile, FileUploadFile, NotionHostedFile
|
|
9
10
|
from notionary.shared.models.icon import Icon
|
|
10
11
|
from notionary.shared.models.parent import Parent
|
|
11
12
|
from notionary.user.schemas import PartialUserDto
|
|
12
13
|
|
|
13
14
|
# ============================================================================
|
|
14
|
-
# File
|
|
15
|
+
# File Data wrapper with caption
|
|
15
16
|
# ============================================================================
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
class CaptionMixin(BaseModel):
|
|
20
|
+
caption: list[RichText] = Field(default_factory=list)
|
|
21
|
+
name: str | None = None
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class
|
|
24
|
-
|
|
25
|
-
url: str
|
|
26
|
-
expiry_time: str
|
|
24
|
+
class ExternalFileWithCaption(CaptionMixin, ExternalFile):
|
|
25
|
+
pass
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
class
|
|
30
|
-
|
|
31
|
-
id: str
|
|
28
|
+
class NotionHostedFileWithCaption(CaptionMixin, NotionHostedFile):
|
|
29
|
+
pass
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
class
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
class FileUploadFileWithCaption(CaptionMixin, FileUploadFile):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
type FileWithCaption = Annotated[
|
|
37
|
+
ExternalFileWithCaption | NotionHostedFileWithCaption | FileUploadFileWithCaption,
|
|
38
|
+
Field(discriminator="type"),
|
|
39
|
+
]
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
# ============================================================================
|
|
@@ -65,24 +63,14 @@ class BaseBlock(BaseModel):
|
|
|
65
63
|
# ============================================================================
|
|
66
64
|
|
|
67
65
|
|
|
68
|
-
class AudioData(BaseModel):
|
|
69
|
-
model_config = ConfigDict(from_attributes=True)
|
|
70
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
71
|
-
type: FileType
|
|
72
|
-
external: ExternalFile | None = None
|
|
73
|
-
file: NotionHostedFile | None = None
|
|
74
|
-
file_upload: FileUploadFile | None = None
|
|
75
|
-
name: str | None = None
|
|
76
|
-
|
|
77
|
-
|
|
78
66
|
class AudioBlock(BaseBlock):
|
|
79
67
|
type: Literal[BlockType.AUDIO] = BlockType.AUDIO
|
|
80
|
-
audio:
|
|
68
|
+
audio: FileWithCaption
|
|
81
69
|
|
|
82
70
|
|
|
83
71
|
class CreateAudioBlock(BaseModel):
|
|
84
72
|
type: Literal[BlockType.AUDIO] = BlockType.AUDIO
|
|
85
|
-
audio:
|
|
73
|
+
audio: FileWithCaption
|
|
86
74
|
|
|
87
75
|
|
|
88
76
|
# ============================================================================
|
|
@@ -90,8 +78,7 @@ class CreateAudioBlock(BaseModel):
|
|
|
90
78
|
# ============================================================================
|
|
91
79
|
|
|
92
80
|
|
|
93
|
-
class BookmarkData(
|
|
94
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
81
|
+
class BookmarkData(CaptionMixin):
|
|
95
82
|
url: str
|
|
96
83
|
|
|
97
84
|
|
|
@@ -224,8 +211,7 @@ class CreateChildDatabaseBlock(BaseModel):
|
|
|
224
211
|
# ============================================================================
|
|
225
212
|
|
|
226
213
|
|
|
227
|
-
class CodeData(
|
|
228
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
214
|
+
class CodeData(CaptionMixin):
|
|
229
215
|
rich_text: list[RichText]
|
|
230
216
|
language: CodingLanguage = CodingLanguage.PLAIN_TEXT
|
|
231
217
|
|
|
@@ -311,9 +297,8 @@ class CreateDividerBlock(BaseModel):
|
|
|
311
297
|
# ============================================================================
|
|
312
298
|
|
|
313
299
|
|
|
314
|
-
class EmbedData(
|
|
300
|
+
class EmbedData(CaptionMixin):
|
|
315
301
|
url: str
|
|
316
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
317
302
|
|
|
318
303
|
|
|
319
304
|
class EmbedBlock(BaseBlock):
|
|
@@ -352,12 +337,12 @@ class CreateEquationBlock(BaseModel):
|
|
|
352
337
|
|
|
353
338
|
class FileBlock(BaseBlock):
|
|
354
339
|
type: Literal[BlockType.FILE] = BlockType.FILE
|
|
355
|
-
file:
|
|
340
|
+
file: FileWithCaption
|
|
356
341
|
|
|
357
342
|
|
|
358
343
|
class CreateFileBlock(BaseModel):
|
|
359
344
|
type: Literal[BlockType.FILE] = BlockType.FILE
|
|
360
|
-
file:
|
|
345
|
+
file: FileWithCaption
|
|
361
346
|
|
|
362
347
|
|
|
363
348
|
# ============================================================================
|
|
@@ -416,24 +401,14 @@ CreateHeadingBlock = CreateHeading1Block | CreateHeading2Block | CreateHeading3B
|
|
|
416
401
|
# ============================================================================
|
|
417
402
|
|
|
418
403
|
|
|
419
|
-
class ImageData(BaseModel):
|
|
420
|
-
model_config = ConfigDict(from_attributes=True)
|
|
421
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
422
|
-
type: FileType
|
|
423
|
-
external: ExternalFile | None = None
|
|
424
|
-
file: NotionHostedFile | None = None
|
|
425
|
-
file_upload: FileUploadFile | None = None
|
|
426
|
-
name: str | None = None
|
|
427
|
-
|
|
428
|
-
|
|
429
404
|
class ImageBlock(BaseBlock):
|
|
430
405
|
type: Literal[BlockType.IMAGE] = BlockType.IMAGE
|
|
431
|
-
image:
|
|
406
|
+
image: FileWithCaption
|
|
432
407
|
|
|
433
408
|
|
|
434
409
|
class CreateImageBlock(BaseModel):
|
|
435
410
|
type: Literal[BlockType.IMAGE] = BlockType.IMAGE
|
|
436
|
-
image:
|
|
411
|
+
image: FileWithCaption
|
|
437
412
|
|
|
438
413
|
|
|
439
414
|
# ============================================================================
|
|
@@ -497,24 +472,14 @@ class CreateParagraphBlock(BaseModel):
|
|
|
497
472
|
# ============================================================================
|
|
498
473
|
|
|
499
474
|
|
|
500
|
-
class PdfData(BaseModel):
|
|
501
|
-
model_config = ConfigDict(from_attributes=True)
|
|
502
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
503
|
-
type: FileType
|
|
504
|
-
external: ExternalFile | None = None
|
|
505
|
-
file: NotionHostedFile | None = None
|
|
506
|
-
file_upload: FileUploadFile | None = None
|
|
507
|
-
name: str | None = None
|
|
508
|
-
|
|
509
|
-
|
|
510
475
|
class PdfBlock(BaseBlock):
|
|
511
476
|
type: Literal[BlockType.PDF] = BlockType.PDF
|
|
512
|
-
pdf:
|
|
477
|
+
pdf: FileWithCaption
|
|
513
478
|
|
|
514
479
|
|
|
515
480
|
class CreatePdfBlock(BaseModel):
|
|
516
481
|
type: Literal[BlockType.PDF] = BlockType.PDF
|
|
517
|
-
pdf:
|
|
482
|
+
pdf: FileWithCaption
|
|
518
483
|
|
|
519
484
|
|
|
520
485
|
# ============================================================================
|
|
@@ -669,31 +634,21 @@ class CreateToggleBlock(BaseModel):
|
|
|
669
634
|
# ============================================================================
|
|
670
635
|
|
|
671
636
|
|
|
672
|
-
class VideoData(BaseModel):
|
|
673
|
-
model_config = ConfigDict(from_attributes=True)
|
|
674
|
-
caption: list[RichText] = Field(default_factory=list)
|
|
675
|
-
type: FileType
|
|
676
|
-
external: ExternalFile | None = None
|
|
677
|
-
file: NotionHostedFile | None = None
|
|
678
|
-
file_upload: FileUploadFile | None = None
|
|
679
|
-
name: str | None = None
|
|
680
|
-
|
|
681
|
-
|
|
682
637
|
class VideoBlock(BaseBlock):
|
|
683
638
|
type: Literal[BlockType.VIDEO] = BlockType.VIDEO
|
|
684
|
-
video:
|
|
639
|
+
video: FileWithCaption
|
|
685
640
|
|
|
686
641
|
|
|
687
642
|
class CreateVideoBlock(BaseModel):
|
|
688
643
|
type: Literal[BlockType.VIDEO] = BlockType.VIDEO
|
|
689
|
-
video:
|
|
644
|
+
video: FileWithCaption
|
|
690
645
|
|
|
691
646
|
|
|
692
647
|
# ============================================================================
|
|
693
648
|
# Block Union Type
|
|
694
649
|
# ============================================================================
|
|
695
650
|
|
|
696
|
-
Block = Annotated[
|
|
651
|
+
type Block = Annotated[
|
|
697
652
|
(
|
|
698
653
|
AudioBlock
|
|
699
654
|
| BookmarkBlock
|
|
@@ -743,7 +698,7 @@ class BlockChildrenResponse(BaseModel):
|
|
|
743
698
|
request_id: str
|
|
744
699
|
|
|
745
700
|
|
|
746
|
-
BlockCreatePayload = Annotated[
|
|
701
|
+
type BlockCreatePayload = Annotated[
|
|
747
702
|
(
|
|
748
703
|
CreateAudioBlock
|
|
749
704
|
| CreateBookmarkBlock
|