notionary 0.2.27__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.27.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.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 -712
- 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.27.dist-info/RECORD +0 -202
@@ -0,0 +1,169 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from collections.abc import Awaitable, Callable
|
5
|
+
from typing import Self
|
6
|
+
|
7
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
8
|
+
from notionary.data_source.service import NotionDataSource
|
9
|
+
from notionary.database.client import NotionDatabaseHttpClient
|
10
|
+
from notionary.database.database_metadata_update_client import DatabaseMetadataUpdateClient
|
11
|
+
from notionary.database.schemas import NotionDatabaseDto
|
12
|
+
from notionary.shared.entity.dto_parsers import (
|
13
|
+
extract_cover_image_url_from_dto,
|
14
|
+
extract_description,
|
15
|
+
extract_emoji_icon_from_dto,
|
16
|
+
extract_external_icon_url_from_dto,
|
17
|
+
extract_title,
|
18
|
+
)
|
19
|
+
from notionary.shared.entity.service import Entity
|
20
|
+
from notionary.user.schemas import PartialUserDto
|
21
|
+
from notionary.workspace.query.service import WorkspaceQueryService
|
22
|
+
|
23
|
+
type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
|
24
|
+
|
25
|
+
|
26
|
+
class NotionDatabase(Entity):
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
id: str,
|
30
|
+
title: str,
|
31
|
+
created_time: str,
|
32
|
+
created_by: PartialUserDto,
|
33
|
+
last_edited_time: str,
|
34
|
+
last_edited_by: PartialUserDto,
|
35
|
+
url: str,
|
36
|
+
in_trash: bool,
|
37
|
+
is_inline: bool,
|
38
|
+
data_source_ids: list[str],
|
39
|
+
public_url: str | None = None,
|
40
|
+
emoji_icon: str | None = None,
|
41
|
+
external_icon_url: str | None = None,
|
42
|
+
cover_image_url: str | None = None,
|
43
|
+
description: str | None = None,
|
44
|
+
client: NotionDatabaseHttpClient | None = None,
|
45
|
+
metadata_update_client: DatabaseMetadataUpdateClient | None = None,
|
46
|
+
) -> None:
|
47
|
+
super().__init__(
|
48
|
+
id=id,
|
49
|
+
created_time=created_time,
|
50
|
+
created_by=created_by,
|
51
|
+
last_edited_time=last_edited_time,
|
52
|
+
last_edited_by=last_edited_by,
|
53
|
+
in_trash=in_trash,
|
54
|
+
emoji_icon=emoji_icon,
|
55
|
+
external_icon_url=external_icon_url,
|
56
|
+
cover_image_url=cover_image_url,
|
57
|
+
)
|
58
|
+
self._title = title
|
59
|
+
self._url = url
|
60
|
+
self._public_url = public_url
|
61
|
+
self._description = description
|
62
|
+
self._is_inline = is_inline
|
63
|
+
|
64
|
+
self._data_sources: list[NotionDataSource] | None = None
|
65
|
+
self._data_source_ids = data_source_ids
|
66
|
+
|
67
|
+
self.client = client or NotionDatabaseHttpClient(database_id=id)
|
68
|
+
self._metadata_update_client = metadata_update_client or DatabaseMetadataUpdateClient(database_id=id)
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
async def from_id(
|
72
|
+
cls,
|
73
|
+
database_id: str,
|
74
|
+
rich_text_converter: RichTextToMarkdownConverter | None = None,
|
75
|
+
database_client: NotionDatabaseHttpClient | None = None,
|
76
|
+
) -> Self:
|
77
|
+
converter = rich_text_converter or RichTextToMarkdownConverter()
|
78
|
+
client = database_client or NotionDatabaseHttpClient(database_id=database_id)
|
79
|
+
|
80
|
+
async with client:
|
81
|
+
response_dto = await client.get_database()
|
82
|
+
|
83
|
+
return await cls._create_from_dto(response_dto, converter, client)
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
async def from_title(
|
87
|
+
cls,
|
88
|
+
database_title: str,
|
89
|
+
min_similarity: float = 0.6,
|
90
|
+
search_service: WorkspaceQueryService | None = None,
|
91
|
+
) -> Self:
|
92
|
+
service = search_service or WorkspaceQueryService()
|
93
|
+
return await service.find_database(database_title, min_similarity=min_similarity)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
async def _create_from_dto(
|
97
|
+
cls,
|
98
|
+
response: NotionDatabaseDto,
|
99
|
+
rich_text_converter: RichTextToMarkdownConverter,
|
100
|
+
client: NotionDatabaseHttpClient,
|
101
|
+
) -> Self:
|
102
|
+
title, description = await asyncio.gather(
|
103
|
+
extract_title(response, rich_text_converter), extract_description(response, rich_text_converter)
|
104
|
+
)
|
105
|
+
|
106
|
+
return cls(
|
107
|
+
id=response.id,
|
108
|
+
title=title,
|
109
|
+
description=description,
|
110
|
+
created_time=response.created_time,
|
111
|
+
created_by=response.created_by,
|
112
|
+
last_edited_time=response.last_edited_time,
|
113
|
+
last_edited_by=response.last_edited_by,
|
114
|
+
in_trash=response.in_trash,
|
115
|
+
is_inline=response.is_inline,
|
116
|
+
url=response.url,
|
117
|
+
public_url=response.public_url,
|
118
|
+
emoji_icon=extract_emoji_icon_from_dto(response),
|
119
|
+
external_icon_url=extract_external_icon_url_from_dto(response),
|
120
|
+
cover_image_url=extract_cover_image_url_from_dto(response),
|
121
|
+
data_source_ids=[ds.id for ds in response.data_sources],
|
122
|
+
client=client,
|
123
|
+
)
|
124
|
+
|
125
|
+
@property
|
126
|
+
def _entity_metadata_update_client(self) -> DatabaseMetadataUpdateClient:
|
127
|
+
return self._metadata_update_client
|
128
|
+
|
129
|
+
@property
|
130
|
+
def title(self) -> str:
|
131
|
+
return self._title
|
132
|
+
|
133
|
+
@property
|
134
|
+
def url(self) -> str:
|
135
|
+
return self._url
|
136
|
+
|
137
|
+
@property
|
138
|
+
def public_url(self) -> str | None:
|
139
|
+
return self._public_url
|
140
|
+
|
141
|
+
@property
|
142
|
+
def is_inline(self) -> bool:
|
143
|
+
return self._is_inline
|
144
|
+
|
145
|
+
def get_description(self) -> str | None:
|
146
|
+
return self._description
|
147
|
+
|
148
|
+
async def get_data_sources(
|
149
|
+
self,
|
150
|
+
data_source_factory: DataSourceFactory = NotionDataSource.from_id,
|
151
|
+
) -> list[NotionDataSource]:
|
152
|
+
if self._data_sources is None:
|
153
|
+
self._data_sources = await self._load_data_sources(data_source_factory)
|
154
|
+
return self._data_sources
|
155
|
+
|
156
|
+
async def _load_data_sources(
|
157
|
+
self,
|
158
|
+
data_source_factory: DataSourceFactory,
|
159
|
+
) -> list[NotionDataSource]:
|
160
|
+
tasks = [data_source_factory(ds_id) for ds_id in self._data_source_ids]
|
161
|
+
return list(await asyncio.gather(*tasks))
|
162
|
+
|
163
|
+
async def set_title(self, title: str) -> None:
|
164
|
+
result = await self.client.update_database_title(title=title)
|
165
|
+
self._title = result.title[0].plain_text if result.title else ""
|
166
|
+
|
167
|
+
async def set_description(self, description: str) -> None:
|
168
|
+
updated_description = await self.client.update_database_description(description=description)
|
169
|
+
self._description = updated_description
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from .api import (
|
2
|
+
NotionApiError,
|
3
|
+
NotionAuthenticationError,
|
4
|
+
NotionConnectionError,
|
5
|
+
NotionRateLimitError,
|
6
|
+
NotionResourceNotFoundError,
|
7
|
+
NotionServerError,
|
8
|
+
NotionValidationError,
|
9
|
+
)
|
10
|
+
from .base import NotionaryError
|
11
|
+
from .data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
|
12
|
+
from .properties import AccessPagePropertyWithoutDataSourceError, PagePropertyNotFoundError, PagePropertyTypeError
|
13
|
+
from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"AccessPagePropertyWithoutDataSourceError",
|
17
|
+
"DataSourceNotFound",
|
18
|
+
"DataSourcePropertyNotFound",
|
19
|
+
"DataSourcePropertyTypeError",
|
20
|
+
"DatabaseNotFound",
|
21
|
+
"EntityNotFound",
|
22
|
+
"NotionApiError",
|
23
|
+
"NotionAuthenticationError",
|
24
|
+
"NotionConnectionError",
|
25
|
+
"NotionRateLimitError",
|
26
|
+
"NotionResourceNotFoundError",
|
27
|
+
"NotionServerError",
|
28
|
+
"NotionValidationError",
|
29
|
+
"NotionaryError",
|
30
|
+
"PageNotFound",
|
31
|
+
"PagePropertyNotFoundError",
|
32
|
+
"PagePropertyTypeError",
|
33
|
+
]
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from notionary.exceptions.base import NotionaryError
|
2
|
+
|
3
|
+
|
4
|
+
class NotionApiError(NotionaryError):
|
5
|
+
def __init__(
|
6
|
+
self,
|
7
|
+
message: str,
|
8
|
+
status_code: int | None = None,
|
9
|
+
response_text: int | None = None,
|
10
|
+
):
|
11
|
+
super().__init__(message)
|
12
|
+
self.status_code = status_code
|
13
|
+
self.response_text = response_text
|
14
|
+
|
15
|
+
|
16
|
+
class NotionAuthenticationError(NotionApiError):
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
class NotionPermissionError(NotionApiError):
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
class NotionRateLimitError(NotionApiError):
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
28
|
+
class NotionResourceNotFoundError(NotionApiError):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class NotionValidationError(NotionApiError):
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
class NotionServerError(NotionApiError):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class NotionConnectionError(NotionApiError):
|
41
|
+
pass
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from notionary.exceptions.base import NotionaryError
|
2
|
+
|
3
|
+
RATIO_TOLERANCE = 0.0001
|
4
|
+
|
5
|
+
|
6
|
+
class InsufficientColumnsError(NotionaryError):
|
7
|
+
def __init__(self, column_count: int) -> None:
|
8
|
+
self.column_count = column_count
|
9
|
+
super().__init__(f"Columns container must contain at least 2 column blocks, but only {column_count} found")
|
10
|
+
|
11
|
+
|
12
|
+
class InvalidColumnRatioSumError(NotionaryError):
|
13
|
+
def __init__(self, total: float, tolerance: float = RATIO_TOLERANCE) -> None:
|
14
|
+
self.total = total
|
15
|
+
self.tolerance = tolerance
|
16
|
+
super().__init__(f"Width ratios must sum to 1.0 (±{tolerance}), but sum is {total}")
|
@@ -0,0 +1,182 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from notionary.data_source.query.schema import (
|
4
|
+
ArrayOperator,
|
5
|
+
BooleanOperator,
|
6
|
+
DateOperator,
|
7
|
+
NumberOperator,
|
8
|
+
Operator,
|
9
|
+
StringOperator,
|
10
|
+
)
|
11
|
+
from notionary.exceptions.base import NotionaryError
|
12
|
+
from notionary.shared.properties.type import PropertyType
|
13
|
+
|
14
|
+
|
15
|
+
class QueryBuilderError(NotionaryError):
|
16
|
+
def __init__(self, message: str, property_name: str | None = None) -> None:
|
17
|
+
self.property_name = property_name
|
18
|
+
super().__init__(message)
|
19
|
+
|
20
|
+
|
21
|
+
class InvalidOperatorForPropertyType(QueryBuilderError):
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
property_name: str,
|
25
|
+
property_type: PropertyType,
|
26
|
+
operator: Operator,
|
27
|
+
valid_operators: list[Operator],
|
28
|
+
) -> None:
|
29
|
+
self.property_type = property_type
|
30
|
+
self.operator = operator
|
31
|
+
self.valid_operators = valid_operators
|
32
|
+
|
33
|
+
valid_operators_str = ", ".join([f"'{op.value}'" for op in valid_operators])
|
34
|
+
message = (
|
35
|
+
f"Cannot use operator '{operator.value}' on property '{property_name}' of type '{property_type}'. "
|
36
|
+
f"Valid operators for this property type are: {valid_operators_str}"
|
37
|
+
)
|
38
|
+
super().__init__(message, property_name)
|
39
|
+
|
40
|
+
|
41
|
+
class InvalidValueForOperator(QueryBuilderError):
|
42
|
+
def __init__(
|
43
|
+
self,
|
44
|
+
property_name: str,
|
45
|
+
operator: Operator,
|
46
|
+
provided_value: Any,
|
47
|
+
expected_value_type: str,
|
48
|
+
example_value: Any | None = None,
|
49
|
+
) -> None:
|
50
|
+
self.operator = operator
|
51
|
+
self.provided_value = provided_value
|
52
|
+
self.expected_value_type = expected_value_type
|
53
|
+
self.example_value = example_value
|
54
|
+
|
55
|
+
message = (
|
56
|
+
f"Invalid value for operator '{operator.value}' on property '{property_name}'. "
|
57
|
+
f"Expected {expected_value_type}, but got {type(provided_value).__name__}"
|
58
|
+
)
|
59
|
+
|
60
|
+
if example_value is not None:
|
61
|
+
message += f". Example: {example_value}"
|
62
|
+
|
63
|
+
super().__init__(message, property_name)
|
64
|
+
|
65
|
+
|
66
|
+
class MissingRequiredValue(QueryBuilderError):
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
property_name: str,
|
70
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
71
|
+
) -> None:
|
72
|
+
self.operator = operator
|
73
|
+
|
74
|
+
message = f"Operator '{operator.value}' on property '{property_name}' requires a value, but none was provided"
|
75
|
+
super().__init__(message, property_name)
|
76
|
+
|
77
|
+
|
78
|
+
class ValueNotAllowedForOperator(QueryBuilderError):
|
79
|
+
def __init__(
|
80
|
+
self,
|
81
|
+
property_name: str,
|
82
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
83
|
+
) -> None:
|
84
|
+
self.operator = operator
|
85
|
+
|
86
|
+
message = (
|
87
|
+
f"Operator '{operator.value}' on property '{property_name}' does not accept a value. "
|
88
|
+
f"Operators like 'is_empty', 'is_not_empty', 'is_true', 'is_false' don't need values"
|
89
|
+
)
|
90
|
+
super().__init__(message, property_name)
|
91
|
+
|
92
|
+
|
93
|
+
class InvalidDateFormat(QueryBuilderError):
|
94
|
+
def __init__(
|
95
|
+
self,
|
96
|
+
property_name: str,
|
97
|
+
provided_date: str,
|
98
|
+
expected_format: str = "ISO 8601 (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)",
|
99
|
+
) -> None:
|
100
|
+
self.provided_date = provided_date
|
101
|
+
self.expected_format = expected_format
|
102
|
+
|
103
|
+
message = (
|
104
|
+
f"Invalid date format for property '{property_name}'. "
|
105
|
+
f"Provided: '{provided_date}', expected format: {expected_format}"
|
106
|
+
)
|
107
|
+
super().__init__(message, property_name)
|
108
|
+
|
109
|
+
|
110
|
+
class InvalidNumberValue(QueryBuilderError):
|
111
|
+
def __init__(
|
112
|
+
self,
|
113
|
+
property_name: str,
|
114
|
+
provided_value: Any,
|
115
|
+
reason: str,
|
116
|
+
) -> None:
|
117
|
+
self.provided_value = provided_value
|
118
|
+
self.reason = reason
|
119
|
+
|
120
|
+
message = f"Invalid number value for property '{property_name}': {reason}. Provided value: {provided_value}"
|
121
|
+
super().__init__(message, property_name)
|
122
|
+
|
123
|
+
|
124
|
+
class PropertyNotConfigured(QueryBuilderError):
|
125
|
+
def __init__(
|
126
|
+
self,
|
127
|
+
property_name: str,
|
128
|
+
) -> None:
|
129
|
+
message = (
|
130
|
+
f"Property '{property_name}' exists but has no type configuration. "
|
131
|
+
f"Cannot determine valid operators for this property"
|
132
|
+
)
|
133
|
+
super().__init__(message, property_name)
|
134
|
+
|
135
|
+
|
136
|
+
class NoPropertySelected(QueryBuilderError):
|
137
|
+
def __init__(self) -> None:
|
138
|
+
message = (
|
139
|
+
"No property selected. Use .where('property_name') or .where_not('property_name') "
|
140
|
+
"before calling an operator method like .equals(), .contains(), etc."
|
141
|
+
)
|
142
|
+
super().__init__(message)
|
143
|
+
|
144
|
+
|
145
|
+
class EmptyOrGroupError(QueryBuilderError):
|
146
|
+
def __init__(self) -> None:
|
147
|
+
message = (
|
148
|
+
"Cannot create an OR group with no conditions. Add at least one filter condition before using .or_where()"
|
149
|
+
)
|
150
|
+
super().__init__(message)
|
151
|
+
|
152
|
+
|
153
|
+
class ConflictingFiltersError(QueryBuilderError):
|
154
|
+
def __init__(
|
155
|
+
self,
|
156
|
+
property_name: str,
|
157
|
+
description: str,
|
158
|
+
) -> None:
|
159
|
+
message = (
|
160
|
+
f"Conflicting filters detected on property '{property_name}': {description}. "
|
161
|
+
f"This query will never return any results"
|
162
|
+
)
|
163
|
+
super().__init__(message, property_name)
|
164
|
+
|
165
|
+
|
166
|
+
class InvalidSortProperty(QueryBuilderError):
|
167
|
+
def __init__(
|
168
|
+
self,
|
169
|
+
property_name: str,
|
170
|
+
reason: str,
|
171
|
+
available_properties: list[str] | None = None,
|
172
|
+
) -> None:
|
173
|
+
self.available_properties = available_properties
|
174
|
+
self.reason = reason
|
175
|
+
|
176
|
+
message = f"Cannot sort by property '{property_name}': {reason}"
|
177
|
+
|
178
|
+
if available_properties:
|
179
|
+
props_str = ", ".join([f"'{p}'" for p in sorted(available_properties)])
|
180
|
+
message += f". Available properties: {props_str}"
|
181
|
+
|
182
|
+
super().__init__(message, property_name)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import difflib
|
2
|
+
|
3
|
+
from notionary.exceptions.base import NotionaryError
|
4
|
+
|
5
|
+
|
6
|
+
class DataSourcePropertyNotFound(NotionaryError):
|
7
|
+
def __init__(
|
8
|
+
self,
|
9
|
+
property_name: str,
|
10
|
+
available_properties: list[str] | None = None,
|
11
|
+
max_suggestions: int = 5,
|
12
|
+
cutoff: float = 0.6,
|
13
|
+
) -> None:
|
14
|
+
self.property_name = property_name
|
15
|
+
|
16
|
+
# Calculate suggestions from available properties
|
17
|
+
if available_properties:
|
18
|
+
self.suggestions = difflib.get_close_matches(
|
19
|
+
property_name, available_properties, n=max_suggestions, cutoff=cutoff
|
20
|
+
)
|
21
|
+
else:
|
22
|
+
self.suggestions = []
|
23
|
+
|
24
|
+
message = f"Property '{self.property_name}' not found."
|
25
|
+
if self.suggestions:
|
26
|
+
suggestions_str = "', '".join(self.suggestions)
|
27
|
+
message += f" Did you mean '{suggestions_str}'?"
|
28
|
+
super().__init__(message)
|
29
|
+
|
30
|
+
|
31
|
+
class DataSourcePropertyTypeError(NotionaryError):
|
32
|
+
def __init__(self, property_name: str, expected_type: str, actual_type: str) -> None:
|
33
|
+
message = f"Property '{property_name}' has the wrong type. Expected: '{expected_type}', found: '{actual_type}'."
|
34
|
+
super().__init__(message)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import difflib
|
2
|
+
from typing import ClassVar
|
3
|
+
|
4
|
+
from notionary.exceptions.base import NotionaryError
|
5
|
+
from notionary.shared.models.parent import ParentType
|
6
|
+
|
7
|
+
|
8
|
+
class PagePropertyNotFoundError(NotionaryError):
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
page_url: str,
|
12
|
+
property_name: str,
|
13
|
+
available_properties: list[str] | None = None,
|
14
|
+
max_suggestions: int = 3,
|
15
|
+
cutoff: float = 0.6,
|
16
|
+
) -> None:
|
17
|
+
self.property_name = property_name
|
18
|
+
|
19
|
+
if available_properties:
|
20
|
+
self.suggestions = difflib.get_close_matches(
|
21
|
+
property_name, available_properties, n=max_suggestions, cutoff=cutoff
|
22
|
+
)
|
23
|
+
else:
|
24
|
+
self.suggestions = []
|
25
|
+
|
26
|
+
message = f"Property '{property_name}' not found."
|
27
|
+
if self.suggestions:
|
28
|
+
suggestions_str = "', '".join(self.suggestions)
|
29
|
+
message += f" Did you mean '{suggestions_str}'?"
|
30
|
+
message += f" Please check the page at {page_url} to verify if the property exists and is correctly named."
|
31
|
+
super().__init__(message)
|
32
|
+
|
33
|
+
|
34
|
+
class PagePropertyTypeError(NotionaryError):
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
property_name: str,
|
38
|
+
actual_type: str,
|
39
|
+
) -> None:
|
40
|
+
message = f"Property '{property_name}' is of type '{actual_type}'. Use the appropriate getter method for this property type."
|
41
|
+
super().__init__(message)
|
42
|
+
|
43
|
+
|
44
|
+
class AccessPagePropertyWithoutDataSourceError(NotionaryError):
|
45
|
+
_PARENT_DESCRIPTIONS: ClassVar[dict[ParentType, str]] = {
|
46
|
+
ParentType.WORKSPACE: "the workspace itself",
|
47
|
+
ParentType.PAGE_ID: "another page",
|
48
|
+
ParentType.BLOCK_ID: "a block",
|
49
|
+
ParentType.DATABASE_ID: "a database",
|
50
|
+
}
|
51
|
+
|
52
|
+
def __init__(self, parent_type: ParentType) -> None:
|
53
|
+
parent_desc = self._PARENT_DESCRIPTIONS.get(parent_type, f"its parent type is '{parent_type}'")
|
54
|
+
message = (
|
55
|
+
f"Cannot access properties other than title because this page's parent is {parent_desc}. "
|
56
|
+
"To use operations like property reading/writing, you need to use a page whose parent is a data source."
|
57
|
+
)
|
58
|
+
super().__init__(message)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from notionary.exceptions.base import NotionaryError
|
2
|
+
|
3
|
+
|
4
|
+
class EntityNotFound(NotionaryError):
|
5
|
+
def __init__(self, entity_type: str, query: str, available_titles: list[str] | None = None) -> None:
|
6
|
+
self.entity_type = entity_type
|
7
|
+
self.query = query
|
8
|
+
self.available_titles = available_titles or []
|
9
|
+
|
10
|
+
if self.available_titles:
|
11
|
+
message = (
|
12
|
+
f"No sufficiently similar {entity_type} found for query '{query}'. "
|
13
|
+
f"Did you mean one of these? Top results: {self.available_titles}"
|
14
|
+
)
|
15
|
+
else:
|
16
|
+
message = f"No {entity_type} found for query '{query}'. The search returned no results."
|
17
|
+
|
18
|
+
super().__init__(message)
|
19
|
+
|
20
|
+
|
21
|
+
class PageNotFound(EntityNotFound):
|
22
|
+
def __init__(self, query: str, available_titles: list[str] | None = None) -> None:
|
23
|
+
super().__init__("page", query, available_titles)
|
24
|
+
|
25
|
+
|
26
|
+
class DataSourceNotFound(EntityNotFound):
|
27
|
+
def __init__(self, query: str, available_titles: list[str] | None = None) -> None:
|
28
|
+
super().__init__("data source", query, available_titles)
|
29
|
+
|
30
|
+
|
31
|
+
class DatabaseNotFound(EntityNotFound):
|
32
|
+
def __init__(self, query: str, available_titles: list[str] | None = None) -> None:
|
33
|
+
super().__init__("database", query, available_titles)
|