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
notionary/comments/client.py
CHANGED
@@ -1,211 +1,76 @@
|
|
1
|
-
from
|
1
|
+
from collections.abc import AsyncGenerator
|
2
2
|
|
3
|
-
from
|
3
|
+
from notionary.blocks.rich_text.models import RichText
|
4
|
+
from notionary.comments.schemas import (
|
5
|
+
CommentCreateRequest,
|
6
|
+
CommentDto,
|
7
|
+
CommentListRequest,
|
8
|
+
CommentListResponse,
|
9
|
+
)
|
10
|
+
from notionary.http.client import NotionHttpClient
|
11
|
+
from notionary.utils.pagination import paginate_notion_api, paginate_notion_api_generator
|
4
12
|
|
5
|
-
from notionary.base_notion_client import BaseNotionClient
|
6
|
-
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
7
|
-
from notionary.comments.models import Comment, CommentListResponse
|
8
13
|
|
14
|
+
class CommentClient(NotionHttpClient):
|
15
|
+
def __init__(self) -> None:
|
16
|
+
super().__init__()
|
9
17
|
|
10
|
-
|
11
|
-
"""
|
12
|
-
Client for Notion comment operations.
|
13
|
-
Uses Pydantic models for typed responses.
|
14
|
-
|
15
|
-
Notes / API constraints:
|
16
|
-
- Listing returns only *unresolved* comments. Resolved comments are not returned.
|
17
|
-
- You can create:
|
18
|
-
1) a top-level comment on a page
|
19
|
-
2) a reply in an existing discussion (requires discussion_id)
|
20
|
-
You cannot start a brand-new inline thread via API.
|
21
|
-
- Read/Insert comment capabilities must be enabled for the integration.
|
22
|
-
"""
|
23
|
-
|
24
|
-
async def retrieve_comment(self, comment_id: str) -> Comment:
|
25
|
-
"""
|
26
|
-
Retrieve a single Comment object by its ID.
|
27
|
-
|
28
|
-
Requires the integration to have "Read comment" capability enabled.
|
29
|
-
Raises 403 (restricted_resource) without it.
|
30
|
-
"""
|
31
|
-
resp = await self.get(f"comments/{comment_id}")
|
32
|
-
if resp is None:
|
33
|
-
raise RuntimeError("Failed to retrieve comment.")
|
34
|
-
return Comment.model_validate(resp)
|
35
|
-
|
36
|
-
async def list_all_comments_for_page(
|
37
|
-
self, *, page_id: str, page_size: int = 100
|
38
|
-
) -> list[Comment]:
|
39
|
-
"""Returns all unresolved comments for a page (handles pagination)."""
|
40
|
-
results: list[Comment] = []
|
41
|
-
cursor: str | None = None
|
42
|
-
while True:
|
43
|
-
page = await self.list_comments(
|
44
|
-
block_id=page_id, start_cursor=cursor, page_size=page_size
|
45
|
-
)
|
46
|
-
results.extend(page.results)
|
47
|
-
if not page.has_more:
|
48
|
-
break
|
49
|
-
cursor = page.next_cursor
|
50
|
-
return results
|
51
|
-
|
52
|
-
async def list_comments(
|
18
|
+
async def iter_comments(
|
53
19
|
self,
|
54
|
-
*,
|
55
20
|
block_id: str,
|
56
|
-
|
57
|
-
page_size:
|
58
|
-
) ->
|
21
|
+
*,
|
22
|
+
page_size: int = 100,
|
23
|
+
) -> AsyncGenerator[CommentDto]:
|
59
24
|
"""
|
60
|
-
|
61
|
-
|
62
|
-
Args:
|
63
|
-
block_id: Page ID or block ID to list comments for.
|
64
|
-
start_cursor: Pagination cursor.
|
65
|
-
page_size: Max items per page (<= 100).
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
CommentListResponse with results, next_cursor, has_more, etc.
|
25
|
+
Iterates through all comments for a block, yielding each comment individually.
|
26
|
+
Uses pagination to handle large result sets efficiently without loading everything into memory.
|
69
27
|
"""
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
params["page_size"] = page_size
|
28
|
+
async for comment in paginate_notion_api_generator(
|
29
|
+
self._list_comments_page, block_id=block_id, page_size=page_size
|
30
|
+
):
|
31
|
+
yield comment
|
75
32
|
|
76
|
-
|
77
|
-
|
78
|
-
raise RuntimeError("Failed to list comments.")
|
79
|
-
return CommentListResponse.model_validate(resp)
|
33
|
+
async def get_all_comments(self, block_id: str, *, page_size: int = 100) -> list[CommentDto]:
|
34
|
+
all_comments = await paginate_notion_api(self._list_comments_page, block_id=block_id, page_size=page_size)
|
80
35
|
|
81
|
-
|
36
|
+
self.logger.debug("Retrieved %d total comments for block %s", len(all_comments), block_id)
|
37
|
+
return all_comments
|
38
|
+
|
39
|
+
async def _list_comments_page(
|
82
40
|
self,
|
83
|
-
*,
|
84
41
|
block_id: str,
|
42
|
+
*,
|
43
|
+
start_cursor: str | None = None,
|
85
44
|
page_size: int = 100,
|
86
|
-
) ->
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
block_id=block_id, start_cursor=cursor, page_size=page_size
|
95
|
-
)
|
96
|
-
for item in page.results:
|
97
|
-
yield item
|
98
|
-
if not page.has_more:
|
99
|
-
break
|
100
|
-
cursor = page.next_cursor
|
45
|
+
) -> CommentListResponse:
|
46
|
+
request = CommentListRequest(
|
47
|
+
block_id=block_id,
|
48
|
+
start_cursor=start_cursor,
|
49
|
+
page_size=page_size,
|
50
|
+
)
|
51
|
+
resp = await self.get("comments", params=request.model_dump())
|
52
|
+
return CommentListResponse.model_validate(resp)
|
101
53
|
|
102
|
-
async def
|
54
|
+
async def create_comment_for_page(
|
103
55
|
self,
|
104
|
-
|
56
|
+
rich_text: list[RichText],
|
105
57
|
page_id: str,
|
106
|
-
|
107
|
-
|
108
|
-
attachments: Optional[list[dict]] = None,
|
109
|
-
) -> Comment:
|
110
|
-
"""
|
111
|
-
Create a top-level comment on a page.
|
58
|
+
) -> CommentDto:
|
59
|
+
request = CommentCreateRequest.for_page(page_id=page_id, rich_text=rich_text)
|
112
60
|
|
113
|
-
|
114
|
-
page_id: Target page ID.
|
115
|
-
text: Plain text content for the comment (rich_text will be constructed).
|
116
|
-
display_name: Optional "Comment Display Name" object to override author label.
|
117
|
-
attachments: Optional list of "Comment Attachment" objects (max 3).
|
118
|
-
|
119
|
-
Returns:
|
120
|
-
The created Comment object.
|
121
|
-
"""
|
122
|
-
body: dict = {
|
123
|
-
"parent": {"page_id": page_id},
|
124
|
-
"rich_text": [{"type": "text", "text": {"content": text}}],
|
125
|
-
}
|
126
|
-
if display_name:
|
127
|
-
body["display_name"] = display_name
|
128
|
-
if attachments:
|
129
|
-
body["attachments"] = attachments
|
61
|
+
body = request.model_dump(exclude_unset=True, exclude_none=True)
|
130
62
|
|
131
63
|
resp = await self.post("comments", data=body)
|
132
|
-
|
133
|
-
raise RuntimeError("Failed to create page comment.")
|
134
|
-
return Comment.model_validate(resp)
|
64
|
+
return CommentDto.model_validate(resp)
|
135
65
|
|
136
|
-
async def
|
66
|
+
async def create_comment_for_discussion(
|
137
67
|
self,
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
rich_text: Optional[list[RichTextObject]] = None,
|
143
|
-
display_name: Optional[dict[str, Any]] = None,
|
144
|
-
attachments: Optional[list[dict[str, Any]]] = None,
|
145
|
-
) -> Comment:
|
146
|
-
"""
|
147
|
-
Create a comment on a page OR reply to an existing discussion.
|
148
|
-
|
149
|
-
Rules:
|
150
|
-
- Exactly one of page_id or discussion_id must be provided.
|
151
|
-
- Provide either rich_text OR content (plain text). If both given, rich_text wins.
|
152
|
-
- Up to 3 attachments allowed by Notion.
|
153
|
-
"""
|
154
|
-
# validate parent
|
155
|
-
if (page_id is None) == (discussion_id is None):
|
156
|
-
raise ValueError("Specify exactly one parent: page_id OR discussion_id")
|
157
|
-
|
158
|
-
# build rich_text if only content is provided
|
159
|
-
rt = rich_text if rich_text else None
|
160
|
-
if rt is None:
|
161
|
-
if not content:
|
162
|
-
raise ValueError("Provide either 'rich_text' or 'content'.")
|
163
|
-
rt = [{"type": "text", "text": {"content": content}}]
|
164
|
-
|
165
|
-
body: dict[str, Any] = {"rich_text": rt}
|
166
|
-
if page_id:
|
167
|
-
body["parent"] = {"page_id": page_id}
|
168
|
-
else:
|
169
|
-
body["discussion_id"] = discussion_id
|
68
|
+
rich_text: list[RichText],
|
69
|
+
discussion_id: str,
|
70
|
+
) -> CommentDto:
|
71
|
+
request = CommentCreateRequest.for_discussion(discussion_id=discussion_id, rich_text=rich_text)
|
170
72
|
|
171
|
-
|
172
|
-
body["display_name"] = display_name
|
173
|
-
if attachments:
|
174
|
-
body["attachments"] = attachments
|
73
|
+
body = request.model_dump(exclude_unset=True, exclude_none=True)
|
175
74
|
|
176
75
|
resp = await self.post("comments", data=body)
|
177
|
-
|
178
|
-
raise RuntimeError("Failed to create comment.")
|
179
|
-
return Comment.model_validate(resp)
|
180
|
-
|
181
|
-
# ---------- Convenience wrappers ----------
|
182
|
-
|
183
|
-
async def create_comment_on_page(
|
184
|
-
self,
|
185
|
-
*,
|
186
|
-
page_id: str,
|
187
|
-
text: str,
|
188
|
-
display_name: Optional[dict] = None,
|
189
|
-
attachments: Optional[list[dict]] = None,
|
190
|
-
) -> Comment:
|
191
|
-
return await self.create_comment(
|
192
|
-
page_id=page_id,
|
193
|
-
content=text,
|
194
|
-
display_name=display_name,
|
195
|
-
attachments=attachments,
|
196
|
-
)
|
197
|
-
|
198
|
-
async def reply_to_discussion(
|
199
|
-
self,
|
200
|
-
*,
|
201
|
-
discussion_id: str,
|
202
|
-
text: str,
|
203
|
-
display_name: Optional[dict] = None,
|
204
|
-
attachments: Optional[list[dict]] = None,
|
205
|
-
) -> Comment:
|
206
|
-
return await self.create_comment(
|
207
|
-
discussion_id=discussion_id,
|
208
|
-
content=text,
|
209
|
-
display_name=display_name,
|
210
|
-
attachments=attachments,
|
211
|
-
)
|
76
|
+
return CommentDto.model_validate(resp)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
4
|
+
from notionary.comments.models import Comment
|
5
|
+
from notionary.comments.schemas import CommentDto
|
6
|
+
from notionary.user.client import UserHttpClient
|
7
|
+
from notionary.user.person import PersonUser
|
8
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
9
|
+
|
10
|
+
|
11
|
+
class CommentFactory(LoggingMixin):
|
12
|
+
UNKNOWN_AUTHOR = "Unknown Author"
|
13
|
+
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
http_client: UserHttpClient | None = None,
|
17
|
+
markdown_converter: RichTextToMarkdownConverter | None = None,
|
18
|
+
) -> None:
|
19
|
+
self.http_client = http_client
|
20
|
+
self.markdown_converter = markdown_converter or RichTextToMarkdownConverter()
|
21
|
+
|
22
|
+
async def create_from_dto(self, dto: CommentDto) -> Comment:
|
23
|
+
author_name, content = await asyncio.gather(self._resolve_user_name(dto), self._resolve_content(dto))
|
24
|
+
|
25
|
+
return Comment(author_name=author_name, content=content)
|
26
|
+
|
27
|
+
async def _resolve_user_name(self, dto: CommentDto) -> str:
|
28
|
+
user_id = dto.created_by.id
|
29
|
+
|
30
|
+
try:
|
31
|
+
person = await PersonUser.from_id(user_id, self.http_client)
|
32
|
+
if person and person.name:
|
33
|
+
return person.name
|
34
|
+
except Exception:
|
35
|
+
self.logger.warning(f"Failed to resolve user name for user_id: {user_id}", exc_info=True)
|
36
|
+
|
37
|
+
return self.UNKNOWN_AUTHOR
|
38
|
+
|
39
|
+
async def _resolve_content(self, dto: CommentDto) -> str:
|
40
|
+
return await self.markdown_converter.to_markdown(dto.rich_text)
|
notionary/comments/models.py
CHANGED
@@ -1,129 +1,7 @@
|
|
1
|
-
|
2
|
-
from __future__ import annotations
|
1
|
+
from dataclasses import dataclass
|
3
2
|
|
4
|
-
from datetime import datetime
|
5
|
-
from typing import Literal, Optional, Union
|
6
3
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class UserRef(BaseModel):
|
13
|
-
"""Minimal Notion user reference."""
|
14
|
-
|
15
|
-
model_config = ConfigDict(extra="ignore")
|
16
|
-
object: Literal["user"] = "user"
|
17
|
-
id: str
|
18
|
-
|
19
|
-
|
20
|
-
class CommentParent(BaseModel):
|
21
|
-
"""
|
22
|
-
Parent of a comment. Can be page_id or block_id.
|
23
|
-
Notion responds with the active one; the other remains None.
|
24
|
-
"""
|
25
|
-
|
26
|
-
model_config = ConfigDict(extra="ignore")
|
27
|
-
type: Literal["page_id", "block_id"]
|
28
|
-
page_id: Optional[str] = None
|
29
|
-
block_id: Optional[str] = None
|
30
|
-
|
31
|
-
|
32
|
-
class FileWithExpiry(BaseModel):
|
33
|
-
"""File object with temporary URL (common Notion pattern)."""
|
34
|
-
|
35
|
-
model_config = ConfigDict(extra="ignore")
|
36
|
-
url: str
|
37
|
-
expiry_time: Optional[datetime] = None
|
38
|
-
|
39
|
-
|
40
|
-
class CommentAttachmentFile(BaseModel):
|
41
|
-
"""Attachment stored by Notion with expiring download URL."""
|
42
|
-
|
43
|
-
model_config = ConfigDict(extra="ignore")
|
44
|
-
type: Literal["file"] = "file"
|
45
|
-
name: Optional[str] = None
|
46
|
-
file: FileWithExpiry
|
47
|
-
|
48
|
-
|
49
|
-
class CommentAttachmentExternal(BaseModel):
|
50
|
-
"""External attachment referenced by URL."""
|
51
|
-
|
52
|
-
model_config = ConfigDict(extra="ignore")
|
53
|
-
type: Literal["external"] = "external"
|
54
|
-
name: Optional[str] = None
|
55
|
-
external: dict # {"url": "..."} – kept generic
|
56
|
-
|
57
|
-
|
58
|
-
CommentAttachment = Union[CommentAttachmentFile, CommentAttachmentExternal]
|
59
|
-
|
60
|
-
|
61
|
-
# ---------------------------
|
62
|
-
# Display name override (optional)
|
63
|
-
# ---------------------------
|
64
|
-
|
65
|
-
|
66
|
-
class CommentDisplayName(BaseModel):
|
67
|
-
"""
|
68
|
-
Optional display name override for comments created by an integration.
|
69
|
-
Example: {"type": "integration", "resolved_name": "int"}.
|
70
|
-
"""
|
71
|
-
|
72
|
-
model_config = ConfigDict(extra="ignore")
|
73
|
-
type: str
|
74
|
-
resolved_name: Optional[str] = None
|
75
|
-
|
76
|
-
|
77
|
-
# ---------------------------
|
78
|
-
# Core Comment object
|
79
|
-
# ---------------------------
|
80
|
-
|
81
|
-
|
82
|
-
class Comment(BaseModel):
|
83
|
-
"""
|
84
|
-
Notion Comment object as returned by:
|
85
|
-
- GET /v1/comments/{comment_id} (retrieve)
|
86
|
-
- GET /v1/comments?block_id=... (list -> in results[])
|
87
|
-
- POST /v1/comments (create)
|
88
|
-
"""
|
89
|
-
|
90
|
-
model_config = ConfigDict(extra="ignore")
|
91
|
-
|
92
|
-
object: Literal["comment"] = "comment"
|
93
|
-
id: str
|
94
|
-
|
95
|
-
parent: CommentParent
|
96
|
-
discussion_id: str
|
97
|
-
|
98
|
-
created_time: datetime
|
99
|
-
last_edited_time: datetime
|
100
|
-
|
101
|
-
created_by: UserRef
|
102
|
-
|
103
|
-
rich_text: list[RichTextObject] = Field(default_factory=list)
|
104
|
-
|
105
|
-
# Optional fields that may appear depending on capabilities/payload
|
106
|
-
display_name: Optional[CommentDisplayName] = None
|
107
|
-
attachments: Optional[list[CommentAttachment]] = None
|
108
|
-
|
109
|
-
|
110
|
-
# ---------------------------
|
111
|
-
# List envelope (for list-comments)
|
112
|
-
# ---------------------------
|
113
|
-
|
114
|
-
|
115
|
-
class CommentListResponse(BaseModel):
|
116
|
-
"""
|
117
|
-
Envelope for GET /v1/comments?block_id=...
|
118
|
-
"""
|
119
|
-
|
120
|
-
model_config = ConfigDict(extra="ignore")
|
121
|
-
|
122
|
-
object: Literal["list"] = "list"
|
123
|
-
results: list[Comment] = Field(default_factory=list)
|
124
|
-
next_cursor: Optional[str] = None
|
125
|
-
has_more: bool = False
|
126
|
-
|
127
|
-
# Notion includes these two fields on the list envelope.
|
128
|
-
type: Optional[Literal["comment"]] = None
|
129
|
-
comment: Optional[dict] = None
|
4
|
+
@dataclass
|
5
|
+
class Comment:
|
6
|
+
author_name: str
|
7
|
+
content: str
|
@@ -0,0 +1,240 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from enum import StrEnum
|
5
|
+
from typing import Literal
|
6
|
+
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
|
9
|
+
from notionary.blocks.rich_text.models import RichText
|
10
|
+
|
11
|
+
# ---------------------------
|
12
|
+
# Comment Parent
|
13
|
+
# ---------------------------
|
14
|
+
|
15
|
+
|
16
|
+
class CommentParentType(StrEnum):
|
17
|
+
PAGE_ID = "page_id"
|
18
|
+
BLOCK_ID = "block_id"
|
19
|
+
|
20
|
+
|
21
|
+
class PageCommentParent(BaseModel):
|
22
|
+
type: Literal[CommentParentType.PAGE_ID] = CommentParentType.PAGE_ID
|
23
|
+
page_id: str
|
24
|
+
|
25
|
+
|
26
|
+
class BlockCommentParent(BaseModel):
|
27
|
+
type: Literal[CommentParentType.BLOCK_ID] = CommentParentType.BLOCK_ID
|
28
|
+
block_id: str
|
29
|
+
|
30
|
+
|
31
|
+
CommentParent = PageCommentParent | BlockCommentParent
|
32
|
+
|
33
|
+
|
34
|
+
# ---------------------------
|
35
|
+
# Comment Attachment (Response/DTO)
|
36
|
+
# ---------------------------
|
37
|
+
|
38
|
+
|
39
|
+
class CommentAttachmentCategory(StrEnum):
|
40
|
+
AUDIO = "audio"
|
41
|
+
IMAGE = "image"
|
42
|
+
PDF = "pdf"
|
43
|
+
PRODUCTIVITY = "productivity"
|
44
|
+
VIDEO = "video"
|
45
|
+
|
46
|
+
|
47
|
+
class FileWithExpiry(BaseModel):
|
48
|
+
url: str
|
49
|
+
expiry_time: datetime
|
50
|
+
|
51
|
+
|
52
|
+
class CommentAttachmentDto(BaseModel):
|
53
|
+
category: CommentAttachmentCategory
|
54
|
+
file: FileWithExpiry
|
55
|
+
|
56
|
+
|
57
|
+
# ---------------------------
|
58
|
+
# Comment Attachment (Request/Input)
|
59
|
+
# ---------------------------
|
60
|
+
|
61
|
+
|
62
|
+
class CommentAttachmentFileUploadType(StrEnum):
|
63
|
+
FILE_UPLOAD = "file_upload"
|
64
|
+
|
65
|
+
|
66
|
+
class CommentAttachmentInput(BaseModel):
|
67
|
+
file_upload_id: str
|
68
|
+
type: Literal[CommentAttachmentFileUploadType.FILE_UPLOAD] = CommentAttachmentFileUploadType.FILE_UPLOAD
|
69
|
+
|
70
|
+
|
71
|
+
# ---------------------------
|
72
|
+
# Comment Display Name
|
73
|
+
# ---------------------------
|
74
|
+
|
75
|
+
|
76
|
+
class CommentDisplayNameType(StrEnum):
|
77
|
+
INTEGRATION = "integration"
|
78
|
+
USER = "user"
|
79
|
+
CUSTOM = "custom"
|
80
|
+
|
81
|
+
|
82
|
+
class CustomDisplayName(BaseModel):
|
83
|
+
name: str
|
84
|
+
|
85
|
+
|
86
|
+
class IntegrationDisplayName(BaseModel):
|
87
|
+
type: Literal[CommentDisplayNameType.INTEGRATION] = CommentDisplayNameType.INTEGRATION
|
88
|
+
|
89
|
+
|
90
|
+
class UserDisplayName(BaseModel):
|
91
|
+
type: Literal[CommentDisplayNameType.USER] = CommentDisplayNameType.USER
|
92
|
+
|
93
|
+
|
94
|
+
class CustomCommentDisplayName(BaseModel):
|
95
|
+
type: Literal[CommentDisplayNameType.CUSTOM] = CommentDisplayNameType.CUSTOM
|
96
|
+
custom: CustomDisplayName
|
97
|
+
|
98
|
+
|
99
|
+
CommentDisplayNameInput = IntegrationDisplayName | UserDisplayName | CustomCommentDisplayName
|
100
|
+
|
101
|
+
|
102
|
+
class CommentDisplayNameDto(BaseModel):
|
103
|
+
type: CommentDisplayNameType
|
104
|
+
resolved_name: str
|
105
|
+
|
106
|
+
|
107
|
+
# ---------------------------
|
108
|
+
# Comment Create Request
|
109
|
+
# ---------------------------
|
110
|
+
|
111
|
+
|
112
|
+
class CommentCreateRequest(BaseModel):
|
113
|
+
rich_text: list[RichText]
|
114
|
+
parent: CommentParent | None = None
|
115
|
+
discussion_id: str | None = None
|
116
|
+
display_name: CommentDisplayNameInput | None = None
|
117
|
+
attachments: list[CommentAttachmentInput] | None = None
|
118
|
+
|
119
|
+
@classmethod
|
120
|
+
def for_page(
|
121
|
+
cls,
|
122
|
+
page_id: str,
|
123
|
+
rich_text: list[RichText],
|
124
|
+
display_name: CommentDisplayNameInput | None = None,
|
125
|
+
attachments: list[CommentAttachmentInput] | None = None,
|
126
|
+
) -> CommentCreateRequest:
|
127
|
+
return cls(
|
128
|
+
rich_text=rich_text,
|
129
|
+
parent=PageCommentParent(page_id=page_id),
|
130
|
+
display_name=display_name,
|
131
|
+
attachments=attachments,
|
132
|
+
)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def for_block(
|
136
|
+
cls,
|
137
|
+
block_id: str,
|
138
|
+
rich_text: list[RichText],
|
139
|
+
display_name: CommentDisplayNameInput | None = None,
|
140
|
+
attachments: list[CommentAttachmentInput] | None = None,
|
141
|
+
) -> CommentCreateRequest:
|
142
|
+
return cls(
|
143
|
+
rich_text=rich_text,
|
144
|
+
parent=BlockCommentParent(block_id=block_id),
|
145
|
+
display_name=display_name,
|
146
|
+
attachments=attachments,
|
147
|
+
)
|
148
|
+
|
149
|
+
@classmethod
|
150
|
+
def for_discussion(
|
151
|
+
cls,
|
152
|
+
discussion_id: str,
|
153
|
+
rich_text: list[RichText],
|
154
|
+
display_name: CommentDisplayNameInput | None = None,
|
155
|
+
attachments: list[CommentAttachmentInput] | None = None,
|
156
|
+
) -> CommentCreateRequest:
|
157
|
+
return cls(
|
158
|
+
rich_text=rich_text,
|
159
|
+
discussion_id=discussion_id,
|
160
|
+
display_name=display_name,
|
161
|
+
attachments=attachments,
|
162
|
+
)
|
163
|
+
|
164
|
+
|
165
|
+
# ---------------------------
|
166
|
+
# Comment List Request
|
167
|
+
# ---------------------------
|
168
|
+
|
169
|
+
|
170
|
+
class CommentListRequest(BaseModel):
|
171
|
+
block_id: str
|
172
|
+
start_cursor: str | None = None
|
173
|
+
page_size: int | None = None
|
174
|
+
|
175
|
+
|
176
|
+
# ---------------------------
|
177
|
+
# User Reference
|
178
|
+
# ---------------------------
|
179
|
+
|
180
|
+
|
181
|
+
class UserRef(BaseModel):
|
182
|
+
object: Literal["user"] = "user"
|
183
|
+
id: str
|
184
|
+
|
185
|
+
|
186
|
+
# ---------------------------
|
187
|
+
# Comment DTO (Response)
|
188
|
+
# ---------------------------
|
189
|
+
|
190
|
+
|
191
|
+
class CommentDto(BaseModel):
|
192
|
+
"""Comment object as returned by the API"""
|
193
|
+
|
194
|
+
object: Literal["comment"] = "comment"
|
195
|
+
id: str
|
196
|
+
|
197
|
+
parent: CommentParent
|
198
|
+
discussion_id: str
|
199
|
+
|
200
|
+
created_time: datetime
|
201
|
+
last_edited_time: datetime
|
202
|
+
|
203
|
+
created_by: UserRef
|
204
|
+
|
205
|
+
rich_text: list[RichText] = Field(default_factory=list)
|
206
|
+
attachments: list[CommentAttachmentDto] = Field(default_factory=list)
|
207
|
+
display_name: CommentDisplayNameDto | None = None
|
208
|
+
|
209
|
+
|
210
|
+
# ---------------------------
|
211
|
+
# List Response
|
212
|
+
# ---------------------------
|
213
|
+
|
214
|
+
|
215
|
+
class CommentListResponse(BaseModel):
|
216
|
+
object: Literal["list"] = "list"
|
217
|
+
results: list[CommentDto] = Field(default_factory=list)
|
218
|
+
next_cursor: str | None = None
|
219
|
+
has_more: bool = False
|
220
|
+
|
221
|
+
|
222
|
+
# ---------------------------
|
223
|
+
# Convenience Builders
|
224
|
+
# ---------------------------
|
225
|
+
|
226
|
+
|
227
|
+
class CommentDisplayNameBuilder:
|
228
|
+
"""Helper class to build display names easily"""
|
229
|
+
|
230
|
+
@staticmethod
|
231
|
+
def integration() -> IntegrationDisplayName:
|
232
|
+
return IntegrationDisplayName()
|
233
|
+
|
234
|
+
@staticmethod
|
235
|
+
def user() -> UserDisplayName:
|
236
|
+
return UserDisplayName()
|
237
|
+
|
238
|
+
@staticmethod
|
239
|
+
def custom(name: str) -> CustomCommentDisplayName:
|
240
|
+
return CustomCommentDisplayName(custom=CustomDisplayName(name=name))
|