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,171 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import random
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from collections.abc import Sequence
|
6
|
+
from typing import TYPE_CHECKING, Self
|
7
|
+
|
8
|
+
from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
|
9
|
+
from notionary.user.schemas import PartialUserDto
|
10
|
+
from notionary.user.service import UserService
|
11
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
12
|
+
from notionary.utils.uuid_utils import extract_uuid
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from notionary.user.base import BaseUser
|
16
|
+
|
17
|
+
|
18
|
+
class Entity(LoggingMixin, ABC):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
id: str,
|
22
|
+
created_time: str,
|
23
|
+
created_by: PartialUserDto,
|
24
|
+
last_edited_time: str,
|
25
|
+
last_edited_by: PartialUserDto,
|
26
|
+
in_trash: bool,
|
27
|
+
emoji_icon: str | None = None,
|
28
|
+
external_icon_url: str | None = None,
|
29
|
+
cover_image_url: str | None = None,
|
30
|
+
user_service: UserService | None = None,
|
31
|
+
) -> None:
|
32
|
+
self._id = id
|
33
|
+
self._created_time = created_time
|
34
|
+
self._created_by = created_by
|
35
|
+
self._last_edited_time = last_edited_time
|
36
|
+
self._last_edited_by = last_edited_by
|
37
|
+
self._emoji_icon = emoji_icon
|
38
|
+
self._external_icon_url = external_icon_url
|
39
|
+
self._cover_image_url = cover_image_url
|
40
|
+
self._in_trash = in_trash
|
41
|
+
self._user_service = user_service or UserService()
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
@abstractmethod
|
45
|
+
async def from_id(cls, id: str) -> Self:
|
46
|
+
pass
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
@abstractmethod
|
50
|
+
async def from_title(cls, title: str) -> Self:
|
51
|
+
pass
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
async def from_url(cls, url: str) -> Self:
|
55
|
+
entity_id = extract_uuid(url)
|
56
|
+
if not entity_id:
|
57
|
+
raise ValueError(f"Could not extract entity ID from URL: {url}")
|
58
|
+
return await cls.from_id(entity_id)
|
59
|
+
|
60
|
+
@property
|
61
|
+
@abstractmethod
|
62
|
+
def _entity_metadata_update_client(self) -> EntityMetadataUpdateClient:
|
63
|
+
# functionality for updating properties like title, icon, cover, archive status depends on interface for template like implementation
|
64
|
+
# has to be implementated by inheritants to correctly use the methods below
|
65
|
+
...
|
66
|
+
|
67
|
+
@property
|
68
|
+
def id(self) -> str:
|
69
|
+
return self._id
|
70
|
+
|
71
|
+
@property
|
72
|
+
def created_time(self) -> str:
|
73
|
+
return self._created_time
|
74
|
+
|
75
|
+
@property
|
76
|
+
def last_edited_time(self) -> str:
|
77
|
+
return self._last_edited_time
|
78
|
+
|
79
|
+
@property
|
80
|
+
def in_trash(self) -> bool:
|
81
|
+
return self._in_trash
|
82
|
+
|
83
|
+
@property
|
84
|
+
def emoji_icon(self) -> str | None:
|
85
|
+
return self._emoji_icon
|
86
|
+
|
87
|
+
@property
|
88
|
+
def external_icon_url(self) -> str | None:
|
89
|
+
return self._external_icon_url
|
90
|
+
|
91
|
+
@property
|
92
|
+
def cover_image_url(self) -> str | None:
|
93
|
+
return self._cover_image_url
|
94
|
+
|
95
|
+
@property
|
96
|
+
def created_by(self) -> PartialUserDto:
|
97
|
+
return self._created_by
|
98
|
+
|
99
|
+
@property
|
100
|
+
def last_edited_by(self) -> PartialUserDto:
|
101
|
+
return self._last_edited_by
|
102
|
+
|
103
|
+
async def get_created_by_user(self) -> BaseUser | None:
|
104
|
+
return await self._user_service.get_user_by_id(self._created_by.id)
|
105
|
+
|
106
|
+
async def get_last_edited_by_user(self) -> BaseUser | None:
|
107
|
+
return await self._user_service.get_user_by_id(self._last_edited_by.id)
|
108
|
+
|
109
|
+
async def set_emoji_icon(self, emoji: str) -> None:
|
110
|
+
entity_response = await self._entity_metadata_update_client.patch_emoji_icon(emoji)
|
111
|
+
self._emoji_icon = entity_response.icon.emoji if entity_response.icon else None
|
112
|
+
self._external_icon_url = None
|
113
|
+
|
114
|
+
async def set_external_icon(self, icon_url: str) -> None:
|
115
|
+
entity_response = await self._entity_metadata_update_client.patch_external_icon(icon_url)
|
116
|
+
self._emoji_icon = None
|
117
|
+
self._external_icon_url = (
|
118
|
+
entity_response.icon.external.url if entity_response.icon and entity_response.icon.external else None
|
119
|
+
)
|
120
|
+
|
121
|
+
async def remove_icon(self) -> None:
|
122
|
+
await self._entity_metadata_update_client.remove_icon()
|
123
|
+
self._emoji_icon = None
|
124
|
+
self._external_icon_url = None
|
125
|
+
|
126
|
+
async def set_cover_image_by_url(self, image_url: str) -> None:
|
127
|
+
entity_response = await self._entity_metadata_update_client.patch_external_cover(image_url)
|
128
|
+
self._cover_image_url = (
|
129
|
+
entity_response.cover.external.url if entity_response.cover and entity_response.cover.external else None
|
130
|
+
)
|
131
|
+
|
132
|
+
async def set_random_gradient_cover(self) -> None:
|
133
|
+
random_cover_url = self._get_random_gradient_cover()
|
134
|
+
await self.set_cover_image_by_url(random_cover_url)
|
135
|
+
|
136
|
+
async def remove_cover_image(self) -> None:
|
137
|
+
await self._entity_metadata_update_client.remove_cover()
|
138
|
+
self._cover_image_url = None
|
139
|
+
|
140
|
+
async def move_to_trash(self) -> None:
|
141
|
+
if self._in_trash:
|
142
|
+
self.logger.warning("Entity is already in trash.")
|
143
|
+
return
|
144
|
+
|
145
|
+
entity_response = await self._entity_metadata_update_client.move_to_trash()
|
146
|
+
self._in_trash = entity_response.in_trash
|
147
|
+
|
148
|
+
async def restore_from_trash(self) -> None:
|
149
|
+
if not self._in_trash:
|
150
|
+
self.logger.warning("Entity is not in trash.")
|
151
|
+
return
|
152
|
+
|
153
|
+
entity_response = await self._entity_metadata_update_client.restore_from_trash()
|
154
|
+
self._in_trash = entity_response.in_trash
|
155
|
+
|
156
|
+
def __repr__(self) -> str:
|
157
|
+
attrs = []
|
158
|
+
for key, value in self.__dict__.items():
|
159
|
+
if key.startswith("_") and not key.startswith("__"):
|
160
|
+
attr_name = key[1:]
|
161
|
+
attrs.append(f"{attr_name}={value!r}")
|
162
|
+
|
163
|
+
attrs_str = ", ".join(attrs)
|
164
|
+
return f"{self.__class__.__name__}({attrs_str})"
|
165
|
+
|
166
|
+
def _get_random_gradient_cover(self) -> str:
|
167
|
+
DEFAULT_NOTION_COVERS: Sequence[str] = [
|
168
|
+
f"https://www.notion.so/images/page-cover/gradients_{i}.png" for i in range(1, 10)
|
169
|
+
]
|
170
|
+
|
171
|
+
return random.choice(DEFAULT_NOTION_COVERS)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Literal, Self
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from notionary.shared.models.file import ExternalFile
|
7
|
+
|
8
|
+
|
9
|
+
class CoverType(StrEnum):
|
10
|
+
EXTERNAL = "external"
|
11
|
+
FILE = "file"
|
12
|
+
|
13
|
+
|
14
|
+
class NotionCover(BaseModel):
|
15
|
+
type: Literal[CoverType.EXTERNAL, CoverType.FILE] = CoverType.EXTERNAL
|
16
|
+
external: ExternalFile | None = None
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def from_url(cls, url: str) -> Self:
|
20
|
+
return cls(icon=ExternalFile(url))
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Literal, Self
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class FileType(StrEnum):
|
8
|
+
EXTERNAL = "external"
|
9
|
+
|
10
|
+
|
11
|
+
class ExternalFile(BaseModel):
|
12
|
+
url: str
|
13
|
+
|
14
|
+
|
15
|
+
class ExternalRessource(BaseModel):
|
16
|
+
type: Literal[FileType.EXTERNAL] = FileType.EXTERNAL
|
17
|
+
external: ExternalFile
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
def from_url(cls, url: str) -> Self:
|
21
|
+
return cls(external=ExternalFile(url=url))
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Literal, Self
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from notionary.shared.models.file import ExternalFile
|
7
|
+
|
8
|
+
|
9
|
+
class IconType(StrEnum):
|
10
|
+
EMOJI = "emoji"
|
11
|
+
EXTERNAL = "external"
|
12
|
+
|
13
|
+
|
14
|
+
class EmojiIcon(BaseModel):
|
15
|
+
type: Literal[IconType.EMOJI] = IconType.EMOJI
|
16
|
+
emoji: str
|
17
|
+
|
18
|
+
|
19
|
+
class ExternalIcon(BaseModel):
|
20
|
+
type: Literal[IconType.EXTERNAL] = IconType.EXTERNAL
|
21
|
+
external: ExternalFile
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def from_url(cls, url: str) -> Self:
|
25
|
+
return cls(external=ExternalFile(url=url))
|
26
|
+
|
27
|
+
|
28
|
+
Icon = EmojiIcon | ExternalIcon
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class ParentType(StrEnum):
|
8
|
+
DATABASE_ID = "database_id"
|
9
|
+
DATA_SOURCE_ID = "data_source_id"
|
10
|
+
PAGE_ID = "page_id"
|
11
|
+
BLOCK_ID = "block_id"
|
12
|
+
WORKSPACE = "workspace"
|
13
|
+
|
14
|
+
|
15
|
+
class DataSourceParent(BaseModel):
|
16
|
+
type: Literal[ParentType.DATA_SOURCE_ID]
|
17
|
+
data_source_id: str
|
18
|
+
database_id: str
|
19
|
+
|
20
|
+
|
21
|
+
class PageParent(BaseModel):
|
22
|
+
type: Literal[ParentType.PAGE_ID]
|
23
|
+
page_id: str
|
24
|
+
|
25
|
+
|
26
|
+
class BlockParent(BaseModel):
|
27
|
+
type: Literal[ParentType.BLOCK_ID]
|
28
|
+
block_id: str
|
29
|
+
|
30
|
+
|
31
|
+
class WorkspaceParent(BaseModel):
|
32
|
+
type: Literal[ParentType.WORKSPACE]
|
33
|
+
workspace: bool
|
34
|
+
|
35
|
+
|
36
|
+
class DatabaseParent(BaseModel):
|
37
|
+
type: Literal[ParentType.DATABASE_ID]
|
38
|
+
database_id: str
|
39
|
+
|
40
|
+
|
41
|
+
Parent = DataSourceParent | PageParent | BlockParent | WorkspaceParent | DatabaseParent
|
@@ -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,89 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Self
|
3
|
+
|
4
|
+
from notionary.user.client import UserHttpClient
|
5
|
+
from notionary.user.schemas import UserResponseDto, UserType
|
6
|
+
from notionary.utils.fuzzy import find_best_match
|
7
|
+
|
8
|
+
|
9
|
+
class BaseUser(ABC):
|
10
|
+
def __init__(
|
11
|
+
self,
|
12
|
+
id: str,
|
13
|
+
name: str | None = None,
|
14
|
+
avatar_url: str | None = None,
|
15
|
+
) -> None:
|
16
|
+
self._id = id
|
17
|
+
self._name = name
|
18
|
+
self._avatar_url = avatar_url
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
async def from_id(
|
22
|
+
cls,
|
23
|
+
user_id: str,
|
24
|
+
http_client: UserHttpClient | None = None,
|
25
|
+
) -> Self:
|
26
|
+
client = http_client or UserHttpClient()
|
27
|
+
user_dto = await client.get_user_by_id(user_id)
|
28
|
+
|
29
|
+
expected_type = cls._get_expected_user_type()
|
30
|
+
if user_dto.type != expected_type:
|
31
|
+
raise ValueError(f"User {user_id} is not a '{expected_type.value}', but '{user_dto.type.value}'")
|
32
|
+
|
33
|
+
return cls.from_dto(user_dto)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
async def from_name(
|
37
|
+
cls,
|
38
|
+
name: str,
|
39
|
+
http_client: UserHttpClient | None = None,
|
40
|
+
) -> Self:
|
41
|
+
client = http_client or UserHttpClient()
|
42
|
+
all_users = await cls._get_all_users_of_type(client)
|
43
|
+
|
44
|
+
if not all_users:
|
45
|
+
user_type = cls._get_expected_user_type().value
|
46
|
+
raise ValueError(f"No '{user_type}' users found in the workspace")
|
47
|
+
|
48
|
+
best_match = find_best_match(query=name, items=all_users, text_extractor=cls._get_name_extractor())
|
49
|
+
if not best_match:
|
50
|
+
user_type = cls._get_expected_user_type().value
|
51
|
+
raise ValueError(f"No '{user_type}' user found with name similar to '{name}'")
|
52
|
+
|
53
|
+
return best_match
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
async def _get_all_users_of_type(cls, http_client: UserHttpClient) -> list[Self]:
|
57
|
+
all_workspace_user_dtos = await http_client.get_all_workspace_users()
|
58
|
+
expected_type = cls._get_expected_user_type()
|
59
|
+
filtered_dtos = [dto for dto in all_workspace_user_dtos if dto.type == expected_type]
|
60
|
+
return [cls.from_dto(dto) for dto in filtered_dtos]
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
@abstractmethod
|
64
|
+
def _get_expected_user_type(cls) -> UserType:
|
65
|
+
pass
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
@abstractmethod
|
69
|
+
def from_dto(cls, user_dto: UserResponseDto) -> Self:
|
70
|
+
pass
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def _get_name_extractor(cls):
|
74
|
+
return lambda user: user.name or ""
|
75
|
+
|
76
|
+
@property
|
77
|
+
def id(self) -> str:
|
78
|
+
return self._id
|
79
|
+
|
80
|
+
@property
|
81
|
+
def name(self) -> str | None:
|
82
|
+
return self._name
|
83
|
+
|
84
|
+
@property
|
85
|
+
def avatar_url(self) -> str | None:
|
86
|
+
return self._avatar_url
|
87
|
+
|
88
|
+
def __repr__(self) -> str:
|
89
|
+
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})"
|