notionary 0.2.27__py3-none-any.whl → 0.3.0__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/__init__.py +4 -4
- notionary/blocks/client.py +90 -216
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +280 -0
- notionary/blocks/rich_text/models.py +178 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +13 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -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 +144 -0
- notionary/blocks/rich_text/rich_text_patterns.py +42 -0
- notionary/blocks/schemas.py +778 -0
- notionary/comments/__init__.py +1 -22
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +38 -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 +104 -0
- notionary/data_source/properties/schemas.py +402 -0
- notionary/data_source/query/builder.py +448 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +302 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +377 -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 +168 -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 +57 -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 +204 -0
- notionary/http/models.py +50 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +73 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +226 -0
- notionary/page/content/markdown/nodes/__init__.py +52 -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 +41 -0
- notionary/page/content/markdown/nodes/callout.py +34 -0
- notionary/page/content/markdown/nodes/code.py +28 -0
- notionary/page/content/markdown/nodes/columns.py +69 -0
- notionary/page/content/markdown/nodes/container.py +64 -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 +36 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +38 -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 +27 -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 +38 -0
- notionary/page/content/markdown/nodes/toggle.py +27 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +126 -0
- notionary/page/content/parser/factory.py +210 -0
- notionary/page/content/parser/parsers/__init__.py +58 -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 +85 -0
- notionary/page/content/parser/parsers/callout.py +100 -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 +76 -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 +115 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +89 -0
- notionary/page/content/parser/parsers/paragraph.py +37 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +125 -0
- notionary/page/content/parser/parsers/space.py +41 -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 +96 -0
- notionary/page/content/parser/parsers/toggle.py +70 -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 +95 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +114 -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 +11 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +130 -0
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +84 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +73 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +78 -0
- notionary/page/content/renderer/context.py +51 -0
- notionary/page/content/renderer/factory.py +231 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -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 +55 -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 +50 -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 +53 -0
- notionary/page/content/renderer/renderers/column_list.py +44 -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 +95 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +42 -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 +52 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +68 -0
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +66 -0
- notionary/page/content/syntax/registry.py +393 -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 +308 -0
- notionary/page/properties/service.py +261 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +225 -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/shared/typings.py +3 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +138 -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/date.py +51 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +68 -0
- notionary/utils/mixins/logging.py +58 -0
- notionary/utils/pagination.py +100 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +4 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +61 -0
- notionary/workspace/query/service.py +100 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- notionary-0.3.0.dist-info/METADATA +201 -0
- notionary-0.3.0.dist-info/RECORD +209 -0
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- 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/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/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/logging_mixin.py +0 -59
- 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/METADATA +0 -270
- notionary-0.2.27.dist-info/RECORD +0 -202
- /notionary/{database → user}/factory.py +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PropertyType(StrEnum):
|
|
5
|
+
TITLE = "title"
|
|
6
|
+
RICH_TEXT = "rich_text"
|
|
7
|
+
SELECT = "select"
|
|
8
|
+
MULTI_SELECT = "multi_select"
|
|
9
|
+
STATUS = "status"
|
|
10
|
+
NUMBER = "number"
|
|
11
|
+
DATE = "date"
|
|
12
|
+
CHECKBOX = "checkbox"
|
|
13
|
+
URL = "url"
|
|
14
|
+
EMAIL = "email"
|
|
15
|
+
PHONE_NUMBER = "phone_number"
|
|
16
|
+
PEOPLE = "people"
|
|
17
|
+
CREATED_BY = "created_by"
|
|
18
|
+
LAST_EDITED_BY = "last_edited_by"
|
|
19
|
+
CREATED_TIME = "created_time"
|
|
20
|
+
LAST_EDITED_TIME = "last_edited_time"
|
|
21
|
+
LAST_VISITED_TIME = "last_visited_time"
|
|
22
|
+
FORMULA = "formula"
|
|
23
|
+
ROLLUP = "rollup"
|
|
24
|
+
FILES = "files"
|
|
25
|
+
RELATION = "relation"
|
|
26
|
+
BUTTON = "button"
|
|
27
|
+
LOCATION = "location"
|
|
28
|
+
PLACE = "place"
|
|
29
|
+
VERIFICATION = "verification"
|
|
30
|
+
UNIQUE_ID = "unique_id"
|
notionary/user/__init__.py
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
3
|
-
from .notion_user import NotionUser
|
|
4
|
-
from .notion_user_manager import NotionUserManager
|
|
1
|
+
from .bot import BotUser
|
|
2
|
+
from .person import PersonUser
|
|
5
3
|
|
|
6
4
|
__all__ = [
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"NotionUserClient",
|
|
10
|
-
"NotionBotUser",
|
|
5
|
+
"BotUser",
|
|
6
|
+
"PersonUser",
|
|
11
7
|
]
|
notionary/user/base.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Self
|
|
5
|
+
|
|
6
|
+
from notionary.exceptions.search import NoUsersInWorkspace, UserNotFound
|
|
7
|
+
from notionary.user.client import UserHttpClient
|
|
8
|
+
from notionary.user.schemas import UserResponseDto, UserType
|
|
9
|
+
from notionary.utils.fuzzy import find_all_matches
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from notionary.user.bot import BotUser
|
|
13
|
+
from notionary.user.person import PersonUser
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseUser:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
id: str,
|
|
20
|
+
name: str | None = None,
|
|
21
|
+
avatar_url: str | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self._id = id
|
|
24
|
+
self._name = name
|
|
25
|
+
self._avatar_url = avatar_url
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
async def from_id_auto(
|
|
29
|
+
cls,
|
|
30
|
+
user_id: str,
|
|
31
|
+
http_client: UserHttpClient | None = None,
|
|
32
|
+
) -> BotUser | PersonUser:
|
|
33
|
+
from notionary.user.bot import BotUser
|
|
34
|
+
from notionary.user.person import PersonUser
|
|
35
|
+
|
|
36
|
+
client = http_client or UserHttpClient()
|
|
37
|
+
user_dto = await client.get_user_by_id(user_id)
|
|
38
|
+
|
|
39
|
+
if user_dto.type == UserType.BOT:
|
|
40
|
+
return BotUser.from_dto(user_dto)
|
|
41
|
+
elif user_dto.type == UserType.PERSON:
|
|
42
|
+
return PersonUser.from_dto(user_dto)
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(f"Unknown user type: {user_dto.type}")
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
async def from_id(
|
|
48
|
+
cls,
|
|
49
|
+
user_id: str,
|
|
50
|
+
http_client: UserHttpClient | None = None,
|
|
51
|
+
) -> Self:
|
|
52
|
+
client = http_client or UserHttpClient()
|
|
53
|
+
user_dto = await client.get_user_by_id(user_id)
|
|
54
|
+
|
|
55
|
+
expected_type = cls._get_expected_user_type()
|
|
56
|
+
if user_dto.type != expected_type:
|
|
57
|
+
raise ValueError(f"User {user_id} is not a '{expected_type.value}', but '{user_dto.type.value}'")
|
|
58
|
+
|
|
59
|
+
return cls.from_dto(user_dto)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
async def from_name(
|
|
63
|
+
cls,
|
|
64
|
+
name: str,
|
|
65
|
+
http_client: UserHttpClient | None = None,
|
|
66
|
+
) -> Self:
|
|
67
|
+
client = http_client or UserHttpClient()
|
|
68
|
+
all_users = await cls._get_all_users_of_type(client)
|
|
69
|
+
|
|
70
|
+
user_type = cls._get_expected_user_type().value
|
|
71
|
+
|
|
72
|
+
if not all_users:
|
|
73
|
+
raise NoUsersInWorkspace(user_type)
|
|
74
|
+
|
|
75
|
+
exact_match = cls._find_exact_match(all_users, name)
|
|
76
|
+
if exact_match:
|
|
77
|
+
return exact_match
|
|
78
|
+
|
|
79
|
+
suggestions = cls._get_fuzzy_suggestions(all_users, name)
|
|
80
|
+
raise UserNotFound(user_type, name, suggestions)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def _find_exact_match(cls, users: list[Self], query: str) -> Self | None:
|
|
84
|
+
query_lower = query.lower()
|
|
85
|
+
for user in users:
|
|
86
|
+
if user.name and user.name.lower() == query_lower:
|
|
87
|
+
return user
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def _get_fuzzy_suggestions(cls, users: list[Self], query: str) -> list[str]:
|
|
92
|
+
sorted_by_similarity = find_all_matches(
|
|
93
|
+
query=query,
|
|
94
|
+
items=users,
|
|
95
|
+
text_extractor=cls._get_name_extractor(),
|
|
96
|
+
min_similarity=0.6,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if sorted_by_similarity:
|
|
100
|
+
return [user.name for user in sorted_by_similarity[:5] if user.name]
|
|
101
|
+
|
|
102
|
+
return [user.name for user in users[:5] if user.name]
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
async def _get_all_users_of_type(cls, http_client: UserHttpClient) -> list[Self]:
|
|
106
|
+
all_workspace_user_dtos = await http_client.get_all_workspace_users()
|
|
107
|
+
expected_type = cls._get_expected_user_type()
|
|
108
|
+
filtered_dtos = [dto for dto in all_workspace_user_dtos if dto.type == expected_type]
|
|
109
|
+
return [cls.from_dto(dto) for dto in filtered_dtos]
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def _get_expected_user_type(cls) -> UserType:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def from_dto(cls, user_dto: UserResponseDto) -> Self:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def _get_name_extractor(cls):
|
|
123
|
+
return lambda user: user.name or ""
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def id(self) -> str:
|
|
127
|
+
return self._id
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def name(self) -> str | None:
|
|
131
|
+
return self._name
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def avatar_url(self) -> str | None:
|
|
135
|
+
return self._avatar_url
|
|
136
|
+
|
|
137
|
+
def __repr__(self) -> str:
|
|
138
|
+
return f"{self.__class__.__name__}(id={self._id!r}, name={self._name!r})"
|
notionary/user/bot.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Self, cast
|
|
2
|
+
|
|
3
|
+
from notionary.user.base import BaseUser
|
|
4
|
+
from notionary.user.client import UserHttpClient
|
|
5
|
+
from notionary.user.schemas import BotUserDto, BotUserResponseDto, UserResponseDto, UserType, WorkspaceOwnerType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BotUser(BaseUser):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
id: str,
|
|
12
|
+
workspace_file_upload_limit_in_bytes: int,
|
|
13
|
+
owner_type: WorkspaceOwnerType | None,
|
|
14
|
+
name: str | None = None,
|
|
15
|
+
avatar_url: str | None = None,
|
|
16
|
+
workspace_name: str | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(id=id, name=name, avatar_url=avatar_url)
|
|
19
|
+
self._workspace_name = workspace_name
|
|
20
|
+
self._workspace_file_upload_limit_in_bytes = workspace_file_upload_limit_in_bytes
|
|
21
|
+
self._owner_type = owner_type
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def _get_expected_user_type(cls) -> UserType:
|
|
25
|
+
return UserType.BOT
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
async def from_current_integration(
|
|
29
|
+
cls,
|
|
30
|
+
http_client: UserHttpClient | None = None,
|
|
31
|
+
) -> Self:
|
|
32
|
+
client = http_client or UserHttpClient()
|
|
33
|
+
user_dto = await client.get_current_integration_bot()
|
|
34
|
+
return cls.from_dto(user_dto)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_dto(cls, user_dto: UserResponseDto) -> Self:
|
|
38
|
+
bot_dto = cast(BotUserResponseDto, user_dto)
|
|
39
|
+
bot_data: BotUserDto = bot_dto.bot
|
|
40
|
+
|
|
41
|
+
owner_type = bot_data.owner.type if bot_data and bot_data.owner else None
|
|
42
|
+
workspace_name = bot_data.workspace_name if bot_data else None
|
|
43
|
+
|
|
44
|
+
limit = 0
|
|
45
|
+
if bot_data and bot_data.workspace_limits:
|
|
46
|
+
limit = bot_data.workspace_limits.max_file_upload_size_in_bytes
|
|
47
|
+
|
|
48
|
+
return cls(
|
|
49
|
+
id=bot_dto.id,
|
|
50
|
+
name=bot_dto.name,
|
|
51
|
+
avatar_url=bot_dto.avatar_url,
|
|
52
|
+
workspace_name=workspace_name,
|
|
53
|
+
workspace_file_upload_limit_in_bytes=limit,
|
|
54
|
+
owner_type=owner_type,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def workspace_name(self) -> str | None:
|
|
59
|
+
return self._workspace_name
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def workspace_file_upload_limit_in_bytes(self) -> int:
|
|
63
|
+
return self._workspace_file_upload_limit_in_bytes
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def owner_type(self) -> WorkspaceOwnerType | None:
|
|
67
|
+
return self._owner_type
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return f"BotUser(id={self._id!r}, name={self._name!r}, avatar_url={self._avatar_url!r}, workspace_name={self._workspace_name!r}, workspace_file_upload_limit_in_bytes={self._workspace_file_upload_limit_in_bytes!r}, owner_type={self._owner_type!r})"
|
notionary/user/client.py
CHANGED
|
@@ -1,128 +1,39 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pydantic import TypeAdapter
|
|
2
2
|
|
|
3
|
-
from notionary.
|
|
4
|
-
from notionary.user.
|
|
5
|
-
|
|
6
|
-
NotionUserResponse,
|
|
3
|
+
from notionary.http.client import NotionHttpClient
|
|
4
|
+
from notionary.user.schemas import (
|
|
5
|
+
BotUserResponseDto,
|
|
7
6
|
NotionUsersListResponse,
|
|
7
|
+
UserResponseDto,
|
|
8
8
|
)
|
|
9
|
+
from notionary.utils.pagination import paginate_notion_api
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
Client for Notion user-specific operations.
|
|
14
|
-
Inherits base HTTP functionality from BaseNotionClient.
|
|
15
|
-
|
|
16
|
-
Note: The Notion API only supports individual user queries and bot user info.
|
|
17
|
-
List users endpoint is available but only returns workspace members (no guests).
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
async def get_user(self, user_id: str) -> Optional[NotionUserResponse]:
|
|
21
|
-
"""
|
|
22
|
-
Retrieve a user by their ID.
|
|
23
|
-
"""
|
|
12
|
+
class UserHttpClient(NotionHttpClient):
|
|
13
|
+
async def get_user_by_id(self, user_id: str) -> UserResponseDto:
|
|
24
14
|
response = await self.get(f"users/{user_id}")
|
|
25
|
-
if response is None:
|
|
26
|
-
self.logger.error("Failed to fetch user %s - API returned None", user_id)
|
|
27
|
-
return None
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
except Exception as e:
|
|
32
|
-
self.logger.error("Failed to validate user response for %s: %s", user_id, e)
|
|
33
|
-
return None
|
|
34
|
-
|
|
35
|
-
async def get_bot_user(self) -> Optional[NotionBotUserResponse]:
|
|
36
|
-
"""
|
|
37
|
-
Retrieve your token's bot user information.
|
|
38
|
-
"""
|
|
39
|
-
response = await self.get("users/me")
|
|
40
|
-
if response is None:
|
|
41
|
-
self.logger.error("Failed to fetch bot user - API returned None")
|
|
42
|
-
return None
|
|
16
|
+
adapter = TypeAdapter(UserResponseDto)
|
|
17
|
+
return adapter.validate_python(response)
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
except Exception as e:
|
|
47
|
-
self.logger.error("Failed to validate bot user response: %s", e)
|
|
48
|
-
return None
|
|
19
|
+
async def get_all_workspace_users(self) -> list[UserResponseDto]:
|
|
20
|
+
all_entities = await paginate_notion_api(self._get_workspace_entities, page_size=100)
|
|
49
21
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
) -> Optional[NotionUsersListResponse]:
|
|
53
|
-
"""
|
|
54
|
-
List all users in the workspace (paginated).
|
|
22
|
+
self.logger.info("Fetched %d total workspace users", len(all_entities))
|
|
23
|
+
return all_entities
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
25
|
+
async def _get_workspace_entities(
|
|
26
|
+
self, page_size: int = 100, start_cursor: str | None = None
|
|
27
|
+
) -> NotionUsersListResponse | None:
|
|
28
|
+
params = {"page_size": min(page_size, 100)}
|
|
59
29
|
if start_cursor:
|
|
60
30
|
params["start_cursor"] = start_cursor
|
|
61
31
|
|
|
62
32
|
response = await self.get("users", params=params)
|
|
63
|
-
if response is None:
|
|
64
|
-
self.logger.error("Failed to fetch users list - API returned None")
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
return NotionUsersListResponse.model_validate(response)
|
|
69
|
-
except Exception as e:
|
|
70
|
-
self.logger.error("Failed to validate users list response: %s", e)
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
async def get_all_users(self) -> List[NotionUserResponse]:
|
|
74
|
-
"""
|
|
75
|
-
Get all users in the workspace by handling pagination automatically.
|
|
76
|
-
"""
|
|
77
|
-
all_users = []
|
|
78
|
-
start_cursor = None
|
|
79
|
-
|
|
80
|
-
while True:
|
|
81
|
-
try:
|
|
82
|
-
response = await self.list_users(
|
|
83
|
-
page_size=100, start_cursor=start_cursor
|
|
84
|
-
)
|
|
85
33
|
|
|
86
|
-
|
|
87
|
-
break
|
|
34
|
+
return NotionUsersListResponse.model_validate(response)
|
|
88
35
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Check if there are more pages
|
|
92
|
-
if not response.has_more or not response.next_cursor:
|
|
93
|
-
break
|
|
94
|
-
|
|
95
|
-
start_cursor = response.next_cursor
|
|
96
|
-
|
|
97
|
-
except Exception as e:
|
|
98
|
-
self.logger.error("Error fetching all users: %s", str(e))
|
|
99
|
-
break
|
|
100
|
-
|
|
101
|
-
self.logger.info("Retrieved %d total users from workspace", len(all_users))
|
|
102
|
-
return all_users
|
|
103
|
-
|
|
104
|
-
async def get_workspace_name(self) -> Optional[str]:
|
|
105
|
-
"""
|
|
106
|
-
Get the workspace name from the bot user.
|
|
107
|
-
"""
|
|
108
|
-
try:
|
|
109
|
-
bot_user = await self.get_bot_user()
|
|
110
|
-
if bot_user and bot_user.bot and bot_user.bot.workspace_name:
|
|
111
|
-
return bot_user.bot.workspace_name
|
|
112
|
-
return None
|
|
113
|
-
except Exception as e:
|
|
114
|
-
self.logger.error("Error fetching workspace name: %s", str(e))
|
|
115
|
-
return None
|
|
36
|
+
async def get_current_integration_bot(self) -> BotUserResponseDto:
|
|
37
|
+
response = await self.get("users/me")
|
|
116
38
|
|
|
117
|
-
|
|
118
|
-
"""
|
|
119
|
-
Get workspace limits from the bot user.
|
|
120
|
-
"""
|
|
121
|
-
try:
|
|
122
|
-
bot_user = await self.get_bot_user()
|
|
123
|
-
if bot_user and bot_user.bot and bot_user.bot.workspace_limits:
|
|
124
|
-
return bot_user.bot.workspace_limits.model_dump()
|
|
125
|
-
return None
|
|
126
|
-
except Exception as e:
|
|
127
|
-
self.logger.error("Error fetching workspace limits: %s", str(e))
|
|
128
|
-
return None
|
|
39
|
+
return BotUserResponseDto.model_validate(response)
|
notionary/user/person.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Self, cast
|
|
2
|
+
|
|
3
|
+
from notionary.user.base import BaseUser
|
|
4
|
+
from notionary.user.schemas import PersonUserResponseDto, UserResponseDto, UserType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PersonUser(BaseUser):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
id: str,
|
|
11
|
+
name: str,
|
|
12
|
+
avatar_url: str,
|
|
13
|
+
email: str,
|
|
14
|
+
) -> None:
|
|
15
|
+
super().__init__(id=id, name=name, avatar_url=avatar_url)
|
|
16
|
+
self._email = email
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _get_expected_user_type(cls) -> UserType:
|
|
20
|
+
return UserType.PERSON
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_dto(cls, user_dto: UserResponseDto) -> Self:
|
|
24
|
+
person_dto = cast(PersonUserResponseDto, user_dto)
|
|
25
|
+
return cls(
|
|
26
|
+
id=person_dto.id,
|
|
27
|
+
name=person_dto.name or "",
|
|
28
|
+
avatar_url=person_dto.avatar_url,
|
|
29
|
+
email=person_dto.person.email or "",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return self._name or ""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def email(self) -> str:
|
|
38
|
+
return self._email
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f"PersonUser(id={self._id!r}, name={self._name!r}, email={self._email!r})"
|
|
@@ -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)
|
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)
|