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,67 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Annotated, Literal
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field
|
5
|
+
|
6
|
+
|
7
|
+
class UserType(StrEnum):
|
8
|
+
PERSON = "person"
|
9
|
+
BOT = "bot"
|
10
|
+
|
11
|
+
|
12
|
+
class WorkspaceOwnerType(StrEnum):
|
13
|
+
USER = "user"
|
14
|
+
WORKSPACE = "workspace"
|
15
|
+
|
16
|
+
|
17
|
+
class PersonUserDto(BaseModel):
|
18
|
+
email: str | None = None
|
19
|
+
|
20
|
+
|
21
|
+
class BotOwnerDto(BaseModel):
|
22
|
+
type: WorkspaceOwnerType
|
23
|
+
workspace: bool | None = None
|
24
|
+
|
25
|
+
|
26
|
+
class WorkspaceLimits(BaseModel):
|
27
|
+
max_file_upload_size_in_bytes: int
|
28
|
+
|
29
|
+
|
30
|
+
class BotUserDto(BaseModel):
|
31
|
+
owner: BotOwnerDto | None = None
|
32
|
+
workspace_name: str | None = None
|
33
|
+
workspace_limits: WorkspaceLimits | None = None
|
34
|
+
|
35
|
+
|
36
|
+
class NotionUserBase(BaseModel):
|
37
|
+
object: Literal["user"] = "user"
|
38
|
+
id: str
|
39
|
+
|
40
|
+
type: UserType
|
41
|
+
|
42
|
+
name: str | None = None
|
43
|
+
avatar_url: str | None = None
|
44
|
+
|
45
|
+
|
46
|
+
class PersonUserResponseDto(NotionUserBase):
|
47
|
+
type: Literal[UserType.PERSON] = UserType.PERSON
|
48
|
+
person: PersonUserDto
|
49
|
+
|
50
|
+
|
51
|
+
class BotUserResponseDto(NotionUserBase):
|
52
|
+
type: Literal[UserType.BOT] = UserType.BOT
|
53
|
+
bot: BotUserDto
|
54
|
+
|
55
|
+
|
56
|
+
UserResponseDto = Annotated[PersonUserResponseDto | BotUserResponseDto, Field(discriminator="type")]
|
57
|
+
|
58
|
+
|
59
|
+
class NotionUsersListResponse(BaseModel):
|
60
|
+
results: list[UserResponseDto]
|
61
|
+
next_cursor: str | None = None
|
62
|
+
has_more: bool
|
63
|
+
|
64
|
+
|
65
|
+
class PartialUserDto(BaseModel):
|
66
|
+
object: Literal["user"] = "user"
|
67
|
+
id: str
|
@@ -0,0 +1,65 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import AsyncIterator
|
4
|
+
|
5
|
+
from notionary.user import BotUser, PersonUser
|
6
|
+
from notionary.user.client import UserHttpClient
|
7
|
+
from notionary.user.schemas import UserType
|
8
|
+
|
9
|
+
|
10
|
+
class UserService:
|
11
|
+
def __init__(self, client: UserHttpClient | None = None) -> None:
|
12
|
+
self._client = client or UserHttpClient()
|
13
|
+
|
14
|
+
async def list_users(self) -> list[PersonUser]:
|
15
|
+
all_users = await self._client.get_all_workspace_users()
|
16
|
+
person_users = [user for user in all_users if user.type == UserType.PERSON]
|
17
|
+
|
18
|
+
return [PersonUser.from_dto(user) for user in person_users]
|
19
|
+
|
20
|
+
async def list_users_stream(self) -> AsyncIterator[PersonUser]:
|
21
|
+
all_users = await self._client.get_all_workspace_users()
|
22
|
+
for user in all_users:
|
23
|
+
if user.type == UserType.PERSON:
|
24
|
+
yield PersonUser.from_dto(user)
|
25
|
+
|
26
|
+
async def list_bot_users(self) -> list[BotUser]:
|
27
|
+
all_users = await self._client.get_all_workspace_users()
|
28
|
+
bot_users = [user for user in all_users if user.type == UserType.BOT]
|
29
|
+
|
30
|
+
return [BotUser.from_dto(user) for user in bot_users]
|
31
|
+
|
32
|
+
async def list_bot_users_stream(self) -> AsyncIterator[BotUser]:
|
33
|
+
all_users = await self._client.get_all_workspace_users()
|
34
|
+
for user in all_users:
|
35
|
+
if user.type == UserType.BOT:
|
36
|
+
yield BotUser.from_dto(user)
|
37
|
+
|
38
|
+
async def search_users(self, query: str) -> list[PersonUser]:
|
39
|
+
all_person_users = await self.list_users()
|
40
|
+
query_lower = query.lower()
|
41
|
+
|
42
|
+
return [
|
43
|
+
user
|
44
|
+
for user in all_person_users
|
45
|
+
if query_lower in (user.name or "").lower() or query_lower in (user.email or "").lower()
|
46
|
+
]
|
47
|
+
|
48
|
+
async def search_users_stream(self, query: str) -> AsyncIterator[PersonUser]:
|
49
|
+
query_lower = query.lower()
|
50
|
+
|
51
|
+
async for user in self.list_users_stream():
|
52
|
+
if query_lower in (user.name or "").lower() or query_lower in (user.email or "").lower():
|
53
|
+
yield user
|
54
|
+
|
55
|
+
async def get_current_bot(self) -> BotUser:
|
56
|
+
bot_dto = await self._client.get_current_integration_bot()
|
57
|
+
return BotUser.from_dto(bot_dto)
|
58
|
+
|
59
|
+
async def get_user_by_id(self, user_id: str) -> PersonUser | BotUser | None:
|
60
|
+
user_dto = await self._client.get_user_by_id(user_id)
|
61
|
+
|
62
|
+
if user_dto.type == UserType.PERSON:
|
63
|
+
return PersonUser.from_dto(user_dto)
|
64
|
+
else:
|
65
|
+
return BotUser.from_dto(user_dto)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
from collections.abc import Callable
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
|
7
|
+
def async_retry(
|
8
|
+
max_retries: int = 3,
|
9
|
+
initial_delay: float = 1.0,
|
10
|
+
backoff_factor: float = 2.0,
|
11
|
+
retry_on_exceptions: tuple[type[Exception], ...] | None = None,
|
12
|
+
):
|
13
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
14
|
+
@functools.wraps(func)
|
15
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
16
|
+
delay = initial_delay
|
17
|
+
last_exception = None
|
18
|
+
|
19
|
+
for attempt in range(max_retries + 1):
|
20
|
+
try:
|
21
|
+
return await func(*args, **kwargs)
|
22
|
+
except Exception as e:
|
23
|
+
last_exception = e
|
24
|
+
|
25
|
+
# If specific exceptions are defined, only retry those
|
26
|
+
if retry_on_exceptions is not None and not isinstance(e, retry_on_exceptions):
|
27
|
+
raise
|
28
|
+
|
29
|
+
if attempt == max_retries:
|
30
|
+
raise
|
31
|
+
|
32
|
+
await asyncio.sleep(delay)
|
33
|
+
delay *= backoff_factor
|
34
|
+
|
35
|
+
raise last_exception
|
36
|
+
|
37
|
+
return wrapper
|
38
|
+
|
39
|
+
return decorator
|
notionary/utils/date.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
|
4
|
+
def parse_date(date_str: str) -> str:
|
5
|
+
supported_formats = _get_supported_date_formats()
|
6
|
+
|
7
|
+
for date_format in supported_formats:
|
8
|
+
parsed_date = _try_parse_date_with_format(date_str, date_format)
|
9
|
+
if _date_was_successfully_parsed(parsed_date):
|
10
|
+
return _convert_to_iso_format(parsed_date)
|
11
|
+
|
12
|
+
_raise_invalid_date_format_error(date_str)
|
13
|
+
|
14
|
+
|
15
|
+
def _get_supported_date_formats() -> list[str]:
|
16
|
+
return [
|
17
|
+
"%Y-%m-%d", # ISO: 2024-12-31
|
18
|
+
"%d.%m.%Y", # German: 31.12.2024
|
19
|
+
"%m/%d/%Y", # US with slash: 12/31/2024
|
20
|
+
"%m-%d-%Y", # US with dash: 12-31-2024
|
21
|
+
"%d/%m/%Y", # Day first with slash: 31/12/2024
|
22
|
+
"%d-%m-%Y", # Day first with dash: 31-12-2024
|
23
|
+
"%d-%b-%Y", # Short month: 31-Dec-2024
|
24
|
+
"%d %b %Y", # Short month with space: 31 Dec 2024
|
25
|
+
"%d-%B-%Y", # Full month: 31-December-2024
|
26
|
+
"%d %B %Y", # Full month with space: 31 December 2024
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
def _try_parse_date_with_format(date_str: str, date_format: str) -> datetime | None:
|
31
|
+
try:
|
32
|
+
return datetime.strptime(date_str, date_format)
|
33
|
+
except ValueError:
|
34
|
+
return None
|
35
|
+
|
36
|
+
|
37
|
+
def _date_was_successfully_parsed(parsed_date: datetime | None) -> bool:
|
38
|
+
return parsed_date is not None
|
39
|
+
|
40
|
+
|
41
|
+
def _convert_to_iso_format(parsed_date: datetime) -> str:
|
42
|
+
return parsed_date.strftime("%Y-%m-%d")
|
43
|
+
|
44
|
+
|
45
|
+
def _raise_invalid_date_format_error(date_str: str) -> None:
|
46
|
+
error_message = (
|
47
|
+
f"Invalid date format: '{date_str}'. "
|
48
|
+
f"Supported formats: YYYY-MM-DD, DD.MM.YYYY, MM/DD/YYYY, DD/MM/YYYY, "
|
49
|
+
f"DD-Mon-YYYY, DD Month YYYY"
|
50
|
+
)
|
51
|
+
raise ValueError(error_message)
|
notionary/utils/fuzzy.py
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
import difflib
|
2
|
+
from collections.abc import Callable
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any, Generic, TypeVar
|
5
|
+
|
6
|
+
T = TypeVar("T")
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass(frozen=True)
|
10
|
+
class _MatchResult(Generic[T]):
|
11
|
+
item: Any
|
12
|
+
similarity: float
|
13
|
+
|
14
|
+
|
15
|
+
def find_best_match(
|
16
|
+
query: str,
|
17
|
+
items: list[T],
|
18
|
+
text_extractor: Callable[[T], str],
|
19
|
+
min_similarity: float | None = 0.0,
|
20
|
+
) -> T | None:
|
21
|
+
min_similarity = 0.0 if min_similarity is None else min_similarity
|
22
|
+
|
23
|
+
matches = _find_best_matches(query, items, text_extractor, min_similarity, limit=1)
|
24
|
+
return matches[0].item if matches else None
|
25
|
+
|
26
|
+
|
27
|
+
def _find_best_matches(
|
28
|
+
query: str,
|
29
|
+
items: list[T],
|
30
|
+
text_extractor: Callable[[T], str],
|
31
|
+
min_similarity: float = 0.0,
|
32
|
+
limit: int | None = None,
|
33
|
+
) -> list[_MatchResult[T]]:
|
34
|
+
results = []
|
35
|
+
|
36
|
+
for item in items:
|
37
|
+
text = text_extractor(item)
|
38
|
+
similarity = _calculate_similarity(query, text)
|
39
|
+
|
40
|
+
if similarity >= min_similarity:
|
41
|
+
results.append(_MatchResult(item=item, similarity=similarity))
|
42
|
+
|
43
|
+
results = _sort_by_highest_similarity_first(results)
|
44
|
+
|
45
|
+
if limit:
|
46
|
+
return results[:limit]
|
47
|
+
|
48
|
+
return results
|
49
|
+
|
50
|
+
|
51
|
+
def _sort_by_highest_similarity_first(results: list[_MatchResult]) -> list[_MatchResult]:
|
52
|
+
return sorted(results, key=lambda x: x.similarity, reverse=True)
|
53
|
+
|
54
|
+
|
55
|
+
def _calculate_similarity(query: str, target: str) -> float:
|
56
|
+
return difflib.SequenceMatcher(None, query.lower().strip(), target.lower().strip()).ratio()
|
@@ -1,16 +1,9 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
-
from typing import ClassVar
|
3
|
+
from typing import ClassVar
|
4
4
|
|
5
|
-
from dotenv import load_dotenv
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def setup_logging():
|
11
|
-
"""
|
12
|
-
Sets up logging configuration for the application.
|
13
|
-
"""
|
6
|
+
def _setup_logging() -> None:
|
14
7
|
log_level = os.getenv("LOG_LEVEL", "WARNING").upper()
|
15
8
|
logging.basicConfig(
|
16
9
|
level=getattr(logging, log_level),
|
@@ -20,18 +13,13 @@ def setup_logging():
|
|
20
13
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
21
14
|
|
22
15
|
|
23
|
-
|
16
|
+
_setup_logging()
|
24
17
|
|
25
18
|
|
26
19
|
class LoggingMixin:
|
27
|
-
# Class attribute with proper typing
|
28
20
|
logger: ClassVar[logging.Logger] = None
|
29
21
|
|
30
22
|
def __init_subclass__(cls, **kwargs):
|
31
|
-
"""
|
32
|
-
This method is called when a class inherits from LoggingMixin.
|
33
|
-
It automatically sets up the logger as a class attribute.
|
34
|
-
"""
|
35
23
|
super().__init_subclass__(**kwargs)
|
36
24
|
cls.logger = logging.getLogger(cls.__name__)
|
37
25
|
|
@@ -43,7 +31,7 @@ class LoggingMixin:
|
|
43
31
|
return self._logger
|
44
32
|
|
45
33
|
@staticmethod
|
46
|
-
def _get_class_name_from_frame(frame) ->
|
34
|
+
def _get_class_name_from_frame(frame) -> str | None:
|
47
35
|
local_vars = frame.f_locals
|
48
36
|
if "self" in local_vars:
|
49
37
|
return local_vars["self"].__class__.__name__
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from collections.abc import AsyncGenerator, Callable, Coroutine
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class PaginatedResponse(BaseModel):
|
8
|
+
results: list[Any]
|
9
|
+
has_more: bool
|
10
|
+
next_cursor: str | None
|
11
|
+
|
12
|
+
|
13
|
+
async def _fetch_pages(
|
14
|
+
api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
|
15
|
+
**kwargs,
|
16
|
+
) -> AsyncGenerator[PaginatedResponse]:
|
17
|
+
next_cursor = None
|
18
|
+
has_more = True
|
19
|
+
|
20
|
+
while has_more:
|
21
|
+
current_kwargs = kwargs.copy()
|
22
|
+
if next_cursor:
|
23
|
+
current_kwargs["start_cursor"] = next_cursor
|
24
|
+
|
25
|
+
response = await api_call(**current_kwargs)
|
26
|
+
yield response
|
27
|
+
|
28
|
+
has_more = response.has_more
|
29
|
+
next_cursor = response.next_cursor
|
30
|
+
|
31
|
+
|
32
|
+
async def paginate_notion_api(
|
33
|
+
api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
|
34
|
+
**kwargs,
|
35
|
+
) -> list[Any]:
|
36
|
+
all_results = []
|
37
|
+
async for page in _fetch_pages(api_call, **kwargs):
|
38
|
+
if page.results:
|
39
|
+
all_results.extend(page.results)
|
40
|
+
return all_results
|
41
|
+
|
42
|
+
|
43
|
+
async def paginate_notion_api_generator(
|
44
|
+
api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
|
45
|
+
**kwargs,
|
46
|
+
) -> AsyncGenerator[Any]:
|
47
|
+
async for page in _fetch_pages(api_call, **kwargs):
|
48
|
+
if page.results:
|
49
|
+
for item in page.results:
|
50
|
+
yield item
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
def singleton(cls) -> Callable[..., Any]:
|
6
|
+
instances = {}
|
7
|
+
|
8
|
+
def get_instance(*args, **kwargs) -> Any:
|
9
|
+
if cls not in instances:
|
10
|
+
instances[cls] = cls(*args, **kwargs)
|
11
|
+
return instances[cls]
|
12
|
+
|
13
|
+
return get_instance
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
|
4
|
+
def extract_uuid(source: str) -> str | None:
|
5
|
+
UUID_RAW_PATTERN = r"([a-f0-9]{32})"
|
6
|
+
|
7
|
+
if _is_valid_uuid(source):
|
8
|
+
return source
|
9
|
+
|
10
|
+
match = re.search(UUID_RAW_PATTERN, source.lower())
|
11
|
+
if not match:
|
12
|
+
return None
|
13
|
+
|
14
|
+
uuid_raw = match.group(1)
|
15
|
+
return f"{uuid_raw[0:8]}-{uuid_raw[8:12]}-{uuid_raw[12:16]}-{uuid_raw[16:20]}-{uuid_raw[20:32]}"
|
16
|
+
|
17
|
+
|
18
|
+
def _is_valid_uuid(uuid: str) -> bool:
|
19
|
+
UUID_PATTERN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
20
|
+
return bool(re.match(UUID_PATTERN, uuid.lower()))
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from collections.abc import AsyncGenerator
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from notionary.data_source.schemas import DataSourceDto
|
5
|
+
from notionary.http.client import NotionHttpClient
|
6
|
+
from notionary.page.schemas import NotionPageDto
|
7
|
+
from notionary.utils.pagination import paginate_notion_api_generator
|
8
|
+
from notionary.workspace.query.models import WorkspaceQueryConfig
|
9
|
+
from notionary.workspace.schemas import DataSourceSearchResponse, PageSearchResponse
|
10
|
+
|
11
|
+
|
12
|
+
class WorkspaceClient:
|
13
|
+
DEFAULT_PAGE_SIZE = 100
|
14
|
+
|
15
|
+
def __init__(self, http_client: NotionHttpClient | None = None) -> None:
|
16
|
+
self._http_client = http_client or NotionHttpClient()
|
17
|
+
|
18
|
+
async def query_pages_stream(
|
19
|
+
self,
|
20
|
+
search_config: WorkspaceQueryConfig,
|
21
|
+
) -> AsyncGenerator[NotionPageDto]:
|
22
|
+
async for page in paginate_notion_api_generator(
|
23
|
+
self._query_pages,
|
24
|
+
search_config=search_config,
|
25
|
+
):
|
26
|
+
yield page
|
27
|
+
|
28
|
+
async def query_data_sources_stream(
|
29
|
+
self,
|
30
|
+
search_config: WorkspaceQueryConfig,
|
31
|
+
) -> AsyncGenerator[DataSourceDto]:
|
32
|
+
async for data_source in paginate_notion_api_generator(
|
33
|
+
self._query_data_sources,
|
34
|
+
search_config=search_config,
|
35
|
+
):
|
36
|
+
yield data_source
|
37
|
+
|
38
|
+
async def _query_pages(
|
39
|
+
self,
|
40
|
+
search_config: WorkspaceQueryConfig,
|
41
|
+
start_cursor: str | None = None,
|
42
|
+
) -> PageSearchResponse:
|
43
|
+
if start_cursor:
|
44
|
+
search_config.start_cursor = start_cursor
|
45
|
+
|
46
|
+
response = await self._execute_search(search_config)
|
47
|
+
return PageSearchResponse.model_validate(response)
|
48
|
+
|
49
|
+
async def _query_data_sources(
|
50
|
+
self,
|
51
|
+
search_config: WorkspaceQueryConfig,
|
52
|
+
start_cursor: str | None = None,
|
53
|
+
) -> DataSourceSearchResponse:
|
54
|
+
if start_cursor:
|
55
|
+
search_config.start_cursor = start_cursor
|
56
|
+
|
57
|
+
response = await self._execute_search(search_config)
|
58
|
+
return DataSourceSearchResponse.model_validate(response)
|
59
|
+
|
60
|
+
async def _execute_search(self, config: WorkspaceQueryConfig) -> dict[str, Any]:
|
61
|
+
serialized_config = config.model_dump(exclude_none=True, by_alias=True)
|
62
|
+
return await self._http_client.post("search", serialized_config)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from typing import Self
|
2
|
+
|
3
|
+
from notionary.workspace.query.models import (
|
4
|
+
SortDirection,
|
5
|
+
SortTimestamp,
|
6
|
+
WorkspaceQueryConfig,
|
7
|
+
WorkspaceQueryObjectType,
|
8
|
+
)
|
9
|
+
|
10
|
+
|
11
|
+
class WorkspaceQueryConfigBuilder:
|
12
|
+
def __init__(self, config: WorkspaceQueryConfig = None) -> None:
|
13
|
+
self.config = config or WorkspaceQueryConfig()
|
14
|
+
|
15
|
+
def with_query(self, query: str) -> Self:
|
16
|
+
self.config.query = query
|
17
|
+
return self
|
18
|
+
|
19
|
+
def with_pages_only(self) -> Self:
|
20
|
+
self.config.object_type = WorkspaceQueryObjectType.PAGE
|
21
|
+
return self
|
22
|
+
|
23
|
+
def with_data_sources_only(self) -> Self:
|
24
|
+
self.config.object_type = WorkspaceQueryObjectType.DATA_SOURCE
|
25
|
+
return self
|
26
|
+
|
27
|
+
def with_sort_direction(self, direction: SortDirection) -> Self:
|
28
|
+
self.config.sort_direction = direction
|
29
|
+
return self
|
30
|
+
|
31
|
+
def with_sort_ascending(self) -> Self:
|
32
|
+
return self.with_sort_direction(SortDirection.ASCENDING)
|
33
|
+
|
34
|
+
def with_sort_descending(self) -> Self:
|
35
|
+
return self.with_sort_direction(SortDirection.DESCENDING)
|
36
|
+
|
37
|
+
def with_sort_timestamp(self, timestamp: SortTimestamp) -> Self:
|
38
|
+
self.config.sort_timestamp = timestamp
|
39
|
+
return self
|
40
|
+
|
41
|
+
def with_sort_by_created_time(self) -> Self:
|
42
|
+
return self.with_sort_timestamp(SortTimestamp.CREATED_TIME)
|
43
|
+
|
44
|
+
def with_sort_by_last_edited(self) -> Self:
|
45
|
+
return self.with_sort_timestamp(SortTimestamp.LAST_EDITED_TIME)
|
46
|
+
|
47
|
+
def with_page_size(self, size: int) -> Self:
|
48
|
+
self.config.page_size = min(size, 100)
|
49
|
+
return self
|
50
|
+
|
51
|
+
def with_start_cursor(self, cursor: str | None) -> Self:
|
52
|
+
self.config.start_cursor = cursor
|
53
|
+
return self
|
54
|
+
|
55
|
+
def without_cursor(self) -> Self:
|
56
|
+
self.config.start_cursor = None
|
57
|
+
return self
|
58
|
+
|
59
|
+
def build(self) -> WorkspaceQueryConfig:
|
60
|
+
return self.config
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field, field_validator, model_serializer
|
5
|
+
|
6
|
+
|
7
|
+
class SortDirection(StrEnum):
|
8
|
+
ASCENDING = "ascending"
|
9
|
+
DESCENDING = "descending"
|
10
|
+
|
11
|
+
|
12
|
+
class SortTimestamp(StrEnum):
|
13
|
+
LAST_EDITED_TIME = "last_edited_time"
|
14
|
+
CREATED_TIME = "created_time"
|
15
|
+
|
16
|
+
|
17
|
+
class WorkspaceQueryObjectType(StrEnum):
|
18
|
+
PAGE = "page"
|
19
|
+
DATA_SOURCE = "data_source"
|
20
|
+
|
21
|
+
|
22
|
+
class WorkspaceQueryConfig(BaseModel):
|
23
|
+
query: str | None = None
|
24
|
+
object_type: WorkspaceQueryObjectType | None = None
|
25
|
+
sort_direction: SortDirection = SortDirection.DESCENDING
|
26
|
+
sort_timestamp: SortTimestamp = SortTimestamp.LAST_EDITED_TIME
|
27
|
+
page_size: int = Field(default=100, ge=1, le=100)
|
28
|
+
start_cursor: str | None = None
|
29
|
+
|
30
|
+
@field_validator("query")
|
31
|
+
@classmethod
|
32
|
+
def replace_empty_query_with_none(cls, value: str | None) -> str | None:
|
33
|
+
if value is not None and not value.strip():
|
34
|
+
return None
|
35
|
+
return value
|
36
|
+
|
37
|
+
@model_serializer
|
38
|
+
def serialize_model(self) -> dict[str, Any]:
|
39
|
+
search_dict: dict[str, Any] = {}
|
40
|
+
|
41
|
+
if self.query:
|
42
|
+
search_dict["query"] = self.query
|
43
|
+
|
44
|
+
if self.object_type:
|
45
|
+
search_dict["filter"] = {
|
46
|
+
"property": "object",
|
47
|
+
"value": self.object_type.value,
|
48
|
+
}
|
49
|
+
|
50
|
+
search_dict["sort"] = {
|
51
|
+
"direction": self.sort_direction.value,
|
52
|
+
"timestamp": self.sort_timestamp.value,
|
53
|
+
}
|
54
|
+
|
55
|
+
search_dict["page_size"] = self.page_size
|
56
|
+
|
57
|
+
if self.start_cursor:
|
58
|
+
search_dict["start_cursor"] = self.start_cursor
|
59
|
+
|
60
|
+
return search_dict
|