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
@@ -0,0 +1,429 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Self
|
3
|
+
|
4
|
+
from notionary.data_source.properties.models import DataSourceProperty
|
5
|
+
from notionary.data_source.query.schema import (
|
6
|
+
ArrayOperator,
|
7
|
+
BooleanOperator,
|
8
|
+
CompoundFilter,
|
9
|
+
DataSourceQueryParams,
|
10
|
+
DateOperator,
|
11
|
+
FieldType,
|
12
|
+
FilterCondition,
|
13
|
+
InternalFilterCondition,
|
14
|
+
LogicalOperator,
|
15
|
+
NotionFilter,
|
16
|
+
NotionSort,
|
17
|
+
NumberOperator,
|
18
|
+
Operator,
|
19
|
+
OrGroupMarker,
|
20
|
+
PropertyFilter,
|
21
|
+
PropertySort,
|
22
|
+
SortDirection,
|
23
|
+
StringOperator,
|
24
|
+
TimestampSort,
|
25
|
+
TimestampType,
|
26
|
+
)
|
27
|
+
from notionary.data_source.query.validator import OperatorValidator
|
28
|
+
from notionary.exceptions.data_source.properties import DataSourcePropertyNotFound
|
29
|
+
from notionary.utils.date import parse_date
|
30
|
+
|
31
|
+
|
32
|
+
class DataSourceQueryBuilder:
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
properties: dict[str, DataSourceProperty],
|
36
|
+
operator_validator: OperatorValidator | None = None,
|
37
|
+
date_parser: Callable[[str], str] = parse_date,
|
38
|
+
) -> None:
|
39
|
+
self._properties = properties
|
40
|
+
self._operator_validator = operator_validator or OperatorValidator()
|
41
|
+
self._date_parser = date_parser
|
42
|
+
|
43
|
+
self._filters: list[InternalFilterCondition] = []
|
44
|
+
self._sorts: list[NotionSort] = []
|
45
|
+
self._current_property: str | None = None
|
46
|
+
self._negate_next = False
|
47
|
+
self._or_group: list[FilterCondition] | None = None
|
48
|
+
|
49
|
+
def where(self, property_name: str) -> Self:
|
50
|
+
self._finalize_current_or_group()
|
51
|
+
self._ensure_property_exists(property_name)
|
52
|
+
self._select_property_without_negation(property_name)
|
53
|
+
return self
|
54
|
+
|
55
|
+
def where_not(self, property_name: str) -> Self:
|
56
|
+
self._finalize_current_or_group()
|
57
|
+
self._ensure_property_exists(property_name)
|
58
|
+
self._select_property_with_negation(property_name)
|
59
|
+
return self
|
60
|
+
|
61
|
+
def and_where(self, property_name: str) -> Self:
|
62
|
+
return self.where(property_name)
|
63
|
+
|
64
|
+
def and_where_not(self, property_name: str) -> Self:
|
65
|
+
return self.where_not(property_name)
|
66
|
+
|
67
|
+
def or_where(self, property_name: str) -> Self:
|
68
|
+
self._ensure_or_group_exists()
|
69
|
+
self._ensure_property_exists(property_name)
|
70
|
+
self._select_property_without_negation(property_name)
|
71
|
+
return self
|
72
|
+
|
73
|
+
def or_where_not(self, property_name: str) -> Self:
|
74
|
+
self._ensure_or_group_exists()
|
75
|
+
self._ensure_property_exists(property_name)
|
76
|
+
self._select_property_with_negation(property_name)
|
77
|
+
return self
|
78
|
+
|
79
|
+
def equals(self, value: str | int | float) -> Self:
|
80
|
+
return self._add_filter(StringOperator.EQUALS, value)
|
81
|
+
|
82
|
+
def does_not_equal(self, value: str | int | float) -> Self:
|
83
|
+
return self._add_filter(StringOperator.DOES_NOT_EQUAL, value)
|
84
|
+
|
85
|
+
def contains(self, value: str) -> Self:
|
86
|
+
return self._add_filter(StringOperator.CONTAINS, value)
|
87
|
+
|
88
|
+
def does_not_contain(self, value: str) -> Self:
|
89
|
+
return self._add_filter(StringOperator.DOES_NOT_CONTAIN, value)
|
90
|
+
|
91
|
+
def starts_with(self, value: str) -> Self:
|
92
|
+
return self._add_filter(StringOperator.STARTS_WITH, value)
|
93
|
+
|
94
|
+
def ends_with(self, value: str) -> Self:
|
95
|
+
return self._add_filter(StringOperator.ENDS_WITH, value)
|
96
|
+
|
97
|
+
def is_empty(self) -> Self:
|
98
|
+
return self._add_filter(StringOperator.IS_EMPTY, None)
|
99
|
+
|
100
|
+
def is_not_empty(self) -> Self:
|
101
|
+
return self._add_filter(StringOperator.IS_NOT_EMPTY, None)
|
102
|
+
|
103
|
+
def greater_than(self, value: float | int) -> Self:
|
104
|
+
return self._add_filter(NumberOperator.GREATER_THAN, value)
|
105
|
+
|
106
|
+
def greater_than_or_equal_to(self, value: float | int) -> Self:
|
107
|
+
return self._add_filter(NumberOperator.GREATER_THAN_OR_EQUAL_TO, value)
|
108
|
+
|
109
|
+
def less_than(self, value: float | int) -> Self:
|
110
|
+
return self._add_filter(NumberOperator.LESS_THAN, value)
|
111
|
+
|
112
|
+
def less_than_or_equal_to(self, value: float | int) -> Self:
|
113
|
+
return self._add_filter(NumberOperator.LESS_THAN_OR_EQUAL_TO, value)
|
114
|
+
|
115
|
+
def is_true(self) -> Self:
|
116
|
+
return self._add_filter(BooleanOperator.IS_TRUE, None)
|
117
|
+
|
118
|
+
def is_false(self) -> Self:
|
119
|
+
return self._add_filter(BooleanOperator.IS_FALSE, None)
|
120
|
+
|
121
|
+
def before(self, date: str) -> Self:
|
122
|
+
parsed_date = self._date_parser(date)
|
123
|
+
return self._add_filter(DateOperator.BEFORE, parsed_date)
|
124
|
+
|
125
|
+
def after(self, date: str) -> Self:
|
126
|
+
parsed_date = self._date_parser(date)
|
127
|
+
return self._add_filter(DateOperator.AFTER, parsed_date)
|
128
|
+
|
129
|
+
def on_or_before(self, date: str) -> Self:
|
130
|
+
parsed_date = self._date_parser(date)
|
131
|
+
return self._add_filter(DateOperator.ON_OR_BEFORE, parsed_date)
|
132
|
+
|
133
|
+
def on_or_after(self, date: str) -> Self:
|
134
|
+
parsed_date = self._date_parser(date)
|
135
|
+
return self._add_filter(DateOperator.ON_OR_AFTER, parsed_date)
|
136
|
+
|
137
|
+
def array_contains(self, value: str) -> Self:
|
138
|
+
return self._add_filter(ArrayOperator.CONTAINS, value)
|
139
|
+
|
140
|
+
def array_does_not_contain(self, value: str) -> Self:
|
141
|
+
return self._add_filter(ArrayOperator.DOES_NOT_CONTAIN, value)
|
142
|
+
|
143
|
+
def array_is_empty(self) -> Self:
|
144
|
+
return self._add_filter(ArrayOperator.IS_EMPTY, None)
|
145
|
+
|
146
|
+
def array_is_not_empty(self) -> Self:
|
147
|
+
return self._add_filter(ArrayOperator.IS_NOT_EMPTY, None)
|
148
|
+
|
149
|
+
def relation_contains(self, uuid: str) -> Self:
|
150
|
+
return self._add_filter(ArrayOperator.CONTAINS, uuid)
|
151
|
+
|
152
|
+
def relation_is_empty(self) -> Self:
|
153
|
+
return self._add_filter(ArrayOperator.IS_EMPTY, None)
|
154
|
+
|
155
|
+
def people_contains(self, uuid: str) -> Self:
|
156
|
+
return self._add_filter(ArrayOperator.CONTAINS, uuid)
|
157
|
+
|
158
|
+
def people_is_empty(self) -> Self:
|
159
|
+
return self._add_filter(ArrayOperator.IS_EMPTY, None)
|
160
|
+
|
161
|
+
def order_by(self, property_name: str, direction: SortDirection = SortDirection.ASCENDING) -> Self:
|
162
|
+
self._ensure_property_exists(property_name)
|
163
|
+
sort = PropertySort(property=property_name, direction=direction)
|
164
|
+
self._sorts.append(sort)
|
165
|
+
return self
|
166
|
+
|
167
|
+
def order_by_ascending(self, property_name: str) -> Self:
|
168
|
+
return self.order_by(property_name, SortDirection.ASCENDING)
|
169
|
+
|
170
|
+
def order_by_descending(self, property_name: str) -> Self:
|
171
|
+
return self.order_by(property_name, SortDirection.DESCENDING)
|
172
|
+
|
173
|
+
def order_by_created_time(self, direction: SortDirection = SortDirection.DESCENDING) -> Self:
|
174
|
+
sort = TimestampSort(timestamp=TimestampType.CREATED_TIME, direction=direction)
|
175
|
+
self._sorts.append(sort)
|
176
|
+
return self
|
177
|
+
|
178
|
+
def order_by_last_edited_time(self, direction: SortDirection = SortDirection.DESCENDING) -> Self:
|
179
|
+
sort = TimestampSort(timestamp=TimestampType.LAST_EDITED_TIME, direction=direction)
|
180
|
+
self._sorts.append(sort)
|
181
|
+
return self
|
182
|
+
|
183
|
+
def build(self) -> DataSourceQueryParams:
|
184
|
+
self._finalize_current_or_group()
|
185
|
+
notion_filter = self._create_notion_filter_if_needed()
|
186
|
+
sorts = self._create_sorts_if_needed()
|
187
|
+
return DataSourceQueryParams(filter=notion_filter, sorts=sorts)
|
188
|
+
|
189
|
+
def _select_property_without_negation(self, property_name: str) -> None:
|
190
|
+
self._current_property = property_name
|
191
|
+
self._negate_next = False
|
192
|
+
|
193
|
+
def _select_property_with_negation(self, property_name: str) -> None:
|
194
|
+
self._current_property = property_name
|
195
|
+
self._negate_next = True
|
196
|
+
|
197
|
+
def _ensure_property_exists(self, property_name: str) -> None:
|
198
|
+
if self._has_no_properties():
|
199
|
+
return
|
200
|
+
|
201
|
+
if self._property_is_unknown(property_name):
|
202
|
+
self._raise_property_not_found_error(property_name)
|
203
|
+
|
204
|
+
def _has_no_properties(self) -> bool:
|
205
|
+
return not self._properties
|
206
|
+
|
207
|
+
def _property_is_unknown(self, property_name: str) -> bool:
|
208
|
+
return property_name not in self._properties
|
209
|
+
|
210
|
+
def _raise_property_not_found_error(self, property_name: str) -> None:
|
211
|
+
available_properties = list(self._properties.keys())
|
212
|
+
raise DataSourcePropertyNotFound(
|
213
|
+
property_name=property_name,
|
214
|
+
available_properties=available_properties,
|
215
|
+
)
|
216
|
+
|
217
|
+
def _ensure_or_group_exists(self) -> None:
|
218
|
+
if self._or_group_is_not_active():
|
219
|
+
self._start_new_or_group()
|
220
|
+
|
221
|
+
def _or_group_is_not_active(self) -> bool:
|
222
|
+
return self._or_group is None
|
223
|
+
|
224
|
+
def _start_new_or_group(self) -> None:
|
225
|
+
self._or_group = []
|
226
|
+
self._move_last_filter_to_or_group()
|
227
|
+
|
228
|
+
def _move_last_filter_to_or_group(self) -> None:
|
229
|
+
if self._has_no_filters():
|
230
|
+
return
|
231
|
+
|
232
|
+
last_filter = self._filters.pop()
|
233
|
+
if self._is_regular_filter_condition(last_filter):
|
234
|
+
self._or_group.append(last_filter)
|
235
|
+
|
236
|
+
def _has_no_filters(self) -> bool:
|
237
|
+
return not self._filters
|
238
|
+
|
239
|
+
def _is_regular_filter_condition(self, filter_item: InternalFilterCondition) -> bool:
|
240
|
+
return isinstance(filter_item, FilterCondition)
|
241
|
+
|
242
|
+
def _finalize_current_or_group(self) -> None:
|
243
|
+
if self._should_finalize_or_group():
|
244
|
+
self._convert_or_group_to_marker()
|
245
|
+
self._clear_or_group()
|
246
|
+
|
247
|
+
def _should_finalize_or_group(self) -> bool:
|
248
|
+
return self._or_group is not None and len(self._or_group) > 0
|
249
|
+
|
250
|
+
def _convert_or_group_to_marker(self) -> None:
|
251
|
+
or_marker = OrGroupMarker(conditions=self._or_group)
|
252
|
+
self._filters.append(or_marker)
|
253
|
+
|
254
|
+
def _clear_or_group(self) -> None:
|
255
|
+
self._or_group = None
|
256
|
+
|
257
|
+
def _add_filter(
|
258
|
+
self,
|
259
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
260
|
+
value: str | int | float | list[str | int | float] | None,
|
261
|
+
) -> Self:
|
262
|
+
self._ensure_property_is_selected()
|
263
|
+
self._validate_operator_for_current_property(operator)
|
264
|
+
final_operator = self._apply_negation_if_needed(operator)
|
265
|
+
filter_condition = self._create_filter_condition(final_operator, value)
|
266
|
+
self._store_filter_condition(filter_condition)
|
267
|
+
self._reset_current_property()
|
268
|
+
return self
|
269
|
+
|
270
|
+
def _validate_operator_for_current_property(self, operator: Operator) -> None:
|
271
|
+
if not self._current_property or not self._properties:
|
272
|
+
return
|
273
|
+
|
274
|
+
property_obj = self._properties.get(self._current_property)
|
275
|
+
if property_obj:
|
276
|
+
self._operator_validator.validate_operator_for_property(self._current_property, property_obj, operator)
|
277
|
+
return self
|
278
|
+
|
279
|
+
def _ensure_property_is_selected(self) -> None:
|
280
|
+
if self._no_property_is_selected():
|
281
|
+
raise ValueError("No property selected. Use .where(property_name) first.")
|
282
|
+
|
283
|
+
def _no_property_is_selected(self) -> bool:
|
284
|
+
return self._current_property is None
|
285
|
+
|
286
|
+
def _apply_negation_if_needed(
|
287
|
+
self,
|
288
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
289
|
+
) -> StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator:
|
290
|
+
if not self._negate_next:
|
291
|
+
return operator
|
292
|
+
|
293
|
+
negated_operator = self._negate_operator(operator)
|
294
|
+
self._negate_next = False
|
295
|
+
return negated_operator
|
296
|
+
|
297
|
+
def _create_filter_condition(
|
298
|
+
self,
|
299
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
300
|
+
value: str | int | float | list[str | int | float] | None,
|
301
|
+
) -> FilterCondition:
|
302
|
+
field_type = self._determine_field_type_from_operator(operator)
|
303
|
+
return FilterCondition(
|
304
|
+
field=self._current_property,
|
305
|
+
field_type=field_type,
|
306
|
+
operator=operator,
|
307
|
+
value=value,
|
308
|
+
)
|
309
|
+
|
310
|
+
def _store_filter_condition(self, filter_condition: FilterCondition) -> None:
|
311
|
+
if self._or_group_is_active():
|
312
|
+
self._or_group.append(filter_condition)
|
313
|
+
else:
|
314
|
+
self._filters.append(filter_condition)
|
315
|
+
|
316
|
+
def _or_group_is_active(self) -> bool:
|
317
|
+
return self._or_group is not None
|
318
|
+
|
319
|
+
def _reset_current_property(self) -> None:
|
320
|
+
self._current_property = None
|
321
|
+
|
322
|
+
def _create_notion_filter_if_needed(self) -> NotionFilter | None:
|
323
|
+
if self._has_no_filters():
|
324
|
+
return None
|
325
|
+
return self._build_notion_filter()
|
326
|
+
|
327
|
+
def _create_sorts_if_needed(self) -> list[NotionSort] | None:
|
328
|
+
if not self._sorts:
|
329
|
+
return None
|
330
|
+
return self._sorts
|
331
|
+
|
332
|
+
def _build_notion_filter(self) -> NotionFilter:
|
333
|
+
if self._has_single_filter():
|
334
|
+
return self._build_single_filter()
|
335
|
+
return self._build_compound_and_filter()
|
336
|
+
|
337
|
+
def _has_single_filter(self) -> bool:
|
338
|
+
return len(self._filters) == 1
|
339
|
+
|
340
|
+
def _build_single_filter(self) -> NotionFilter:
|
341
|
+
return self._build_filter(self._filters[0])
|
342
|
+
|
343
|
+
def _build_compound_and_filter(self) -> CompoundFilter:
|
344
|
+
property_filters = [self._build_filter(f) for f in self._filters]
|
345
|
+
return CompoundFilter(operator=LogicalOperator.AND, filters=property_filters)
|
346
|
+
|
347
|
+
def _build_filter(self, condition: InternalFilterCondition) -> PropertyFilter | CompoundFilter:
|
348
|
+
if isinstance(condition, OrGroupMarker):
|
349
|
+
return self._build_or_compound_filter(condition)
|
350
|
+
return self._build_property_filter(condition)
|
351
|
+
|
352
|
+
def _build_or_compound_filter(self, or_marker: OrGroupMarker) -> CompoundFilter:
|
353
|
+
property_filters = [self._build_property_filter(c) for c in or_marker.conditions]
|
354
|
+
return CompoundFilter(operator=LogicalOperator.OR, filters=property_filters)
|
355
|
+
|
356
|
+
def _build_property_filter(self, condition: FilterCondition) -> PropertyFilter:
|
357
|
+
property_definition = self._get_property_definition(condition.field)
|
358
|
+
return PropertyFilter(
|
359
|
+
property=condition.field,
|
360
|
+
property_type=property_definition.type,
|
361
|
+
operator=condition.operator,
|
362
|
+
value=condition.value,
|
363
|
+
)
|
364
|
+
|
365
|
+
def _get_property_definition(self, property_name: str) -> DataSourceProperty:
|
366
|
+
property_definition = self._properties.get(property_name)
|
367
|
+
if property_definition is None:
|
368
|
+
self._raise_property_not_found_error(property_name)
|
369
|
+
return property_definition
|
370
|
+
|
371
|
+
def _negate_operator(
|
372
|
+
self,
|
373
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
374
|
+
) -> StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator:
|
375
|
+
negation_map = {
|
376
|
+
StringOperator.EQUALS: StringOperator.DOES_NOT_EQUAL,
|
377
|
+
StringOperator.DOES_NOT_EQUAL: StringOperator.EQUALS,
|
378
|
+
StringOperator.CONTAINS: StringOperator.DOES_NOT_CONTAIN,
|
379
|
+
StringOperator.DOES_NOT_CONTAIN: StringOperator.CONTAINS,
|
380
|
+
StringOperator.IS_EMPTY: StringOperator.IS_NOT_EMPTY,
|
381
|
+
StringOperator.IS_NOT_EMPTY: StringOperator.IS_EMPTY,
|
382
|
+
NumberOperator.EQUALS: NumberOperator.DOES_NOT_EQUAL,
|
383
|
+
NumberOperator.DOES_NOT_EQUAL: NumberOperator.EQUALS,
|
384
|
+
NumberOperator.GREATER_THAN: NumberOperator.LESS_THAN_OR_EQUAL_TO,
|
385
|
+
NumberOperator.GREATER_THAN_OR_EQUAL_TO: NumberOperator.LESS_THAN,
|
386
|
+
NumberOperator.LESS_THAN: NumberOperator.GREATER_THAN_OR_EQUAL_TO,
|
387
|
+
NumberOperator.LESS_THAN_OR_EQUAL_TO: NumberOperator.GREATER_THAN,
|
388
|
+
NumberOperator.IS_EMPTY: NumberOperator.IS_NOT_EMPTY,
|
389
|
+
NumberOperator.IS_NOT_EMPTY: NumberOperator.IS_EMPTY,
|
390
|
+
BooleanOperator.IS_TRUE: BooleanOperator.IS_FALSE,
|
391
|
+
BooleanOperator.IS_FALSE: BooleanOperator.IS_TRUE,
|
392
|
+
DateOperator.BEFORE: DateOperator.ON_OR_AFTER,
|
393
|
+
DateOperator.AFTER: DateOperator.ON_OR_BEFORE,
|
394
|
+
DateOperator.ON_OR_BEFORE: DateOperator.AFTER,
|
395
|
+
DateOperator.ON_OR_AFTER: DateOperator.BEFORE,
|
396
|
+
DateOperator.IS_EMPTY: DateOperator.IS_NOT_EMPTY,
|
397
|
+
DateOperator.IS_NOT_EMPTY: DateOperator.IS_EMPTY,
|
398
|
+
ArrayOperator.CONTAINS: ArrayOperator.DOES_NOT_CONTAIN,
|
399
|
+
ArrayOperator.DOES_NOT_CONTAIN: ArrayOperator.CONTAINS,
|
400
|
+
ArrayOperator.IS_EMPTY: ArrayOperator.IS_NOT_EMPTY,
|
401
|
+
ArrayOperator.IS_NOT_EMPTY: ArrayOperator.IS_EMPTY,
|
402
|
+
}
|
403
|
+
|
404
|
+
if operator not in negation_map:
|
405
|
+
self._raise_operator_cannot_be_negated_error(operator)
|
406
|
+
|
407
|
+
return negation_map[operator]
|
408
|
+
|
409
|
+
def _raise_operator_cannot_be_negated_error(
|
410
|
+
self,
|
411
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
412
|
+
) -> None:
|
413
|
+
raise ValueError(f"Operator '{operator}' cannot be negated. This should not happen - please report this issue.")
|
414
|
+
|
415
|
+
def _determine_field_type_from_operator(
|
416
|
+
self,
|
417
|
+
operator: StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator,
|
418
|
+
) -> FieldType:
|
419
|
+
if isinstance(operator, StringOperator):
|
420
|
+
return FieldType.STRING
|
421
|
+
if isinstance(operator, NumberOperator):
|
422
|
+
return FieldType.NUMBER
|
423
|
+
if isinstance(operator, BooleanOperator):
|
424
|
+
return FieldType.BOOLEAN
|
425
|
+
if isinstance(operator, DateOperator):
|
426
|
+
return FieldType.DATE
|
427
|
+
if isinstance(operator, ArrayOperator):
|
428
|
+
return FieldType.ARRAY
|
429
|
+
return FieldType.STRING
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import re
|
2
|
+
from uuid import UUID
|
3
|
+
|
4
|
+
from notionary.blocks.rich_text.name_id_resolver.page import PageNameIdResolver
|
5
|
+
from notionary.blocks.rich_text.name_id_resolver.person import PersonNameIdResolver
|
6
|
+
from notionary.data_source.query.schema import (
|
7
|
+
CompoundFilter,
|
8
|
+
DataSourceQueryParams,
|
9
|
+
NotionFilter,
|
10
|
+
PropertyFilter,
|
11
|
+
)
|
12
|
+
from notionary.shared.properties.type import PropertyType
|
13
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
14
|
+
|
15
|
+
|
16
|
+
class QueryResolver(LoggingMixin):
|
17
|
+
UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$", re.IGNORECASE)
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
user_resolver: PersonNameIdResolver | None = None,
|
22
|
+
page_resolver: PageNameIdResolver | None = None,
|
23
|
+
):
|
24
|
+
self._user_resolver = user_resolver or PersonNameIdResolver()
|
25
|
+
self._page_resolver = page_resolver or PageNameIdResolver()
|
26
|
+
|
27
|
+
async def resolve_params(self, params: DataSourceQueryParams) -> DataSourceQueryParams:
|
28
|
+
if not params.filter:
|
29
|
+
return params
|
30
|
+
|
31
|
+
resolved_filter = await self._resolve_filter(params.filter)
|
32
|
+
return DataSourceQueryParams(filter=resolved_filter, sorts=params.sorts)
|
33
|
+
|
34
|
+
async def _resolve_filter(self, filter: NotionFilter) -> NotionFilter:
|
35
|
+
if isinstance(filter, PropertyFilter):
|
36
|
+
return await self._resolve_property_filter(filter)
|
37
|
+
elif isinstance(filter, CompoundFilter):
|
38
|
+
return await self._resolve_compound_filter(filter)
|
39
|
+
return filter
|
40
|
+
|
41
|
+
async def _resolve_compound_filter(self, compound: CompoundFilter) -> CompoundFilter:
|
42
|
+
resolved_filters = []
|
43
|
+
for filter in compound.filters:
|
44
|
+
resolved = await self._resolve_filter(filter)
|
45
|
+
resolved_filters.append(resolved)
|
46
|
+
|
47
|
+
return CompoundFilter(operator=compound.operator, filters=resolved_filters)
|
48
|
+
|
49
|
+
async def _resolve_property_filter(self, prop_filter: PropertyFilter) -> PropertyFilter:
|
50
|
+
if not self._is_resolvable_property_type(prop_filter.property_type):
|
51
|
+
return prop_filter
|
52
|
+
|
53
|
+
if prop_filter.value is None:
|
54
|
+
return prop_filter
|
55
|
+
|
56
|
+
if self._is_uuid(prop_filter.value):
|
57
|
+
return prop_filter
|
58
|
+
|
59
|
+
resolved_value = await self._resolve_value(prop_filter.value, prop_filter.property_type)
|
60
|
+
|
61
|
+
return PropertyFilter(
|
62
|
+
property=prop_filter.property,
|
63
|
+
property_type=prop_filter.property_type,
|
64
|
+
operator=prop_filter.operator,
|
65
|
+
value=resolved_value,
|
66
|
+
)
|
67
|
+
|
68
|
+
def _is_resolvable_property_type(self, property_type: PropertyType) -> bool:
|
69
|
+
return property_type in (PropertyType.PEOPLE, PropertyType.RELATION)
|
70
|
+
|
71
|
+
def _is_uuid(self, value: str | int | float | bool | list) -> bool:
|
72
|
+
if not isinstance(value, str):
|
73
|
+
return False
|
74
|
+
|
75
|
+
return self._is_standard_uuid(value) or self._is_notion_style_uuid(value)
|
76
|
+
|
77
|
+
def _is_standard_uuid(self, value: str) -> bool:
|
78
|
+
try:
|
79
|
+
UUID(value)
|
80
|
+
return True
|
81
|
+
except (ValueError, AttributeError):
|
82
|
+
return False
|
83
|
+
|
84
|
+
def _is_notion_style_uuid(self, value: str) -> bool:
|
85
|
+
return bool(self.UUID_PATTERN.match(value))
|
86
|
+
|
87
|
+
async def _resolve_value(self, value: str, property_type: PropertyType) -> str:
|
88
|
+
if property_type == PropertyType.PEOPLE:
|
89
|
+
return await self._resolve_user_name_to_id(value)
|
90
|
+
|
91
|
+
if property_type == PropertyType.RELATION:
|
92
|
+
return await self._resolve_page_name_to_id(value)
|
93
|
+
|
94
|
+
return value
|
95
|
+
|
96
|
+
def _ensure_value_is_string(self, value: str | int | float | bool | list) -> None:
|
97
|
+
if not isinstance(value, str):
|
98
|
+
raise ValueError(f"Cannot resolve non-string value: {value}")
|
99
|
+
|
100
|
+
async def _resolve_user_name_to_id(self, name: str) -> str:
|
101
|
+
resolved = await self._user_resolver.resolve_name_to_id(name)
|
102
|
+
|
103
|
+
if not resolved:
|
104
|
+
raise ValueError(f"Could not resolve user name '{name}' to ID")
|
105
|
+
|
106
|
+
return resolved
|
107
|
+
|
108
|
+
async def _resolve_page_name_to_id(self, name: str) -> str:
|
109
|
+
resolved = await self._page_resolver.resolve_name_to_id(name)
|
110
|
+
|
111
|
+
if not resolved:
|
112
|
+
raise ValueError(f"Could not resolve page name '{name}' to ID")
|
113
|
+
|
114
|
+
return resolved
|