notionary 0.2.26__py3-none-any.whl → 0.2.28__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 +5 -20
- notionary/blocks/client.py +87 -215
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
- notionary/blocks/rich_text/models.py +164 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
- notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
- notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
- notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
- notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
- notionary/blocks/rich_text/rich_text_patterns.py +39 -0
- notionary/blocks/schemas.py +746 -0
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +40 -0
- notionary/comments/models.py +5 -127
- notionary/comments/schemas.py +240 -0
- notionary/comments/service.py +34 -0
- notionary/data_source/http/client.py +11 -0
- notionary/data_source/http/data_source_instance_client.py +94 -0
- notionary/data_source/properties/models.py +406 -0
- notionary/data_source/query/builder.py +429 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +304 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +353 -0
- notionary/database/client.py +30 -135
- notionary/database/database_metadata_update_client.py +19 -0
- notionary/database/schemas.py +29 -0
- notionary/database/service.py +169 -0
- notionary/exceptions/__init__.py +33 -0
- notionary/exceptions/api.py +41 -0
- notionary/exceptions/base.py +2 -0
- notionary/exceptions/block_parsing.py +16 -0
- notionary/exceptions/data_source/__init__.py +6 -0
- notionary/exceptions/data_source/builder.py +182 -0
- notionary/exceptions/data_source/properties.py +34 -0
- notionary/exceptions/properties.py +58 -0
- notionary/exceptions/search.py +33 -0
- notionary/file_upload/client.py +18 -30
- notionary/file_upload/models.py +7 -8
- notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
- notionary/http/client.py +205 -0
- notionary/http/models.py +49 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +68 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +304 -0
- notionary/page/content/markdown/nodes/__init__.py +54 -0
- notionary/page/content/markdown/nodes/audio.py +23 -0
- notionary/page/content/markdown/nodes/base.py +12 -0
- notionary/page/content/markdown/nodes/bookmark.py +25 -0
- notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
- notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
- notionary/page/content/markdown/nodes/callout.py +32 -0
- notionary/page/content/markdown/nodes/code.py +30 -0
- notionary/page/content/markdown/nodes/columns.py +51 -0
- notionary/page/content/markdown/nodes/divider.py +14 -0
- notionary/page/content/markdown/nodes/embed.py +23 -0
- notionary/page/content/markdown/nodes/equation.py +19 -0
- notionary/page/content/markdown/nodes/file.py +23 -0
- notionary/page/content/markdown/nodes/heading.py +16 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +15 -0
- notionary/page/content/markdown/nodes/paragraph.py +14 -0
- notionary/page/content/markdown/nodes/pdf.py +23 -0
- notionary/page/content/markdown/nodes/quote.py +15 -0
- notionary/page/content/markdown/nodes/space.py +14 -0
- notionary/page/content/markdown/nodes/table.py +45 -0
- notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
- notionary/page/content/markdown/nodes/todo.py +22 -0
- notionary/page/content/markdown/nodes/toggle.py +28 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +49 -0
- notionary/page/content/parser/factory.py +219 -0
- notionary/page/content/parser/parsers/__init__.py +60 -0
- notionary/page/content/parser/parsers/audio.py +40 -0
- notionary/page/content/parser/parsers/base.py +30 -0
- notionary/page/content/parser/parsers/bookmark.py +33 -0
- notionary/page/content/parser/parsers/breadcrumb.py +33 -0
- notionary/page/content/parser/parsers/bulleted_list.py +41 -0
- notionary/page/content/parser/parsers/callout.py +129 -0
- notionary/page/content/parser/parsers/caption.py +55 -0
- notionary/page/content/parser/parsers/code.py +81 -0
- notionary/page/content/parser/parsers/column.py +117 -0
- notionary/page/content/parser/parsers/column_list.py +81 -0
- notionary/page/content/parser/parsers/divider.py +33 -0
- notionary/page/content/parser/parsers/embed.py +33 -0
- notionary/page/content/parser/parsers/equation.py +65 -0
- notionary/page/content/parser/parsers/file.py +42 -0
- notionary/page/content/parser/parsers/heading.py +58 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +45 -0
- notionary/page/content/parser/parsers/paragraph.py +36 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +65 -0
- notionary/page/content/parser/parsers/space.py +35 -0
- notionary/page/content/parser/parsers/table.py +144 -0
- notionary/page/content/parser/parsers/table_of_contents.py +32 -0
- notionary/page/content/parser/parsers/todo.py +58 -0
- notionary/page/content/parser/parsers/toggle.py +127 -0
- notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
- notionary/page/content/parser/parsers/video.py +42 -0
- notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
- notionary/page/content/parser/post_processing/port.py +9 -0
- notionary/page/content/parser/post_processing/service.py +16 -0
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +69 -0
- notionary/page/content/renderer/context.py +48 -0
- notionary/page/content/renderer/factory.py +240 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
- notionary/page/content/renderer/post_processing/port.py +7 -0
- notionary/page/content/renderer/post_processing/service.py +15 -0
- notionary/page/content/renderer/renderers/__init__.py +57 -0
- notionary/page/content/renderer/renderers/audio.py +31 -0
- notionary/page/content/renderer/renderers/base.py +31 -0
- notionary/page/content/renderer/renderers/bookmark.py +25 -0
- notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
- notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
- notionary/page/content/renderer/renderers/callout.py +65 -0
- notionary/page/content/renderer/renderers/captioned_block.py +58 -0
- notionary/page/content/renderer/renderers/code.py +34 -0
- notionary/page/content/renderer/renderers/column.py +44 -0
- notionary/page/content/renderer/renderers/column_list.py +31 -0
- notionary/page/content/renderer/renderers/divider.py +22 -0
- notionary/page/content/renderer/renderers/embed.py +25 -0
- notionary/page/content/renderer/renderers/equation.py +37 -0
- notionary/page/content/renderer/renderers/fallback.py +24 -0
- notionary/page/content/renderer/renderers/file.py +40 -0
- notionary/page/content/renderer/renderers/heading.py +69 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +41 -0
- notionary/page/content/renderer/renderers/paragraph.py +40 -0
- notionary/page/content/renderer/renderers/pdf.py +31 -0
- notionary/page/content/renderer/renderers/quote.py +49 -0
- notionary/page/content/renderer/renderers/table.py +115 -0
- notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
- notionary/page/content/renderer/renderers/table_row.py +17 -0
- notionary/page/content/renderer/renderers/todo.py +56 -0
- notionary/page/content/renderer/renderers/toggle.py +53 -0
- notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +65 -0
- notionary/page/content/syntax/models.py +68 -0
- notionary/page/content/syntax/service.py +453 -0
- notionary/page/page_context.py +7 -16
- notionary/page/page_http_client.py +15 -0
- notionary/page/page_metadata_update_client.py +19 -0
- notionary/page/properties/client.py +144 -0
- notionary/page/properties/factory.py +26 -0
- notionary/page/properties/models.py +307 -0
- notionary/page/properties/service.py +257 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +222 -0
- notionary/shared/entity/client.py +29 -0
- notionary/shared/entity/dto_parsers.py +53 -0
- notionary/shared/entity/entity_metadata_update_client.py +41 -0
- notionary/shared/entity/schemas.py +45 -0
- notionary/shared/entity/service.py +171 -0
- notionary/shared/models/cover.py +20 -0
- notionary/shared/models/file.py +21 -0
- notionary/shared/models/icon.py +28 -0
- notionary/shared/models/parent.py +41 -0
- notionary/shared/properties/type.py +30 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +89 -0
- notionary/user/bot.py +70 -0
- notionary/user/client.py +22 -111
- notionary/user/person.py +41 -0
- notionary/user/schemas.py +67 -0
- notionary/user/service.py +65 -0
- notionary/utils/async_retry.py +39 -0
- notionary/utils/date.py +51 -0
- notionary/utils/fuzzy.py +56 -0
- notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
- notionary/utils/pagination.py +50 -0
- notionary/utils/singleton.py +13 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +3 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +60 -0
- notionary/workspace/query/service.py +93 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- notionary/blocks/__init__.py +0 -5
- notionary/blocks/_bootstrap.py +0 -271
- notionary/blocks/audio/__init__.py +0 -11
- notionary/blocks/audio/audio_element.py +0 -158
- notionary/blocks/audio/audio_markdown_node.py +0 -24
- notionary/blocks/audio/audio_models.py +0 -10
- notionary/blocks/base_block_element.py +0 -42
- notionary/blocks/bookmark/__init__.py +0 -12
- notionary/blocks/bookmark/bookmark_element.py +0 -83
- notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
- notionary/blocks/bookmark/bookmark_models.py +0 -15
- notionary/blocks/breadcrumbs/__init__.py +0 -15
- notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
- notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
- notionary/blocks/bulleted_list/__init__.py +0 -15
- notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
- notionary/blocks/callout/__init__.py +0 -12
- notionary/blocks/callout/callout_element.py +0 -99
- notionary/blocks/callout/callout_markdown_node.py +0 -19
- notionary/blocks/callout/callout_models.py +0 -33
- notionary/blocks/child_database/__init__.py +0 -14
- notionary/blocks/child_database/child_database_element.py +0 -59
- notionary/blocks/child_database/child_database_models.py +0 -12
- notionary/blocks/child_page/__init__.py +0 -9
- notionary/blocks/child_page/child_page_element.py +0 -94
- notionary/blocks/child_page/child_page_models.py +0 -12
- notionary/blocks/code/__init__.py +0 -11
- notionary/blocks/code/code_element.py +0 -149
- notionary/blocks/code/code_markdown_node.py +0 -80
- notionary/blocks/code/code_models.py +0 -94
- notionary/blocks/column/__init__.py +0 -25
- notionary/blocks/column/column_element.py +0 -65
- notionary/blocks/column/column_list_element.py +0 -52
- notionary/blocks/column/column_list_markdown_node.py +0 -34
- notionary/blocks/column/column_markdown_node.py +0 -42
- notionary/blocks/column/column_models.py +0 -26
- notionary/blocks/divider/__init__.py +0 -12
- notionary/blocks/divider/divider_element.py +0 -41
- notionary/blocks/divider/divider_markdown_node.py +0 -11
- notionary/blocks/divider/divider_models.py +0 -12
- notionary/blocks/embed/__init__.py +0 -12
- notionary/blocks/embed/embed_element.py +0 -98
- notionary/blocks/embed/embed_markdown_node.py +0 -19
- notionary/blocks/embed/embed_models.py +0 -14
- notionary/blocks/equation/__init__.py +0 -13
- notionary/blocks/equation/equation_element.py +0 -133
- notionary/blocks/equation/equation_element_markdown_node.py +0 -23
- notionary/blocks/equation/equation_models.py +0 -11
- notionary/blocks/file/__init__.py +0 -23
- notionary/blocks/file/file_element.py +0 -133
- notionary/blocks/file/file_element_markdown_node.py +0 -24
- notionary/blocks/file/file_element_models.py +0 -39
- notionary/blocks/heading/__init__.py +0 -19
- notionary/blocks/heading/heading_element.py +0 -112
- notionary/blocks/heading/heading_markdown_node.py +0 -16
- notionary/blocks/heading/heading_models.py +0 -29
- notionary/blocks/image_block/__init__.py +0 -11
- notionary/blocks/image_block/image_element.py +0 -130
- notionary/blocks/image_block/image_markdown_node.py +0 -25
- notionary/blocks/image_block/image_models.py +0 -10
- notionary/blocks/markdown/markdown_builder.py +0 -525
- notionary/blocks/markdown/markdown_document_model.py +0 -0
- notionary/blocks/markdown/markdown_node.py +0 -25
- notionary/blocks/mixins/captions/__init__.py +0 -4
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
- notionary/blocks/mixins/captions/caption_mixin.py +0 -92
- notionary/blocks/mixins/file_upload/__init__.py +0 -3
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
- notionary/blocks/models.py +0 -174
- notionary/blocks/numbered_list/__init__.py +0 -16
- notionary/blocks/numbered_list/numbered_list_element.py +0 -65
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
- notionary/blocks/numbered_list/numbered_list_models.py +0 -17
- notionary/blocks/paragraph/__init__.py +0 -15
- notionary/blocks/paragraph/paragraph_element.py +0 -58
- notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
- notionary/blocks/paragraph/paragraph_models.py +0 -16
- notionary/blocks/pdf/__init__.py +0 -11
- notionary/blocks/pdf/pdf_element.py +0 -146
- notionary/blocks/pdf/pdf_markdown_node.py +0 -24
- notionary/blocks/pdf/pdf_models.py +0 -11
- notionary/blocks/quote/__init__.py +0 -14
- notionary/blocks/quote/quote_element.py +0 -75
- notionary/blocks/quote/quote_markdown_node.py +0 -16
- notionary/blocks/quote/quote_models.py +0 -18
- notionary/blocks/registry/__init__.py +0 -3
- notionary/blocks/registry/block_registry.py +0 -150
- notionary/blocks/rich_text/__init__.py +0 -33
- notionary/blocks/rich_text/rich_text_models.py +0 -221
- notionary/blocks/rich_text/text_inline_formatter.py +0 -456
- notionary/blocks/syntax_prompt_builder.py +0 -137
- notionary/blocks/table/__init__.py +0 -19
- notionary/blocks/table/table_element.py +0 -225
- notionary/blocks/table/table_markdown_node.py +0 -42
- notionary/blocks/table/table_models.py +0 -28
- notionary/blocks/table_of_contents/__init__.py +0 -17
- notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
- notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
- notionary/blocks/todo/__init__.py +0 -12
- notionary/blocks/todo/todo_element.py +0 -81
- notionary/blocks/todo/todo_markdown_node.py +0 -21
- notionary/blocks/todo/todo_models.py +0 -18
- notionary/blocks/toggle/__init__.py +0 -12
- notionary/blocks/toggle/toggle_element.py +0 -112
- notionary/blocks/toggle/toggle_markdown_node.py +0 -31
- notionary/blocks/toggle/toggle_models.py +0 -17
- notionary/blocks/toggleable_heading/__init__.py +0 -11
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
- notionary/blocks/types.py +0 -130
- notionary/blocks/video/__init__.py +0 -11
- notionary/blocks/video/video_element.py +0 -187
- notionary/blocks/video/video_element_models.py +0 -10
- notionary/blocks/video/video_markdown_node.py +0 -26
- notionary/comments/__init__.py +0 -26
- notionary/database/__init__.py +0 -4
- notionary/database/database.py +0 -480
- notionary/database/database_filter_builder.py +0 -173
- notionary/database/database_provider.py +0 -227
- notionary/database/exceptions.py +0 -13
- notionary/database/factory.py +0 -0
- notionary/database/models.py +0 -337
- notionary/database/notion_database.py +0 -487
- notionary/file_upload/__init__.py +0 -7
- notionary/page/client.py +0 -124
- notionary/page/markdown_whitespace_processor.py +0 -129
- notionary/page/models.py +0 -322
- notionary/page/notion_page.py +0 -674
- notionary/page/page_content_deleting_service.py +0 -117
- notionary/page/page_content_writer.py +0 -80
- notionary/page/property_formatter.py +0 -99
- notionary/page/reader/handler/__init__.py +0 -19
- notionary/page/reader/handler/base_block_renderer.py +0 -44
- notionary/page/reader/handler/block_processing_context.py +0 -35
- notionary/page/reader/handler/block_rendering_context.py +0 -48
- notionary/page/reader/handler/column_list_renderer.py +0 -51
- notionary/page/reader/handler/column_renderer.py +0 -60
- notionary/page/reader/handler/equation_renderer.py +0 -0
- notionary/page/reader/handler/line_renderer.py +0 -73
- notionary/page/reader/handler/numbered_list_renderer.py +0 -85
- notionary/page/reader/handler/toggle_renderer.py +0 -69
- notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
- notionary/page/reader/page_content_retriever.py +0 -81
- notionary/page/search_filter_builder.py +0 -132
- notionary/page/utils.py +0 -60
- notionary/page/writer/handler/__init__.py +0 -24
- notionary/page/writer/handler/code_handler.py +0 -72
- notionary/page/writer/handler/column_handler.py +0 -141
- notionary/page/writer/handler/column_list_handler.py +0 -139
- notionary/page/writer/handler/equation_handler.py +0 -74
- notionary/page/writer/handler/line_handler.py +0 -35
- notionary/page/writer/handler/line_processing_context.py +0 -54
- notionary/page/writer/handler/regular_line_handler.py +0 -86
- notionary/page/writer/handler/table_handler.py +0 -66
- notionary/page/writer/handler/toggle_handler.py +0 -159
- notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
- notionary/page/writer/markdown_to_notion_converter.py +0 -139
- notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
- notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
- notionary/page/writer/notion_text_length_processor.py +0 -150
- notionary/schemas/__init__.py +0 -3
- notionary/schemas/base.py +0 -73
- notionary/shared/__init__.py +0 -3
- notionary/shared/name_to_id_resolver.py +0 -203
- notionary/telemetry/__init__.py +0 -19
- notionary/telemetry/service.py +0 -136
- notionary/telemetry/views.py +0 -73
- notionary/user/base_notion_user.py +0 -53
- notionary/user/models.py +0 -84
- notionary/user/notion_bot_user.py +0 -226
- notionary/user/notion_user.py +0 -255
- notionary/user/notion_user_manager.py +0 -101
- notionary/util/__init__.py +0 -15
- notionary/util/concurrency_limiter.py +0 -0
- notionary/util/factory_decorator.py +0 -0
- notionary/util/factory_only.py +0 -37
- notionary/util/fuzzy.py +0 -75
- notionary/util/page_id_utils.py +0 -27
- notionary/util/singleton.py +0 -18
- notionary/util/singleton_metaclass.py +0 -22
- notionary/workspace.py +0 -105
- notionary-0.2.26.dist-info/RECORD +0 -202
@@ -1,159 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import re
|
4
|
-
|
5
|
-
from notionary.blocks.toggle.toggle_element import ToggleElement
|
6
|
-
from notionary.page.writer.handler import (
|
7
|
-
LineHandler,
|
8
|
-
LineProcessingContext,
|
9
|
-
ParentBlockContext,
|
10
|
-
)
|
11
|
-
|
12
|
-
|
13
|
-
class ToggleHandler(LineHandler):
|
14
|
-
"""Handles regular toggle blocks with ultra-simplified +++ syntax."""
|
15
|
-
|
16
|
-
def __init__(self):
|
17
|
-
super().__init__()
|
18
|
-
# Updated: Support both "+++title" and "+++ title"
|
19
|
-
self._start_pattern = re.compile(r"^[+]{3}\s*(.+)$", re.IGNORECASE)
|
20
|
-
self._end_pattern = re.compile(r"^[+]{3}\s*$")
|
21
|
-
|
22
|
-
def _can_handle(self, context: LineProcessingContext) -> bool:
|
23
|
-
return (
|
24
|
-
self._is_toggle_start(context)
|
25
|
-
or self._is_toggle_end(context)
|
26
|
-
or self._is_toggle_content(context)
|
27
|
-
)
|
28
|
-
|
29
|
-
async def _process(self, context: LineProcessingContext) -> None:
|
30
|
-
# Explicit, readable branches (small duplication is acceptable)
|
31
|
-
if self._is_toggle_start(context):
|
32
|
-
await self._start_toggle(context)
|
33
|
-
context.was_processed = True
|
34
|
-
context.should_continue = True
|
35
|
-
|
36
|
-
if self._is_toggle_end(context):
|
37
|
-
await self._finalize_toggle(context)
|
38
|
-
context.was_processed = True
|
39
|
-
context.should_continue = True
|
40
|
-
|
41
|
-
if self._is_toggle_content(context):
|
42
|
-
self._add_toggle_content(context)
|
43
|
-
context.was_processed = True
|
44
|
-
context.should_continue = True
|
45
|
-
|
46
|
-
def _is_toggle_start(self, context: LineProcessingContext) -> bool:
|
47
|
-
"""Check if line starts a toggle (+++ Title or +++Title)."""
|
48
|
-
line = context.line.strip()
|
49
|
-
|
50
|
-
# Must match our pattern (now allows optional space)
|
51
|
-
if not self._start_pattern.match(line):
|
52
|
-
return False
|
53
|
-
|
54
|
-
# But NOT match toggleable heading pattern (has # after +++)
|
55
|
-
# Updated: Support both "+++#title" and "+++ # title"
|
56
|
-
toggleable_heading_pattern = re.compile(
|
57
|
-
r"^[+]{3}\s*#{1,3}\s+.+$", re.IGNORECASE
|
58
|
-
)
|
59
|
-
if toggleable_heading_pattern.match(line):
|
60
|
-
return False
|
61
|
-
|
62
|
-
return True
|
63
|
-
|
64
|
-
def _is_toggle_end(self, context: LineProcessingContext) -> bool:
|
65
|
-
"""Check if we need to end a toggle (+++)."""
|
66
|
-
if not self._end_pattern.match(context.line.strip()):
|
67
|
-
return False
|
68
|
-
|
69
|
-
if not context.parent_stack:
|
70
|
-
return False
|
71
|
-
|
72
|
-
# Check if top of stack is a Toggle
|
73
|
-
current_parent = context.parent_stack[-1]
|
74
|
-
return issubclass(current_parent.element_type, ToggleElement)
|
75
|
-
|
76
|
-
async def _start_toggle(self, context: LineProcessingContext) -> None:
|
77
|
-
"""Start a new toggle block."""
|
78
|
-
toggle_element = ToggleElement()
|
79
|
-
|
80
|
-
# Create the block
|
81
|
-
result = await toggle_element.markdown_to_notion(context.line)
|
82
|
-
if not result:
|
83
|
-
return
|
84
|
-
|
85
|
-
block = result
|
86
|
-
|
87
|
-
# Push to parent stack
|
88
|
-
parent_context = ParentBlockContext(
|
89
|
-
block=block,
|
90
|
-
element_type=ToggleElement,
|
91
|
-
child_lines=[],
|
92
|
-
)
|
93
|
-
context.parent_stack.append(parent_context)
|
94
|
-
|
95
|
-
async def _finalize_toggle(self, context: LineProcessingContext) -> None:
|
96
|
-
"""Finalize a toggle block and add it to result_blocks."""
|
97
|
-
toggle_context = context.parent_stack.pop()
|
98
|
-
|
99
|
-
if toggle_context.has_children():
|
100
|
-
all_children = await self._get_all_children(
|
101
|
-
toggle_context, context.block_registry
|
102
|
-
)
|
103
|
-
toggle_context.block.toggle.children = all_children
|
104
|
-
|
105
|
-
# Check if we have a parent context to add this toggle to
|
106
|
-
if context.parent_stack:
|
107
|
-
# Add this toggle as a child block to the parent
|
108
|
-
parent_context = context.parent_stack[-1]
|
109
|
-
parent_context.add_child_block(toggle_context.block)
|
110
|
-
else:
|
111
|
-
# No parent, add to top level
|
112
|
-
context.result_blocks.append(toggle_context.block)
|
113
|
-
|
114
|
-
def _is_toggle_content(self, context: LineProcessingContext) -> bool:
|
115
|
-
"""Check if we're inside a toggle context and should handle content."""
|
116
|
-
if not context.parent_stack:
|
117
|
-
return False
|
118
|
-
|
119
|
-
current_parent = context.parent_stack[-1]
|
120
|
-
if not issubclass(current_parent.element_type, ToggleElement):
|
121
|
-
return False
|
122
|
-
|
123
|
-
# Handle all content inside toggle (not start/end patterns)
|
124
|
-
line = context.line.strip()
|
125
|
-
return not (self._start_pattern.match(line) or self._end_pattern.match(line))
|
126
|
-
|
127
|
-
def _add_toggle_content(self, context: LineProcessingContext) -> None:
|
128
|
-
"""Add content to the current toggle context."""
|
129
|
-
context.parent_stack[-1].add_child_line(context.line)
|
130
|
-
|
131
|
-
async def _convert_children_text(self, text: str, block_registry) -> list:
|
132
|
-
"""Convert children text to blocks."""
|
133
|
-
from notionary.page.writer.markdown_to_notion_converter import (
|
134
|
-
MarkdownToNotionConverter,
|
135
|
-
)
|
136
|
-
|
137
|
-
if not text.strip():
|
138
|
-
return []
|
139
|
-
|
140
|
-
child_converter = MarkdownToNotionConverter(block_registry)
|
141
|
-
return await child_converter.process_lines(text)
|
142
|
-
|
143
|
-
async def _get_all_children(self, parent_context, block_registry) -> list:
|
144
|
-
"""Helper method to combine text-based and direct block children."""
|
145
|
-
children_blocks = []
|
146
|
-
|
147
|
-
# Process text lines
|
148
|
-
if parent_context.child_lines:
|
149
|
-
children_text = "\n".join(parent_context.child_lines)
|
150
|
-
text_blocks = await self._convert_children_text(
|
151
|
-
children_text, block_registry
|
152
|
-
)
|
153
|
-
children_blocks.extend(text_blocks)
|
154
|
-
|
155
|
-
# Add direct blocks (like processed columns)
|
156
|
-
if hasattr(parent_context, "child_blocks") and parent_context.child_blocks:
|
157
|
-
children_blocks.extend(parent_context.child_blocks)
|
158
|
-
|
159
|
-
return children_blocks
|
@@ -1,174 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import re
|
4
|
-
|
5
|
-
from notionary.blocks.models import BlockCreateRequest
|
6
|
-
from notionary.blocks.toggleable_heading.toggleable_heading_element import (
|
7
|
-
ToggleableHeadingElement,
|
8
|
-
)
|
9
|
-
from notionary.blocks.types import BlockType
|
10
|
-
from notionary.page.writer.handler import (
|
11
|
-
LineHandler,
|
12
|
-
LineProcessingContext,
|
13
|
-
ParentBlockContext,
|
14
|
-
)
|
15
|
-
|
16
|
-
|
17
|
-
class ToggleableHeadingHandler(LineHandler):
|
18
|
-
"""Handles toggleable heading blocks with +++# syntax."""
|
19
|
-
|
20
|
-
def __init__(self):
|
21
|
-
super().__init__()
|
22
|
-
# Updated: Support both "+++# title" and "+++#title"
|
23
|
-
self._start_pattern = re.compile(
|
24
|
-
r"^[+]{3}\s*(?P<level>#{1,3})\s*(.+)$", re.IGNORECASE
|
25
|
-
)
|
26
|
-
# +++
|
27
|
-
self._end_pattern = re.compile(r"^[+]{3}\s*$")
|
28
|
-
|
29
|
-
def _can_handle(self, context: LineProcessingContext) -> bool:
|
30
|
-
return (
|
31
|
-
self._is_toggleable_heading_start(context)
|
32
|
-
or self._is_toggleable_heading_end(context)
|
33
|
-
or self._is_toggleable_heading_content(context)
|
34
|
-
)
|
35
|
-
|
36
|
-
async def _process(self, context: LineProcessingContext) -> None:
|
37
|
-
"""Process toggleable heading start, end, or content with unified handling."""
|
38
|
-
|
39
|
-
async def _handle(action):
|
40
|
-
await action(context)
|
41
|
-
context.was_processed = True
|
42
|
-
context.should_continue = True
|
43
|
-
return True
|
44
|
-
|
45
|
-
if self._is_toggleable_heading_start(context):
|
46
|
-
return await _handle(self._start_toggleable_heading)
|
47
|
-
if self._is_toggleable_heading_end(context):
|
48
|
-
return await _handle(self._finalize_toggleable_heading)
|
49
|
-
if self._is_toggleable_heading_content(context):
|
50
|
-
return await _handle(self._add_toggleable_heading_content)
|
51
|
-
|
52
|
-
def _is_toggleable_heading_start(self, context: LineProcessingContext) -> bool:
|
53
|
-
"""Check if line starts a toggleable heading (+++# "Title" or +++#"Title")."""
|
54
|
-
return self._start_pattern.match(context.line.strip()) is not None
|
55
|
-
|
56
|
-
def _is_toggleable_heading_end(self, context: LineProcessingContext) -> bool:
|
57
|
-
"""Check if we need to end a toggleable heading (+++)."""
|
58
|
-
if not self._end_pattern.match(context.line.strip()):
|
59
|
-
return False
|
60
|
-
|
61
|
-
if not context.parent_stack:
|
62
|
-
return False
|
63
|
-
|
64
|
-
# Check if top of stack is a ToggleableHeading
|
65
|
-
current_parent = context.parent_stack[-1]
|
66
|
-
return issubclass(current_parent.element_type, ToggleableHeadingElement)
|
67
|
-
|
68
|
-
async def _start_toggleable_heading(self, context: LineProcessingContext) -> None:
|
69
|
-
"""Start a new toggleable heading block."""
|
70
|
-
toggleable_heading_element = ToggleableHeadingElement()
|
71
|
-
|
72
|
-
# Create the block
|
73
|
-
result = await toggleable_heading_element.markdown_to_notion(context.line)
|
74
|
-
if not result:
|
75
|
-
return
|
76
|
-
|
77
|
-
block = result
|
78
|
-
|
79
|
-
# Push to parent stack
|
80
|
-
parent_context = ParentBlockContext(
|
81
|
-
block=block,
|
82
|
-
element_type=ToggleableHeadingElement,
|
83
|
-
child_lines=[],
|
84
|
-
)
|
85
|
-
context.parent_stack.append(parent_context)
|
86
|
-
|
87
|
-
def _is_toggleable_heading_content(self, context: LineProcessingContext) -> bool:
|
88
|
-
"""Check if we're inside a toggleable heading context and should handle content."""
|
89
|
-
if not context.parent_stack:
|
90
|
-
return False
|
91
|
-
|
92
|
-
current_parent = context.parent_stack[-1]
|
93
|
-
if not issubclass(current_parent.element_type, ToggleableHeadingElement):
|
94
|
-
return False
|
95
|
-
|
96
|
-
# Handle all content inside toggleable heading (not start/end patterns)
|
97
|
-
line = context.line.strip()
|
98
|
-
return not (self._start_pattern.match(line) or self._end_pattern.match(line))
|
99
|
-
|
100
|
-
async def _add_toggleable_heading_content(
|
101
|
-
self, context: LineProcessingContext
|
102
|
-
) -> None:
|
103
|
-
"""Add content to the current toggleable heading context."""
|
104
|
-
context.parent_stack[-1].add_child_line(context.line)
|
105
|
-
|
106
|
-
async def _finalize_toggleable_heading(
|
107
|
-
self, context: LineProcessingContext
|
108
|
-
) -> None:
|
109
|
-
"""Finalize a toggleable heading block and add it to result_blocks."""
|
110
|
-
heading_context = context.parent_stack.pop()
|
111
|
-
|
112
|
-
if heading_context.has_children():
|
113
|
-
all_children = await self._get_all_children(
|
114
|
-
heading_context, context.block_registry
|
115
|
-
)
|
116
|
-
self._assign_heading_children(heading_context.block, all_children)
|
117
|
-
|
118
|
-
# Check if we have a parent context to add this heading to
|
119
|
-
if context.parent_stack:
|
120
|
-
# Add this heading as a child block to the parent
|
121
|
-
parent_context = context.parent_stack[-1]
|
122
|
-
if hasattr(parent_context, "add_child_block"):
|
123
|
-
parent_context.add_child_block(heading_context.block)
|
124
|
-
else:
|
125
|
-
# Fallback: add to result_blocks for backward compatibility
|
126
|
-
context.result_blocks.append(heading_context.block)
|
127
|
-
else:
|
128
|
-
# No parent, add to top level
|
129
|
-
context.result_blocks.append(heading_context.block)
|
130
|
-
|
131
|
-
async def _get_all_children(
|
132
|
-
self, parent_context: ParentBlockContext, block_registry
|
133
|
-
) -> list:
|
134
|
-
"""Helper method to combine text-based and direct block children."""
|
135
|
-
children_blocks = []
|
136
|
-
|
137
|
-
# Process text lines
|
138
|
-
if parent_context.child_lines:
|
139
|
-
children_text = "\n".join(parent_context.child_lines)
|
140
|
-
text_blocks = await self._convert_children_text(
|
141
|
-
children_text, block_registry
|
142
|
-
)
|
143
|
-
children_blocks.extend(text_blocks)
|
144
|
-
|
145
|
-
# Add direct blocks
|
146
|
-
if hasattr(parent_context, "child_blocks") and parent_context.child_blocks:
|
147
|
-
children_blocks.extend(parent_context.child_blocks)
|
148
|
-
|
149
|
-
return children_blocks
|
150
|
-
|
151
|
-
def _assign_heading_children(
|
152
|
-
self, parent_block: BlockCreateRequest, children: list[BlockCreateRequest]
|
153
|
-
) -> None:
|
154
|
-
"""Assign children to toggleable heading blocks."""
|
155
|
-
block_type = parent_block.type
|
156
|
-
|
157
|
-
if block_type == BlockType.HEADING_1:
|
158
|
-
parent_block.heading_1.children = children
|
159
|
-
elif block_type == BlockType.HEADING_2:
|
160
|
-
parent_block.heading_2.children = children
|
161
|
-
elif block_type == BlockType.HEADING_3:
|
162
|
-
parent_block.heading_3.children = children
|
163
|
-
|
164
|
-
async def _convert_children_text(self, text: str, block_registry) -> list:
|
165
|
-
"""Convert children text to blocks."""
|
166
|
-
from notionary.page.writer.markdown_to_notion_converter import (
|
167
|
-
MarkdownToNotionConverter,
|
168
|
-
)
|
169
|
-
|
170
|
-
if not text.strip():
|
171
|
-
return []
|
172
|
-
|
173
|
-
child_converter = MarkdownToNotionConverter(block_registry)
|
174
|
-
return await child_converter.process_lines(text)
|
@@ -1,139 +0,0 @@
|
|
1
|
-
from notionary.blocks.models import BlockCreateRequest
|
2
|
-
from notionary.blocks.registry.block_registry import BlockRegistry
|
3
|
-
from notionary.page.writer.handler import (
|
4
|
-
CodeHandler,
|
5
|
-
ColumnHandler,
|
6
|
-
ColumnListHandler,
|
7
|
-
EquationHandler,
|
8
|
-
LineProcessingContext,
|
9
|
-
ParentBlockContext,
|
10
|
-
RegularLineHandler,
|
11
|
-
TableHandler,
|
12
|
-
ToggleableHeadingHandler,
|
13
|
-
ToggleHandler,
|
14
|
-
)
|
15
|
-
from notionary.page.writer.notion_text_length_processor import (
|
16
|
-
NotionTextLengthProcessor,
|
17
|
-
)
|
18
|
-
from notionary.util.logging_mixin import LoggingMixin
|
19
|
-
|
20
|
-
|
21
|
-
class HandlerOrderValidationError(RuntimeError):
|
22
|
-
"""Raised when handler chain order is incorrect."""
|
23
|
-
|
24
|
-
pass
|
25
|
-
|
26
|
-
|
27
|
-
class MarkdownToNotionConverter(LoggingMixin):
|
28
|
-
"""Converts Markdown text to Notion API block format with unified stack-based processing."""
|
29
|
-
|
30
|
-
def __init__(self, block_registry: BlockRegistry) -> None:
|
31
|
-
self._block_registry = block_registry
|
32
|
-
self._text_length_post_processor = NotionTextLengthProcessor()
|
33
|
-
self._setup_handler_chain()
|
34
|
-
|
35
|
-
async def convert(self, markdown_text: str) -> list[BlockCreateRequest]:
|
36
|
-
if not markdown_text.strip():
|
37
|
-
return []
|
38
|
-
|
39
|
-
all_blocks = await self.process_lines(markdown_text)
|
40
|
-
|
41
|
-
# Apply text length post-processing (truncation)
|
42
|
-
all_blocks = self._text_length_post_processor.process(all_blocks)
|
43
|
-
|
44
|
-
return all_blocks
|
45
|
-
|
46
|
-
async def process_lines(self, text: str) -> list[BlockCreateRequest]:
|
47
|
-
lines = text.split("\n")
|
48
|
-
result_blocks: list[BlockCreateRequest] = []
|
49
|
-
parent_stack: list[ParentBlockContext] = []
|
50
|
-
|
51
|
-
i = 0
|
52
|
-
while i < len(lines):
|
53
|
-
line = lines[i]
|
54
|
-
|
55
|
-
context = LineProcessingContext(
|
56
|
-
line=line,
|
57
|
-
result_blocks=result_blocks,
|
58
|
-
parent_stack=parent_stack,
|
59
|
-
block_registry=self._block_registry,
|
60
|
-
all_lines=lines,
|
61
|
-
current_line_index=i,
|
62
|
-
lines_consumed=0,
|
63
|
-
)
|
64
|
-
|
65
|
-
await self._handler_chain.handle(context)
|
66
|
-
|
67
|
-
# Skip consumed lines
|
68
|
-
i += 1 + context.lines_consumed
|
69
|
-
|
70
|
-
if context.should_continue:
|
71
|
-
continue
|
72
|
-
|
73
|
-
return result_blocks
|
74
|
-
|
75
|
-
def _setup_handler_chain(self) -> None:
|
76
|
-
code_handler = CodeHandler()
|
77
|
-
equation_handler = EquationHandler()
|
78
|
-
table_handler = TableHandler()
|
79
|
-
column_list_handler = ColumnListHandler()
|
80
|
-
column_handler = ColumnHandler()
|
81
|
-
toggle_handler = ToggleHandler()
|
82
|
-
toggleable_heading_handler = ToggleableHeadingHandler()
|
83
|
-
regular_handler = RegularLineHandler()
|
84
|
-
|
85
|
-
# Create handler chain
|
86
|
-
code_handler.set_next(equation_handler).set_next(table_handler).set_next(
|
87
|
-
column_handler
|
88
|
-
).set_next(column_list_handler).set_next(toggleable_heading_handler).set_next(
|
89
|
-
toggle_handler
|
90
|
-
).set_next(
|
91
|
-
regular_handler
|
92
|
-
)
|
93
|
-
|
94
|
-
self._handler_chain = code_handler
|
95
|
-
|
96
|
-
# Validate critical order - only log/error if something is wrong
|
97
|
-
self._validate_handler_order(
|
98
|
-
[
|
99
|
-
code_handler,
|
100
|
-
equation_handler,
|
101
|
-
table_handler,
|
102
|
-
column_handler,
|
103
|
-
column_list_handler,
|
104
|
-
toggleable_heading_handler,
|
105
|
-
toggle_handler,
|
106
|
-
regular_handler,
|
107
|
-
]
|
108
|
-
)
|
109
|
-
|
110
|
-
def _validate_handler_order(self, handlers) -> None:
|
111
|
-
"""Validate critical handler positioning rules - only warns/errors when needed."""
|
112
|
-
handler_classes = [handler.__class__ for handler in handlers]
|
113
|
-
|
114
|
-
# Critical: ColumnHandler MUST come before ColumnListHandler
|
115
|
-
try:
|
116
|
-
column_handler_pos = handler_classes.index(ColumnHandler)
|
117
|
-
column_list_handler_pos = handler_classes.index(ColumnListHandler)
|
118
|
-
|
119
|
-
if column_handler_pos >= column_list_handler_pos:
|
120
|
-
error_msg = (
|
121
|
-
f"CRITICAL: ColumnHandler must come BEFORE ColumnListHandler. "
|
122
|
-
f"Current order: ColumnHandler at {column_handler_pos}, ColumnListHandler at {column_list_handler_pos}. "
|
123
|
-
f"Fix: Move ColumnHandler before ColumnListHandler in _setup_handler_chain()"
|
124
|
-
)
|
125
|
-
self.logger.error(error_msg)
|
126
|
-
raise HandlerOrderValidationError(error_msg)
|
127
|
-
|
128
|
-
except ValueError as e:
|
129
|
-
error_msg = f"Missing required handlers in chain: {e}"
|
130
|
-
self.logger.error(error_msg)
|
131
|
-
raise HandlerOrderValidationError(error_msg)
|
132
|
-
|
133
|
-
# Critical: RegularLineHandler should be last (fallback)
|
134
|
-
if handler_classes[-1] != RegularLineHandler:
|
135
|
-
error_msg = (
|
136
|
-
f"WARNING: RegularLineHandler should be last handler (fallback), "
|
137
|
-
f"but {handler_classes[-1].__name__} is at the end"
|
138
|
-
)
|
139
|
-
self.logger.warning(error_msg)
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# notionary/blocks/context/conversion_context.py
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
from typing import Optional, TYPE_CHECKING
|
5
|
-
from dataclasses import dataclass
|
6
|
-
|
7
|
-
if TYPE_CHECKING:
|
8
|
-
from notionary.database.client import NotionDatabaseClient
|
9
|
-
|
10
|
-
|
11
|
-
@dataclass
|
12
|
-
class ConverterContext:
|
13
|
-
"""
|
14
|
-
Context object that provides dependencies for block conversion operations.
|
15
|
-
"""
|
16
|
-
|
17
|
-
page_id: Optional[str] = None
|
18
|
-
database_client: Optional["NotionDatabaseClient"] = None
|
19
|
-
|
20
|
-
def require_database_client(self) -> NotionDatabaseClient:
|
21
|
-
"""Get database client or raise if not available."""
|
22
|
-
if self.database_client is None:
|
23
|
-
raise ValueError("Database client required but not provided in context")
|
24
|
-
return self.database_client
|
25
|
-
|
26
|
-
def require_page_id(self) -> str:
|
27
|
-
"""Get parent page ID or raise if not available."""
|
28
|
-
if self.page_id is None:
|
29
|
-
raise ValueError("Parent page ID required but not provided in context")
|
30
|
-
return self.page_id
|
File without changes
|
@@ -1,150 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Post-processor for handling Notion API text length limitations.
|
3
|
-
|
4
|
-
Handles text length validation and truncation for blocks that exceed
|
5
|
-
Notion's rich_text character limit of 2000 characters per element.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import TypeGuard, Union
|
9
|
-
|
10
|
-
from notionary.blocks.models import BlockCreateRequest
|
11
|
-
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
12
|
-
from notionary.blocks.types import HasRichText, HasChildren
|
13
|
-
from notionary.util import LoggingMixin
|
14
|
-
|
15
|
-
|
16
|
-
class NotionTextLengthProcessor(LoggingMixin):
|
17
|
-
"""
|
18
|
-
Processes Notion blocks to ensure text content doesn't exceed API limits.
|
19
|
-
|
20
|
-
The Notion API has a limit of 2000 characters per rich_text element.
|
21
|
-
This processor truncates content that exceeds the specified limit.
|
22
|
-
"""
|
23
|
-
|
24
|
-
DEFAULT_MAX_LENGTH = 1900 # Leave some buffer under the 2000 limit
|
25
|
-
|
26
|
-
def __init__(self, max_text_length: int = DEFAULT_MAX_LENGTH) -> None:
|
27
|
-
"""
|
28
|
-
Initialize the processor.
|
29
|
-
|
30
|
-
Args:
|
31
|
-
max_text_length: Maximum allowed text length (default: 1900)
|
32
|
-
"""
|
33
|
-
if max_text_length <= 0:
|
34
|
-
raise ValueError("max_text_length must be positive")
|
35
|
-
if max_text_length > 2000:
|
36
|
-
self.logger.warning(
|
37
|
-
"max_text_length (%d) exceeds Notion's limit of 2000 characters",
|
38
|
-
max_text_length,
|
39
|
-
)
|
40
|
-
|
41
|
-
self.max_text_length = max_text_length
|
42
|
-
|
43
|
-
def process(self, blocks: list[BlockCreateRequest]) -> list[BlockCreateRequest]:
|
44
|
-
"""
|
45
|
-
Process blocks to fix text length limits.
|
46
|
-
"""
|
47
|
-
if not blocks:
|
48
|
-
return blocks
|
49
|
-
|
50
|
-
flattened_blocks = self._flatten_block_list(blocks)
|
51
|
-
return [self._process_single_block(block) for block in flattened_blocks]
|
52
|
-
|
53
|
-
def _process_single_block(self, block: BlockCreateRequest) -> BlockCreateRequest:
|
54
|
-
"""
|
55
|
-
Process a single block to fix text length issues.
|
56
|
-
"""
|
57
|
-
block_copy = block.model_copy(deep=True)
|
58
|
-
|
59
|
-
block_content = self._extract_block_content(block_copy)
|
60
|
-
|
61
|
-
if block_content is not None:
|
62
|
-
self._fix_content_text_lengths(block_content)
|
63
|
-
|
64
|
-
return block_copy
|
65
|
-
|
66
|
-
def _extract_block_content(self, block: BlockCreateRequest) -> object | None:
|
67
|
-
"""
|
68
|
-
Extract the content object from a block using type-safe attribute access.
|
69
|
-
"""
|
70
|
-
# Get the block's content using the block type as attribute name
|
71
|
-
# We assume block.type always exists as per the BlockCreateRequest structure
|
72
|
-
content = getattr(block, block.type, None)
|
73
|
-
|
74
|
-
# Verify it's a valid content object (has rich_text or children)
|
75
|
-
if content and (
|
76
|
-
self._is_rich_text_container(content)
|
77
|
-
or self._is_children_container(content)
|
78
|
-
):
|
79
|
-
return content
|
80
|
-
|
81
|
-
return None
|
82
|
-
|
83
|
-
def _fix_content_text_lengths(self, content: object) -> None:
|
84
|
-
"""
|
85
|
-
Fix text lengths in a content object and its children recursively.
|
86
|
-
"""
|
87
|
-
# Process rich_text if present
|
88
|
-
if self._is_rich_text_container(content):
|
89
|
-
self._truncate_rich_text_content(content.rich_text)
|
90
|
-
|
91
|
-
# Process children recursively if present
|
92
|
-
if self._is_children_container(content):
|
93
|
-
for child in content.children:
|
94
|
-
child_content = self._extract_block_content(child)
|
95
|
-
if child_content:
|
96
|
-
self._fix_content_text_lengths(child_content)
|
97
|
-
|
98
|
-
def _truncate_rich_text_content(self, rich_text_list: list[RichTextObject]) -> None:
|
99
|
-
"""
|
100
|
-
Truncate text content in rich text objects that exceed the limit.
|
101
|
-
"""
|
102
|
-
for rich_text_obj in rich_text_list:
|
103
|
-
if not self._is_text_rich_text_object(rich_text_obj):
|
104
|
-
continue
|
105
|
-
|
106
|
-
content = rich_text_obj.text.content
|
107
|
-
if len(content) > self.max_text_length:
|
108
|
-
self.logger.warning(
|
109
|
-
"Truncating text content from %d to %d characters",
|
110
|
-
len(content),
|
111
|
-
self.max_text_length,
|
112
|
-
)
|
113
|
-
# Truncate the content
|
114
|
-
rich_text_obj.text.content = content[: self.max_text_length]
|
115
|
-
|
116
|
-
def _flatten_block_list(
|
117
|
-
self, blocks: list[Union[BlockCreateRequest, list]]
|
118
|
-
) -> list[BlockCreateRequest]:
|
119
|
-
"""
|
120
|
-
Flatten a potentially nested list of blocks.
|
121
|
-
"""
|
122
|
-
flattened: list[BlockCreateRequest] = []
|
123
|
-
|
124
|
-
for item in blocks:
|
125
|
-
if isinstance(item, list):
|
126
|
-
# Recursively flatten nested lists
|
127
|
-
flattened.extend(self._flatten_block_list(item))
|
128
|
-
else:
|
129
|
-
# Add individual block
|
130
|
-
flattened.append(item)
|
131
|
-
|
132
|
-
return flattened
|
133
|
-
|
134
|
-
def _is_rich_text_container(self, obj: object) -> TypeGuard[HasRichText]:
|
135
|
-
"""Type guard to check if an object has rich_text attribute."""
|
136
|
-
return hasattr(obj, "rich_text") and isinstance(getattr(obj, "rich_text"), list)
|
137
|
-
|
138
|
-
def _is_children_container(self, obj: object) -> TypeGuard[HasChildren]:
|
139
|
-
"""Type guard to check if an object has children attribute."""
|
140
|
-
return hasattr(obj, "children") and isinstance(getattr(obj, "children"), list)
|
141
|
-
|
142
|
-
def _is_text_rich_text_object(
|
143
|
-
self, rich_text_obj: RichTextObject
|
144
|
-
) -> TypeGuard[RichTextObject]:
|
145
|
-
"""Type guard to check if a RichTextObject is of type 'text' with content."""
|
146
|
-
return (
|
147
|
-
rich_text_obj.type == "text"
|
148
|
-
and rich_text_obj.text is not None
|
149
|
-
and rich_text_obj.text.content is not None
|
150
|
-
)
|
notionary/schemas/__init__.py
DELETED