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
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
from typing import override
|
|
2
2
|
|
|
3
3
|
from notionary.blocks.enums import BlockType
|
|
4
|
-
from notionary.blocks.schemas import
|
|
4
|
+
from notionary.blocks.schemas import (
|
|
5
|
+
BlockCreatePayload,
|
|
6
|
+
CreateColumnListBlock,
|
|
7
|
+
CreateColumnListData,
|
|
8
|
+
)
|
|
5
9
|
from notionary.page.content.parser.parsers.base import (
|
|
6
10
|
BlockParsingContext,
|
|
7
11
|
LineParser,
|
|
8
12
|
)
|
|
9
|
-
from notionary.page.content.syntax import
|
|
13
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
class ColumnListParser(LineParser):
|
|
13
|
-
def __init__(self, syntax_registry:
|
|
17
|
+
def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
|
|
14
18
|
super().__init__(syntax_registry)
|
|
15
19
|
self._syntax = syntax_registry.get_column_list_syntax()
|
|
16
20
|
|
|
@@ -35,9 +39,13 @@ class ColumnListParser(LineParser):
|
|
|
35
39
|
column_list_data = CreateColumnListData(children=[])
|
|
36
40
|
return CreateColumnListBlock(column_list=column_list_data)
|
|
37
41
|
|
|
38
|
-
async def _populate_columns(
|
|
42
|
+
async def _populate_columns(
|
|
43
|
+
self, block: CreateColumnListBlock, context: BlockParsingContext
|
|
44
|
+
) -> None:
|
|
39
45
|
parent_indent_level = context.get_line_indentation_level()
|
|
40
|
-
child_lines = self._collect_children_allowing_empty_lines(
|
|
46
|
+
child_lines = self._collect_children_allowing_empty_lines(
|
|
47
|
+
context, parent_indent_level
|
|
48
|
+
)
|
|
41
49
|
|
|
42
50
|
if not child_lines:
|
|
43
51
|
return
|
|
@@ -46,7 +54,9 @@ class ColumnListParser(LineParser):
|
|
|
46
54
|
block.column_list.children = column_blocks
|
|
47
55
|
context.lines_consumed = len(child_lines)
|
|
48
56
|
|
|
49
|
-
async def _parse_column_children(
|
|
57
|
+
async def _parse_column_children(
|
|
58
|
+
self, child_lines: list[str], context: BlockParsingContext
|
|
59
|
+
) -> list:
|
|
50
60
|
stripped_lines = context.strip_indentation_level(child_lines, levels=1)
|
|
51
61
|
child_markdown = "\n".join(stripped_lines)
|
|
52
62
|
parsed_blocks = await context.parse_nested_markdown(child_markdown)
|
|
@@ -67,7 +77,9 @@ class ColumnListParser(LineParser):
|
|
|
67
77
|
|
|
68
78
|
return child_lines
|
|
69
79
|
|
|
70
|
-
def _should_include_as_child(
|
|
80
|
+
def _should_include_as_child(
|
|
81
|
+
self, line: str, expected_indent: int, context: BlockParsingContext
|
|
82
|
+
) -> bool:
|
|
71
83
|
if not line.strip():
|
|
72
84
|
return True
|
|
73
85
|
|
|
@@ -5,11 +5,11 @@ from notionary.page.content.parser.parsers.base import (
|
|
|
5
5
|
BlockParsingContext,
|
|
6
6
|
LineParser,
|
|
7
7
|
)
|
|
8
|
-
from notionary.page.content.syntax import
|
|
8
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class DividerParser(LineParser):
|
|
12
|
-
def __init__(self, syntax_registry:
|
|
12
|
+
def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
|
|
13
13
|
super().__init__(syntax_registry)
|
|
14
14
|
self._syntax = syntax_registry.get_divider_syntax()
|
|
15
15
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
"""Parser for embed blocks."""
|
|
2
|
-
|
|
3
1
|
from typing import override
|
|
4
2
|
|
|
5
3
|
from notionary.blocks.schemas import CreateEmbedBlock, EmbedData
|
|
6
4
|
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
7
|
-
from notionary.page.content.syntax import
|
|
5
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
class EmbedParser(LineParser):
|
|
11
|
-
def __init__(self, syntax_registry:
|
|
9
|
+
def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
|
|
12
10
|
super().__init__(syntax_registry)
|
|
13
11
|
self._syntax = syntax_registry.get_embed_syntax()
|
|
14
12
|
|
|
@@ -5,11 +5,11 @@ from notionary.page.content.parser.parsers.base import (
|
|
|
5
5
|
BlockParsingContext,
|
|
6
6
|
LineParser,
|
|
7
7
|
)
|
|
8
|
-
from notionary.page.content.syntax import
|
|
8
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class EquationParser(LineParser):
|
|
12
|
-
def __init__(self, syntax_registry:
|
|
12
|
+
def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
|
|
13
13
|
super().__init__(syntax_registry)
|
|
14
14
|
self._syntax = syntax_registry.get_equation_syntax()
|
|
15
15
|
|
|
@@ -24,7 +24,9 @@ class EquationParser(LineParser):
|
|
|
24
24
|
equation_content = self._collect_equation_content(context)
|
|
25
25
|
lines_consumed = self._count_lines_consumed(context)
|
|
26
26
|
|
|
27
|
-
block = self._create_equation_block(
|
|
27
|
+
block = self._create_equation_block(
|
|
28
|
+
opening_line=context.line, equation_lines=equation_content
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
if block:
|
|
30
32
|
context.lines_consumed = lines_consumed
|
|
@@ -50,7 +52,9 @@ class EquationParser(LineParser):
|
|
|
50
52
|
|
|
51
53
|
return len(context.get_remaining_lines())
|
|
52
54
|
|
|
53
|
-
def _create_equation_block(
|
|
55
|
+
def _create_equation_block(
|
|
56
|
+
self, opening_line: str, equation_lines: list[str]
|
|
57
|
+
) -> CreateEquationBlock | None:
|
|
54
58
|
if opening_line.strip() != self._syntax.start_delimiter:
|
|
55
59
|
return None
|
|
56
60
|
|
|
@@ -1,42 +1,20 @@
|
|
|
1
|
-
"""Parser for file blocks."""
|
|
2
|
-
|
|
3
1
|
from typing import override
|
|
4
2
|
|
|
5
|
-
from notionary.blocks.schemas import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from notionary.blocks.schemas import CreateFileBlock, ExternalFileWithCaption
|
|
4
|
+
from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
|
|
5
|
+
from notionary.page.content.syntax.definition import (
|
|
6
|
+
SyntaxDefinition,
|
|
7
|
+
SyntaxDefinitionRegistry,
|
|
10
8
|
)
|
|
11
|
-
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
12
|
-
from notionary.page.content.syntax import SyntaxRegistry
|
|
13
|
-
|
|
14
9
|
|
|
15
|
-
class FileParser(LineParser):
|
|
16
|
-
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
-
super().__init__(syntax_registry)
|
|
18
|
-
self._syntax = syntax_registry.get_file_syntax()
|
|
19
10
|
|
|
11
|
+
class FileParser(FileLikeBlockParser[CreateFileBlock]):
|
|
20
12
|
@override
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
13
|
+
def _get_syntax(
|
|
14
|
+
self, syntax_registry: SyntaxDefinitionRegistry
|
|
15
|
+
) -> SyntaxDefinition:
|
|
16
|
+
return syntax_registry.get_file_syntax()
|
|
25
17
|
|
|
26
18
|
@override
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if not url:
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
file_data = FileData(
|
|
33
|
-
type=FileType.EXTERNAL,
|
|
34
|
-
external=ExternalFile(url=url),
|
|
35
|
-
caption=[],
|
|
36
|
-
)
|
|
37
|
-
block = CreateFileBlock(file=file_data)
|
|
38
|
-
context.result_blocks.append(block)
|
|
39
|
-
|
|
40
|
-
def _extract_url(self, line: str) -> str | None:
|
|
41
|
-
match = self._syntax.regex_pattern.search(line)
|
|
42
|
-
return match.group(1).strip() if match else None
|
|
19
|
+
def _create_block(self, file_data: ExternalFileWithCaption) -> CreateFileBlock:
|
|
20
|
+
return CreateFileBlock(file=file_data)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Generic, TypeVar, override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import (
|
|
6
|
+
ExternalFileWithCaption,
|
|
7
|
+
FileUploadFileWithCaption,
|
|
8
|
+
FileWithCaption,
|
|
9
|
+
)
|
|
10
|
+
from notionary.exceptions.file_upload import UploadFailedError, UploadTimeoutError
|
|
11
|
+
from notionary.file_upload.service import NotionFileUpload
|
|
12
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
13
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
14
|
+
from notionary.page.content.syntax.definition.models import SyntaxDefinition
|
|
15
|
+
from notionary.shared.models.file import ExternalFileData, FileUploadedFileData
|
|
16
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
|
17
|
+
|
|
18
|
+
_TBlock = TypeVar("_TBlock")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FileLikeBlockParser(LineParser, LoggingMixin, Generic[_TBlock]):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
syntax_registry: SyntaxDefinitionRegistry,
|
|
25
|
+
file_upload_service: NotionFileUpload | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
super().__init__(syntax_registry)
|
|
28
|
+
self._syntax = self._get_syntax(syntax_registry)
|
|
29
|
+
self._file_upload_service = file_upload_service or NotionFileUpload()
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def _get_syntax(
|
|
33
|
+
self, syntax_registry: SyntaxDefinitionRegistry
|
|
34
|
+
) -> SyntaxDefinition:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def _create_block(self, file_data: FileWithCaption) -> _TBlock:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
43
|
+
if context.is_inside_parent_context():
|
|
44
|
+
return False
|
|
45
|
+
return self._syntax.regex_pattern.search(context.line) is not None
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
49
|
+
path_or_url = self._extract_path_or_url(context.line)
|
|
50
|
+
if not path_or_url:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if self._is_external_url(path_or_url):
|
|
55
|
+
file_data = ExternalFileWithCaption(
|
|
56
|
+
external=ExternalFileData(url=path_or_url)
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
file_data = await self._upload_local_file(path_or_url)
|
|
60
|
+
|
|
61
|
+
block = self._create_block(file_data)
|
|
62
|
+
context.result_blocks.append(block)
|
|
63
|
+
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
self.logger.warning("File not found: '%s' - skipping block", path_or_url)
|
|
66
|
+
except PermissionError:
|
|
67
|
+
self.logger.warning(
|
|
68
|
+
"No permission to read file: '%s' - skipping block", path_or_url
|
|
69
|
+
)
|
|
70
|
+
except IsADirectoryError:
|
|
71
|
+
self.logger.warning(
|
|
72
|
+
"Path is a directory, not a file: '%s' - skipping block", path_or_url
|
|
73
|
+
)
|
|
74
|
+
except (UploadFailedError, UploadTimeoutError) as e:
|
|
75
|
+
self.logger.warning(
|
|
76
|
+
"Upload failed for '%s': %s - skipping block", path_or_url, e
|
|
77
|
+
)
|
|
78
|
+
except OSError as e:
|
|
79
|
+
self.logger.warning(
|
|
80
|
+
"IO error reading file '%s': %s - skipping block", path_or_url, e
|
|
81
|
+
)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.logger.warning(
|
|
84
|
+
"Unexpected error processing file '%s': %s - skipping block",
|
|
85
|
+
path_or_url,
|
|
86
|
+
e,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _extract_path_or_url(self, line: str) -> str | None:
|
|
90
|
+
match = self._syntax.regex_pattern.search(line)
|
|
91
|
+
return match.group(1).strip() if match else None
|
|
92
|
+
|
|
93
|
+
def _is_external_url(self, path_or_url: str) -> bool:
|
|
94
|
+
if path_or_url.startswith("http://") or path_or_url.startswith("https://"):
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
if path_or_url.startswith("data:"):
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
return path_or_url.startswith("/")
|
|
101
|
+
|
|
102
|
+
async def _upload_local_file(self, file_path: str) -> FileUploadFileWithCaption:
|
|
103
|
+
path = Path(file_path)
|
|
104
|
+
self.logger.debug("Uploading local file: '%s'", path)
|
|
105
|
+
upload_response = await self._file_upload_service.upload_file(path)
|
|
106
|
+
|
|
107
|
+
return FileUploadFileWithCaption(
|
|
108
|
+
file_upload=FileUploadedFileData(id=upload_response.id),
|
|
109
|
+
)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from typing import override
|
|
2
2
|
|
|
3
|
-
from notionary.blocks.rich_text.markdown_rich_text_converter import
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
4
|
+
MarkdownRichTextConverter,
|
|
5
|
+
)
|
|
4
6
|
from notionary.blocks.schemas import (
|
|
5
7
|
BlockColor,
|
|
6
8
|
BlockCreatePayload,
|
|
@@ -15,14 +17,18 @@ from notionary.page.content.parser.parsers.base import (
|
|
|
15
17
|
BlockParsingContext,
|
|
16
18
|
LineParser,
|
|
17
19
|
)
|
|
18
|
-
from notionary.page.content.syntax import
|
|
20
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class HeadingParser(LineParser):
|
|
22
24
|
MIN_HEADING_LEVEL = 1
|
|
23
25
|
MAX_HEADING_LEVEL = 3
|
|
24
26
|
|
|
25
|
-
def __init__(
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
syntax_registry: SyntaxDefinitionRegistry,
|
|
30
|
+
rich_text_converter: MarkdownRichTextConverter,
|
|
31
|
+
) -> None:
|
|
26
32
|
super().__init__(syntax_registry)
|
|
27
33
|
self._syntax = syntax_registry.get_heading_syntax()
|
|
28
34
|
self._rich_text_converter = rich_text_converter
|
|
@@ -42,7 +48,9 @@ class HeadingParser(LineParser):
|
|
|
42
48
|
await self._process_nested_children(block, context)
|
|
43
49
|
context.result_blocks.append(block)
|
|
44
50
|
|
|
45
|
-
async def _process_nested_children(
|
|
51
|
+
async def _process_nested_children(
|
|
52
|
+
self, block: CreateHeadingBlock, context: BlockParsingContext
|
|
53
|
+
) -> None:
|
|
46
54
|
parent_indent_level = context.get_line_indentation_level()
|
|
47
55
|
child_lines = context.collect_indented_child_lines(parent_indent_level)
|
|
48
56
|
|
|
@@ -64,7 +72,9 @@ class HeadingParser(LineParser):
|
|
|
64
72
|
|
|
65
73
|
context.lines_consumed = len(child_lines)
|
|
66
74
|
|
|
67
|
-
def _set_heading_toggleable(
|
|
75
|
+
def _set_heading_toggleable(
|
|
76
|
+
self, block: CreateHeadingBlock, is_toggleable: bool
|
|
77
|
+
) -> None:
|
|
68
78
|
if block.type == BlockType.HEADING_1:
|
|
69
79
|
block.heading_1.is_toggleable = is_toggleable
|
|
70
80
|
elif block.type == BlockType.HEADING_2:
|
|
@@ -72,7 +82,9 @@ class HeadingParser(LineParser):
|
|
|
72
82
|
elif block.type == BlockType.HEADING_3:
|
|
73
83
|
block.heading_3.is_toggleable = is_toggleable
|
|
74
84
|
|
|
75
|
-
def _set_heading_children(
|
|
85
|
+
def _set_heading_children(
|
|
86
|
+
self, block: CreateHeadingBlock, children: list[BlockCreatePayload]
|
|
87
|
+
) -> None:
|
|
76
88
|
if block.type == BlockType.HEADING_1:
|
|
77
89
|
block.heading_1.children = children
|
|
78
90
|
elif block.type == BlockType.HEADING_2:
|
|
@@ -100,13 +112,22 @@ class HeadingParser(LineParser):
|
|
|
100
112
|
return self._create_heading_block_by_level(level, heading_data)
|
|
101
113
|
|
|
102
114
|
def _is_valid_heading(self, level: int, content: str) -> bool:
|
|
103
|
-
return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(
|
|
115
|
+
return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(
|
|
116
|
+
content
|
|
117
|
+
)
|
|
104
118
|
|
|
105
119
|
async def _build_heading_data(self, content: str) -> CreateHeadingData:
|
|
106
120
|
rich_text = await self._rich_text_converter.to_rich_text(content)
|
|
107
|
-
return CreateHeadingData(
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
return CreateHeadingData(
|
|
122
|
+
rich_text=rich_text,
|
|
123
|
+
color=BlockColor.DEFAULT,
|
|
124
|
+
is_toggleable=False,
|
|
125
|
+
children=[],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _create_heading_block_by_level(
|
|
129
|
+
self, level: int, heading_data: CreateHeadingData
|
|
130
|
+
) -> CreateHeadingBlock:
|
|
110
131
|
if level == 1:
|
|
111
132
|
return CreateHeading1Block(heading_1=heading_data)
|
|
112
133
|
elif level == 2:
|
|
@@ -1,42 +1,20 @@
|
|
|
1
|
-
"""Parser for image blocks."""
|
|
2
|
-
|
|
3
1
|
from typing import override
|
|
4
2
|
|
|
5
|
-
from notionary.blocks.schemas import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from notionary.blocks.schemas import CreateImageBlock, ExternalFileWithCaption
|
|
4
|
+
from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
|
|
5
|
+
from notionary.page.content.syntax.definition import (
|
|
6
|
+
SyntaxDefinition,
|
|
7
|
+
SyntaxDefinitionRegistry,
|
|
10
8
|
)
|
|
11
|
-
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
12
|
-
from notionary.page.content.syntax import SyntaxRegistry
|
|
13
|
-
|
|
14
9
|
|
|
15
|
-
class ImageParser(LineParser):
|
|
16
|
-
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
-
super().__init__(syntax_registry)
|
|
18
|
-
self._syntax = syntax_registry.get_image_syntax()
|
|
19
10
|
|
|
11
|
+
class ImageParser(FileLikeBlockParser[CreateImageBlock]):
|
|
20
12
|
@override
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
13
|
+
def _get_syntax(
|
|
14
|
+
self, syntax_registry: SyntaxDefinitionRegistry
|
|
15
|
+
) -> SyntaxDefinition:
|
|
16
|
+
return syntax_registry.get_image_syntax()
|
|
25
17
|
|
|
26
18
|
@override
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if not url:
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
image_data = FileData(
|
|
33
|
-
type=FileType.EXTERNAL,
|
|
34
|
-
external=ExternalFile(url=url),
|
|
35
|
-
caption=[],
|
|
36
|
-
)
|
|
37
|
-
block = CreateImageBlock(image=image_data)
|
|
38
|
-
context.result_blocks.append(block)
|
|
39
|
-
|
|
40
|
-
def _extract_url(self, line: str) -> str | None:
|
|
41
|
-
match = self._syntax.regex_pattern.search(line)
|
|
42
|
-
return match.group(1).strip() if match else None
|
|
19
|
+
def _create_block(self, file_data: ExternalFileWithCaption) -> CreateImageBlock:
|
|
20
|
+
return CreateImageBlock(image=file_data)
|
|
@@ -12,11 +12,15 @@ from notionary.page.content.parser.parsers.base import (
|
|
|
12
12
|
BlockParsingContext,
|
|
13
13
|
LineParser,
|
|
14
14
|
)
|
|
15
|
-
from notionary.page.content.syntax import
|
|
15
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class NumberedListParser(LineParser):
|
|
19
|
-
def __init__(
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
syntax_registry: SyntaxDefinitionRegistry,
|
|
22
|
+
rich_text_converter: MarkdownRichTextConverter,
|
|
23
|
+
) -> None:
|
|
20
24
|
super().__init__(syntax_registry)
|
|
21
25
|
self._syntax = syntax_registry.get_numbered_list_syntax()
|
|
22
26
|
self._rich_text_converter = rich_text_converter
|
|
@@ -39,7 +43,9 @@ class NumberedListParser(LineParser):
|
|
|
39
43
|
await self._process_nested_children(block, context)
|
|
40
44
|
context.result_blocks.append(block)
|
|
41
45
|
|
|
42
|
-
async def _process_nested_children(
|
|
46
|
+
async def _process_nested_children(
|
|
47
|
+
self, block: CreateNumberedListItemBlock, context: BlockParsingContext
|
|
48
|
+
) -> None:
|
|
43
49
|
child_lines = self._collect_child_lines(context)
|
|
44
50
|
if not child_lines:
|
|
45
51
|
return
|
|
@@ -61,13 +67,17 @@ class NumberedListParser(LineParser):
|
|
|
61
67
|
children_text = self._convert_lines_to_text(stripped_lines)
|
|
62
68
|
return await context.parse_nested_markdown(children_text)
|
|
63
69
|
|
|
64
|
-
def _remove_parent_indentation(
|
|
70
|
+
def _remove_parent_indentation(
|
|
71
|
+
self, lines: list[str], context: BlockParsingContext
|
|
72
|
+
) -> list[str]:
|
|
65
73
|
return context.strip_indentation_level(lines, levels=1)
|
|
66
74
|
|
|
67
75
|
def _convert_lines_to_text(self, lines: list[str]) -> str:
|
|
68
76
|
return "\n".join(lines)
|
|
69
77
|
|
|
70
|
-
async def _create_numbered_list_block(
|
|
78
|
+
async def _create_numbered_list_block(
|
|
79
|
+
self, text: str
|
|
80
|
+
) -> CreateNumberedListItemBlock | None:
|
|
71
81
|
content = self._extract_list_content(text)
|
|
72
82
|
if content is None:
|
|
73
83
|
return None
|
|
@@ -85,5 +95,7 @@ class NumberedListParser(LineParser):
|
|
|
85
95
|
return await self._rich_text_converter.to_rich_text(content)
|
|
86
96
|
|
|
87
97
|
def _build_block(self, rich_text) -> CreateNumberedListItemBlock:
|
|
88
|
-
numbered_list_content = CreateNumberedListItemData(
|
|
98
|
+
numbered_list_content = CreateNumberedListItemData(
|
|
99
|
+
rich_text=rich_text, color=BlockColor.DEFAULT
|
|
100
|
+
)
|
|
89
101
|
return CreateNumberedListItemBlock(numbered_list_item=numbered_list_content)
|
|
@@ -33,5 +33,7 @@ class ParagraphParser(LineParser):
|
|
|
33
33
|
return None
|
|
34
34
|
|
|
35
35
|
rich_text = await self._rich_text_converter.to_rich_text(text)
|
|
36
|
-
paragraph_content = CreateParagraphData(
|
|
36
|
+
paragraph_content = CreateParagraphData(
|
|
37
|
+
rich_text=rich_text, color=BlockColor.DEFAULT
|
|
38
|
+
)
|
|
37
39
|
return CreateParagraphBlock(paragraph=paragraph_content)
|
|
@@ -1,42 +1,20 @@
|
|
|
1
|
-
"""Parser for PDF blocks."""
|
|
2
|
-
|
|
3
1
|
from typing import override
|
|
4
2
|
|
|
5
|
-
from notionary.blocks.schemas import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from notionary.blocks.schemas import CreatePdfBlock, ExternalFileWithCaption
|
|
4
|
+
from notionary.page.content.parser.parsers.file_like_block import FileLikeBlockParser
|
|
5
|
+
from notionary.page.content.syntax.definition import (
|
|
6
|
+
SyntaxDefinition,
|
|
7
|
+
SyntaxDefinitionRegistry,
|
|
10
8
|
)
|
|
11
|
-
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
12
|
-
from notionary.page.content.syntax import SyntaxRegistry
|
|
13
|
-
|
|
14
9
|
|
|
15
|
-
class PdfParser(LineParser):
|
|
16
|
-
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
-
super().__init__(syntax_registry)
|
|
18
|
-
self._syntax = syntax_registry.get_pdf_syntax()
|
|
19
10
|
|
|
11
|
+
class PdfParser(FileLikeBlockParser[CreatePdfBlock]):
|
|
20
12
|
@override
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
13
|
+
def _get_syntax(
|
|
14
|
+
self, syntax_registry: SyntaxDefinitionRegistry
|
|
15
|
+
) -> SyntaxDefinition:
|
|
16
|
+
return syntax_registry.get_pdf_syntax()
|
|
25
17
|
|
|
26
18
|
@override
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if not url:
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
pdf_data = FileData(
|
|
33
|
-
type=FileType.EXTERNAL,
|
|
34
|
-
external=ExternalFile(url=url),
|
|
35
|
-
caption=[],
|
|
36
|
-
)
|
|
37
|
-
block = CreatePdfBlock(pdf=pdf_data)
|
|
38
|
-
context.result_blocks.append(block)
|
|
39
|
-
|
|
40
|
-
def _extract_url(self, line: str) -> str | None:
|
|
41
|
-
match = self._syntax.regex_pattern.search(line)
|
|
42
|
-
return match.group(1).strip() if match else None
|
|
19
|
+
def _create_block(self, file_data: ExternalFileWithCaption) -> CreatePdfBlock:
|
|
20
|
+
return CreatePdfBlock(pdf=file_data)
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
from typing import override
|
|
2
2
|
|
|
3
|
-
from notionary.blocks.rich_text.markdown_rich_text_converter import
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
4
|
+
MarkdownRichTextConverter,
|
|
5
|
+
)
|
|
4
6
|
from notionary.blocks.schemas import BlockColor, CreateQuoteBlock, CreateQuoteData
|
|
5
7
|
from notionary.page.content.parser.parsers.base import (
|
|
6
8
|
BlockParsingContext,
|
|
7
9
|
LineParser,
|
|
8
10
|
)
|
|
9
|
-
from notionary.page.content.syntax import
|
|
11
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class QuoteParser(LineParser):
|
|
13
|
-
def __init__(
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
syntax_registry: SyntaxDefinitionRegistry,
|
|
18
|
+
rich_text_converter: MarkdownRichTextConverter,
|
|
19
|
+
) -> None:
|
|
14
20
|
super().__init__(syntax_registry)
|
|
15
21
|
self._syntax = syntax_registry.get_quote_syntax()
|
|
16
22
|
self._rich_text_converter = rich_text_converter
|
|
@@ -47,11 +53,16 @@ class QuoteParser(LineParser):
|
|
|
47
53
|
return quote_lines
|
|
48
54
|
|
|
49
55
|
async def _process_nested_children(
|
|
50
|
-
self,
|
|
56
|
+
self,
|
|
57
|
+
block: CreateQuoteBlock,
|
|
58
|
+
context: BlockParsingContext,
|
|
59
|
+
quote_lines: list[str],
|
|
51
60
|
) -> None:
|
|
52
61
|
# Calculate indent level after all quote lines
|
|
53
62
|
last_quote_line_index = len(quote_lines) - 1
|
|
54
|
-
child_lines = self._collect_child_lines_after_quote(
|
|
63
|
+
child_lines = self._collect_child_lines_after_quote(
|
|
64
|
+
context, last_quote_line_index
|
|
65
|
+
)
|
|
55
66
|
|
|
56
67
|
if not child_lines:
|
|
57
68
|
return
|
|
@@ -62,7 +73,9 @@ class QuoteParser(LineParser):
|
|
|
62
73
|
|
|
63
74
|
context.lines_consumed += len(child_lines)
|
|
64
75
|
|
|
65
|
-
def _collect_child_lines_after_quote(
|
|
76
|
+
def _collect_child_lines_after_quote(
|
|
77
|
+
self, context: BlockParsingContext, last_quote_index: int
|
|
78
|
+
) -> list[str]:
|
|
66
79
|
"""Collect indented children after the quote block."""
|
|
67
80
|
parent_indent_level = context.get_line_indentation_level()
|
|
68
81
|
remaining_lines = context.get_remaining_lines()
|
|
@@ -86,18 +99,24 @@ class QuoteParser(LineParser):
|
|
|
86
99
|
|
|
87
100
|
return child_lines
|
|
88
101
|
|
|
89
|
-
async def _parse_child_blocks(
|
|
102
|
+
async def _parse_child_blocks(
|
|
103
|
+
self, child_lines: list[str], context: BlockParsingContext
|
|
104
|
+
) -> list[CreateQuoteBlock]:
|
|
90
105
|
stripped_lines = self._remove_parent_indentation(child_lines, context)
|
|
91
106
|
children_text = self._convert_lines_to_text(stripped_lines)
|
|
92
107
|
return await context.parse_nested_markdown(children_text)
|
|
93
108
|
|
|
94
|
-
def _remove_parent_indentation(
|
|
109
|
+
def _remove_parent_indentation(
|
|
110
|
+
self, lines: list[str], context: BlockParsingContext
|
|
111
|
+
) -> list[str]:
|
|
95
112
|
return context.strip_indentation_level(lines, levels=1)
|
|
96
113
|
|
|
97
114
|
def _convert_lines_to_text(self, lines: list[str]) -> str:
|
|
98
115
|
return "\n".join(lines)
|
|
99
116
|
|
|
100
|
-
async def _create_quote_block(
|
|
117
|
+
async def _create_quote_block(
|
|
118
|
+
self, quote_lines: list[str]
|
|
119
|
+
) -> CreateQuoteBlock | None:
|
|
101
120
|
contents = self._extract_quote_contents(quote_lines)
|
|
102
121
|
if not contents:
|
|
103
122
|
return None
|
|
@@ -6,11 +6,11 @@ from notionary.page.content.parser.parsers.base import (
|
|
|
6
6
|
BlockParsingContext,
|
|
7
7
|
LineParser,
|
|
8
8
|
)
|
|
9
|
-
from notionary.page.content.syntax import
|
|
9
|
+
from notionary.page.content.syntax.definition import SyntaxDefinitionRegistry
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class SpaceParser(LineParser):
|
|
13
|
-
def __init__(self, syntax_registry:
|
|
13
|
+
def __init__(self, syntax_registry: SyntaxDefinitionRegistry) -> None:
|
|
14
14
|
super().__init__(syntax_registry)
|
|
15
15
|
self._syntax = syntax_registry.get_space_syntax()
|
|
16
16
|
|