notionary 0.3.0__py3-none-any.whl → 0.4.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 +14 -2
- notionary/blocks/enums.py +27 -6
- notionary/blocks/schemas.py +32 -78
- notionary/comments/client.py +6 -9
- notionary/comments/schemas.py +2 -29
- notionary/data_source/http/data_source_instance_client.py +4 -4
- notionary/data_source/properties/schemas.py +128 -107
- notionary/data_source/query/__init__.py +9 -0
- notionary/data_source/query/builder.py +12 -3
- notionary/data_source/query/schema.py +5 -0
- notionary/data_source/schemas.py +2 -2
- notionary/data_source/service.py +43 -132
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +19 -63
- notionary/exceptions/__init__.py +10 -2
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +24 -3
- notionary/exceptions/data_source/builder.py +2 -2
- notionary/exceptions/data_source/properties.py +3 -3
- notionary/exceptions/file_upload.py +67 -0
- notionary/exceptions/properties.py +4 -4
- notionary/exceptions/search.py +4 -4
- notionary/file_upload/__init__.py +4 -0
- notionary/file_upload/client.py +124 -210
- notionary/file_upload/config/__init__.py +17 -0
- notionary/file_upload/config/config.py +32 -0
- notionary/file_upload/config/constants.py +16 -0
- notionary/file_upload/file/reader.py +28 -0
- notionary/file_upload/query/__init__.py +7 -0
- notionary/file_upload/query/builder.py +54 -0
- notionary/file_upload/query/models.py +37 -0
- notionary/file_upload/schemas.py +78 -0
- notionary/file_upload/service.py +152 -289
- notionary/file_upload/validation/factory.py +64 -0
- notionary/file_upload/validation/impl/file_name_length.py +23 -0
- notionary/file_upload/validation/models.py +124 -0
- notionary/file_upload/validation/port.py +7 -0
- notionary/file_upload/validation/service.py +17 -0
- notionary/file_upload/validation/validators/__init__.py +11 -0
- notionary/file_upload/validation/validators/file_exists.py +15 -0
- notionary/file_upload/validation/validators/file_extension.py +122 -0
- notionary/file_upload/validation/validators/file_name_length.py +21 -0
- notionary/file_upload/validation/validators/upload_limit.py +31 -0
- notionary/http/client.py +7 -23
- notionary/page/content/factory.py +2 -0
- notionary/page/content/parser/factory.py +8 -5
- notionary/page/content/parser/parsers/audio.py +8 -33
- notionary/page/content/parser/parsers/embed.py +0 -2
- notionary/page/content/parser/parsers/file.py +8 -35
- notionary/page/content/parser/parsers/file_like_block.py +89 -0
- notionary/page/content/parser/parsers/image.py +8 -35
- notionary/page/content/parser/parsers/pdf.py +8 -35
- notionary/page/content/parser/parsers/video.py +8 -35
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +2 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +12 -8
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +2 -0
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +2 -0
- notionary/page/content/renderer/renderers/audio.py +9 -21
- notionary/page/content/renderer/renderers/file.py +9 -21
- notionary/page/content/renderer/renderers/file_like_block.py +43 -0
- notionary/page/content/renderer/renderers/image.py +9 -21
- notionary/page/content/renderer/renderers/pdf.py +9 -21
- notionary/page/content/renderer/renderers/video.py +9 -21
- notionary/page/content/syntax/__init__.py +2 -1
- notionary/page/content/syntax/registry.py +38 -60
- notionary/page/properties/client.py +3 -3
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +15 -4
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +18 -79
- notionary/shared/entity/dto_parsers.py +1 -36
- notionary/shared/entity/entity_metadata_update_client.py +18 -4
- notionary/shared/entity/schemas.py +6 -6
- notionary/shared/entity/service.py +121 -40
- notionary/shared/models/file.py +34 -6
- notionary/shared/models/icon.py +5 -12
- notionary/user/bot.py +12 -12
- notionary/utils/decorators.py +8 -8
- notionary/utils/pagination.py +36 -32
- notionary/workspace/__init__.py +2 -2
- notionary/workspace/client.py +2 -0
- notionary/workspace/query/__init__.py +3 -2
- notionary/workspace/query/builder.py +25 -1
- notionary/workspace/query/models.py +9 -1
- notionary/workspace/query/service.py +15 -11
- notionary/workspace/service.py +46 -36
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/METADATA +9 -5
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/RECORD +92 -71
- notionary/file_upload/models.py +0 -69
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
- {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
notionary/page/service.py
CHANGED
|
@@ -11,62 +11,34 @@ from notionary.page.content.service import PageContentService
|
|
|
11
11
|
from notionary.page.page_http_client import NotionPageHttpClient
|
|
12
12
|
from notionary.page.page_metadata_update_client import PageMetadataUpdateClient
|
|
13
13
|
from notionary.page.properties.factory import PagePropertyHandlerFactory
|
|
14
|
-
from notionary.page.properties.
|
|
14
|
+
from notionary.page.properties.schemas import PageTitleProperty
|
|
15
15
|
from notionary.page.properties.service import PagePropertyHandler
|
|
16
16
|
from notionary.page.schemas import NotionPageDto
|
|
17
|
-
from notionary.shared.entity.dto_parsers import (
|
|
18
|
-
extract_cover_image_url_from_dto,
|
|
19
|
-
extract_emoji_icon_from_dto,
|
|
20
|
-
extract_external_icon_url_from_dto,
|
|
21
|
-
)
|
|
22
17
|
from notionary.shared.entity.service import Entity
|
|
23
|
-
from notionary.user.schemas import PartialUserDto
|
|
24
18
|
from notionary.workspace.query.service import WorkspaceQueryService
|
|
25
19
|
|
|
26
20
|
|
|
27
21
|
class NotionPage(Entity):
|
|
28
22
|
def __init__(
|
|
29
23
|
self,
|
|
30
|
-
|
|
24
|
+
dto: NotionPageDto,
|
|
31
25
|
title: str,
|
|
32
|
-
created_time: str,
|
|
33
|
-
created_by: PartialUserDto,
|
|
34
|
-
last_edited_time: str,
|
|
35
|
-
last_edited_by: PartialUserDto,
|
|
36
|
-
url: str,
|
|
37
|
-
archived: bool,
|
|
38
|
-
in_trash: bool,
|
|
39
26
|
page_property_handler: PagePropertyHandler,
|
|
40
27
|
block_client: NotionBlockHttpClient,
|
|
41
28
|
comment_service: CommentService,
|
|
42
29
|
page_content_service: PageContentService,
|
|
43
30
|
metadata_update_client: PageMetadataUpdateClient,
|
|
44
|
-
public_url: str | None = None,
|
|
45
|
-
emoji_icon: str | None = None,
|
|
46
|
-
external_icon_url: str | None = None,
|
|
47
|
-
cover_image_url: str | None = None,
|
|
48
31
|
) -> None:
|
|
49
|
-
super().__init__(
|
|
50
|
-
|
|
51
|
-
created_time=created_time,
|
|
52
|
-
created_by=created_by,
|
|
53
|
-
last_edited_time=last_edited_time,
|
|
54
|
-
last_edited_by=last_edited_by,
|
|
55
|
-
in_trash=in_trash,
|
|
56
|
-
emoji_icon=emoji_icon,
|
|
57
|
-
external_icon_url=external_icon_url,
|
|
58
|
-
cover_image_url=cover_image_url,
|
|
59
|
-
)
|
|
32
|
+
super().__init__(dto=dto)
|
|
33
|
+
|
|
60
34
|
self._title = title
|
|
61
|
-
self._archived = archived
|
|
62
|
-
self._url = url
|
|
63
|
-
self._public_url = public_url
|
|
35
|
+
self._archived = dto.archived
|
|
64
36
|
|
|
65
37
|
self._block_client = block_client
|
|
66
38
|
self._comment_service = comment_service
|
|
67
39
|
self._page_content_service = page_content_service
|
|
68
|
-
self.properties = page_property_handler
|
|
69
40
|
self._metadata_update_client = metadata_update_client
|
|
41
|
+
self.properties = page_property_handler
|
|
70
42
|
|
|
71
43
|
@classmethod
|
|
72
44
|
async def from_id(
|
|
@@ -75,8 +47,8 @@ class NotionPage(Entity):
|
|
|
75
47
|
page_property_handler_factory: PagePropertyHandlerFactory | None = None,
|
|
76
48
|
) -> Self:
|
|
77
49
|
factory = page_property_handler_factory or PagePropertyHandlerFactory()
|
|
78
|
-
|
|
79
|
-
return await cls._create_from_dto(
|
|
50
|
+
dto = await cls._fetch_page_dto(page_id)
|
|
51
|
+
return await cls._create_from_dto(dto, factory)
|
|
80
52
|
|
|
81
53
|
@classmethod
|
|
82
54
|
async def from_title(
|
|
@@ -95,76 +67,43 @@ class NotionPage(Entity):
|
|
|
95
67
|
@classmethod
|
|
96
68
|
async def _create_from_dto(
|
|
97
69
|
cls,
|
|
98
|
-
|
|
70
|
+
dto: NotionPageDto,
|
|
99
71
|
page_property_handler_factory: PagePropertyHandlerFactory,
|
|
100
72
|
) -> Self:
|
|
101
|
-
title_task = cls._extract_title_from_dto(
|
|
102
|
-
page_property_handler = page_property_handler_factory.create_from_page_response(
|
|
73
|
+
title_task = cls._extract_title_from_dto(dto)
|
|
74
|
+
page_property_handler = page_property_handler_factory.create_from_page_response(dto)
|
|
103
75
|
|
|
104
76
|
title = await title_task
|
|
105
77
|
|
|
106
78
|
return cls._create_with_dependencies(
|
|
107
|
-
|
|
79
|
+
dto=dto,
|
|
108
80
|
title=title,
|
|
109
|
-
created_time=response.created_time,
|
|
110
|
-
created_by=response.created_by,
|
|
111
|
-
last_edited_time=response.last_edited_time,
|
|
112
|
-
last_edited_by=response.last_edited_by,
|
|
113
|
-
archived=response.archived,
|
|
114
|
-
in_trash=response.in_trash,
|
|
115
|
-
url=response.url,
|
|
116
81
|
page_property_handler=page_property_handler,
|
|
117
|
-
public_url=response.public_url,
|
|
118
|
-
emoji_icon=extract_emoji_icon_from_dto(response),
|
|
119
|
-
external_icon_url=extract_external_icon_url_from_dto(response),
|
|
120
|
-
cover_image_url=extract_cover_image_url_from_dto(response),
|
|
121
82
|
)
|
|
122
83
|
|
|
123
84
|
@classmethod
|
|
124
85
|
def _create_with_dependencies(
|
|
125
86
|
cls,
|
|
126
|
-
|
|
87
|
+
dto: NotionPageDto,
|
|
127
88
|
title: str,
|
|
128
|
-
created_time: str,
|
|
129
|
-
created_by: PartialUserDto,
|
|
130
|
-
last_edited_time: str,
|
|
131
|
-
last_edited_by: PartialUserDto,
|
|
132
|
-
url: str,
|
|
133
|
-
archived: bool,
|
|
134
|
-
in_trash: bool,
|
|
135
89
|
page_property_handler: PagePropertyHandler,
|
|
136
|
-
public_url: str | None = None,
|
|
137
|
-
emoji_icon: str | None = None,
|
|
138
|
-
external_icon_url: str | None = None,
|
|
139
|
-
cover_image_url: str | None = None,
|
|
140
90
|
) -> Self:
|
|
141
91
|
block_client = NotionBlockHttpClient()
|
|
142
92
|
comment_service = CommentService()
|
|
143
93
|
|
|
144
94
|
page_content_service_factory = PageContentServiceFactory()
|
|
145
|
-
page_content_service = page_content_service_factory.create(page_id=id, block_client=block_client)
|
|
95
|
+
page_content_service = page_content_service_factory.create(page_id=dto.id, block_client=block_client)
|
|
146
96
|
|
|
147
|
-
metadata_update_client = PageMetadataUpdateClient(page_id=id)
|
|
97
|
+
metadata_update_client = PageMetadataUpdateClient(page_id=dto.id)
|
|
148
98
|
|
|
149
99
|
return cls(
|
|
150
|
-
|
|
100
|
+
dto=dto,
|
|
151
101
|
title=title,
|
|
152
|
-
created_time=created_time,
|
|
153
|
-
created_by=created_by,
|
|
154
|
-
last_edited_time=last_edited_time,
|
|
155
|
-
last_edited_by=last_edited_by,
|
|
156
|
-
url=url,
|
|
157
|
-
archived=archived,
|
|
158
|
-
in_trash=in_trash,
|
|
159
102
|
page_property_handler=page_property_handler,
|
|
160
103
|
block_client=block_client,
|
|
161
104
|
comment_service=comment_service,
|
|
162
105
|
page_content_service=page_content_service,
|
|
163
106
|
metadata_update_client=metadata_update_client,
|
|
164
|
-
public_url=public_url,
|
|
165
|
-
emoji_icon=emoji_icon,
|
|
166
|
-
external_icon_url=external_icon_url,
|
|
167
|
-
cover_image_url=cover_image_url,
|
|
168
107
|
)
|
|
169
108
|
|
|
170
109
|
@staticmethod
|
|
@@ -185,8 +124,8 @@ class NotionPage(Entity):
|
|
|
185
124
|
return self._title
|
|
186
125
|
|
|
187
126
|
@property
|
|
188
|
-
def
|
|
189
|
-
return self.
|
|
127
|
+
def archived(self) -> bool:
|
|
128
|
+
return self._archived
|
|
190
129
|
|
|
191
130
|
@property
|
|
192
131
|
def markdown_builder() -> MarkdownBuilder:
|
|
@@ -1,40 +1,5 @@
|
|
|
1
|
-
from typing import cast
|
|
2
|
-
|
|
3
1
|
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
4
|
-
from notionary.shared.entity.schemas import Describable,
|
|
5
|
-
from notionary.shared.models.cover import CoverType
|
|
6
|
-
from notionary.shared.models.icon import IconType
|
|
7
|
-
from notionary.shared.models.parent import DatabaseParent, DataSourceParent, ParentType
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def extract_emoji_icon_from_dto(entity_dto: EntityResponseDto) -> str | None:
|
|
11
|
-
if not entity_dto.icon or entity_dto.icon.type != IconType.EMOJI:
|
|
12
|
-
return None
|
|
13
|
-
return entity_dto.icon.emoji
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def extract_external_icon_url_from_dto(entity_dto: EntityResponseDto) -> str | None:
|
|
17
|
-
if not entity_dto.icon or entity_dto.icon.type != IconType.EXTERNAL:
|
|
18
|
-
return None
|
|
19
|
-
return entity_dto.icon.external.url if entity_dto.icon.external else None
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def extract_cover_image_url_from_dto(entity_dto: EntityResponseDto) -> str | None:
|
|
23
|
-
if not entity_dto.cover or entity_dto.cover.type != CoverType.EXTERNAL:
|
|
24
|
-
return None
|
|
25
|
-
return entity_dto.cover.external.url if entity_dto.cover.external else None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def extract_database_id(entity_dto: EntityResponseDto) -> str | None:
|
|
29
|
-
if entity_dto.parent.type == ParentType.DATA_SOURCE_ID:
|
|
30
|
-
data_source_parent = cast(DataSourceParent, entity_dto.parent)
|
|
31
|
-
return data_source_parent.database_id if data_source_parent else None
|
|
32
|
-
|
|
33
|
-
if entity_dto.parent.type == ParentType.DATABASE_ID:
|
|
34
|
-
database_parent = cast(DatabaseParent, entity_dto.parent)
|
|
35
|
-
return database_parent.database_id if database_parent else None
|
|
36
|
-
|
|
37
|
-
return None
|
|
2
|
+
from notionary.shared.entity.schemas import Describable, Titled
|
|
38
3
|
|
|
39
4
|
|
|
40
5
|
async def extract_title(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
3
|
from notionary.shared.entity.schemas import EntityResponseDto, NotionEntityUpdateDto
|
|
4
|
-
from notionary.shared.models.
|
|
5
|
-
from notionary.shared.models.icon import EmojiIcon,
|
|
4
|
+
from notionary.shared.models.file import ExternalFile, FileUploadFile
|
|
5
|
+
from notionary.shared.models.icon import EmojiIcon, Icon
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class EntityMetadataUpdateClient(ABC):
|
|
@@ -15,7 +15,14 @@ class EntityMetadataUpdateClient(ABC):
|
|
|
15
15
|
return await self.patch_metadata(update_dto)
|
|
16
16
|
|
|
17
17
|
async def patch_external_icon(self, icon_url: str) -> EntityResponseDto:
|
|
18
|
-
icon =
|
|
18
|
+
icon = ExternalFile.from_url(icon_url)
|
|
19
|
+
return await self._patch_icon(icon)
|
|
20
|
+
|
|
21
|
+
async def patch_icon_from_file_upload(self, file_upload_id: str) -> EntityResponseDto:
|
|
22
|
+
icon = FileUploadFile.from_id(id=file_upload_id)
|
|
23
|
+
return await self._patch_icon(icon)
|
|
24
|
+
|
|
25
|
+
async def _patch_icon(self, icon: Icon) -> EntityResponseDto:
|
|
19
26
|
update_dto = NotionEntityUpdateDto(icon=icon)
|
|
20
27
|
return await self.patch_metadata(update_dto)
|
|
21
28
|
|
|
@@ -24,7 +31,14 @@ class EntityMetadataUpdateClient(ABC):
|
|
|
24
31
|
return await self.patch_metadata(update_dto)
|
|
25
32
|
|
|
26
33
|
async def patch_external_cover(self, cover_url: str) -> EntityResponseDto:
|
|
27
|
-
cover =
|
|
34
|
+
cover = ExternalFile.from_url(cover_url)
|
|
35
|
+
return await self._patch_cover(cover)
|
|
36
|
+
|
|
37
|
+
async def patch_cover_from_file_upload(self, file_upload_id: str) -> EntityResponseDto:
|
|
38
|
+
cover = FileUploadFile.from_id(id=file_upload_id)
|
|
39
|
+
return await self._patch_cover(cover)
|
|
40
|
+
|
|
41
|
+
async def _patch_cover(self, cover: Icon) -> EntityResponseDto:
|
|
28
42
|
update_dto = NotionEntityUpdateDto(cover=cover)
|
|
29
43
|
return await self.patch_metadata(update_dto)
|
|
30
44
|
|
|
@@ -4,26 +4,26 @@ from typing import Protocol
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
6
6
|
from notionary.blocks.rich_text.models import RichText
|
|
7
|
-
from notionary.shared.models.
|
|
7
|
+
from notionary.shared.models.file import File
|
|
8
8
|
from notionary.shared.models.icon import Icon
|
|
9
9
|
from notionary.shared.models.parent import Parent
|
|
10
10
|
from notionary.user.schemas import PartialUserDto
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class _EntityType(StrEnum):
|
|
14
14
|
PAGE = "page"
|
|
15
15
|
DATA_SOURCE = "data_source"
|
|
16
16
|
DATABASE = "database"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class EntityResponseDto(BaseModel):
|
|
20
|
-
object:
|
|
20
|
+
object: _EntityType
|
|
21
21
|
id: str
|
|
22
22
|
created_time: str
|
|
23
23
|
created_by: PartialUserDto
|
|
24
24
|
last_edited_time: str
|
|
25
25
|
last_edited_by: PartialUserDto
|
|
26
|
-
cover:
|
|
26
|
+
cover: File | None = None
|
|
27
27
|
icon: Icon | None = None
|
|
28
28
|
parent: Parent
|
|
29
29
|
in_trash: bool
|
|
@@ -32,8 +32,8 @@ class EntityResponseDto(BaseModel):
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class NotionEntityUpdateDto(BaseModel):
|
|
35
|
-
icon:
|
|
36
|
-
cover:
|
|
35
|
+
icon: File | None = None
|
|
36
|
+
cover: File | None = None
|
|
37
37
|
in_trash: bool | None = None
|
|
38
38
|
|
|
39
39
|
|
|
@@ -1,44 +1,82 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import random
|
|
4
2
|
from abc import ABC, abstractmethod
|
|
5
3
|
from collections.abc import Sequence
|
|
6
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Self, cast
|
|
7
6
|
|
|
7
|
+
from notionary.file_upload.service import NotionFileUpload
|
|
8
8
|
from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
|
|
9
|
-
from notionary.
|
|
9
|
+
from notionary.shared.entity.schemas import EntityResponseDto
|
|
10
|
+
from notionary.shared.models.file import ExternalFile, FileType, NotionHostedFile
|
|
11
|
+
from notionary.shared.models.icon import EmojiIcon, IconType
|
|
12
|
+
from notionary.shared.models.parent import ParentType
|
|
13
|
+
from notionary.user.base import BaseUser
|
|
10
14
|
from notionary.user.service import UserService
|
|
11
15
|
from notionary.utils.mixins.logging import LoggingMixin
|
|
12
16
|
from notionary.utils.uuid_utils import extract_uuid
|
|
13
17
|
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from notionary.user.base import BaseUser
|
|
16
|
-
|
|
17
18
|
|
|
18
19
|
class Entity(LoggingMixin, ABC):
|
|
19
20
|
def __init__(
|
|
20
21
|
self,
|
|
21
|
-
|
|
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,
|
|
22
|
+
dto: EntityResponseDto,
|
|
30
23
|
user_service: UserService | None = None,
|
|
24
|
+
file_upload_service: NotionFileUpload | None = None,
|
|
31
25
|
) -> 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.
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
self.
|
|
26
|
+
self._id = dto.id
|
|
27
|
+
self._created_time = dto.created_time
|
|
28
|
+
self._created_by = dto.created_by
|
|
29
|
+
self._last_edited_time = dto.last_edited_time
|
|
30
|
+
self._last_edited_by = dto.last_edited_by
|
|
31
|
+
self._in_trash = dto.in_trash
|
|
32
|
+
self._parent = dto.parent
|
|
33
|
+
self._url = dto.url
|
|
34
|
+
self._public_url = dto.public_url
|
|
35
|
+
|
|
36
|
+
self._emoji_icon = self._extract_emoji_icon(dto)
|
|
37
|
+
self._external_icon_url = self._extract_external_icon_url(dto)
|
|
38
|
+
self._cover_image_url = self._extract_cover_image_url(dto)
|
|
39
|
+
|
|
41
40
|
self._user_service = user_service or UserService()
|
|
41
|
+
self._file_upload_service = file_upload_service or NotionFileUpload()
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _extract_emoji_icon(dto: EntityResponseDto) -> str | None:
|
|
45
|
+
if dto.icon is None:
|
|
46
|
+
return None
|
|
47
|
+
if dto.icon.type is not IconType.EMOJI:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
emoji_icon = cast(EmojiIcon, dto.icon)
|
|
51
|
+
return emoji_icon.emoji
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _extract_external_icon_url(dto: EntityResponseDto) -> str | None:
|
|
55
|
+
if dto.icon is None:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
if dto.icon.type == IconType.EXTERNAL:
|
|
59
|
+
external_icon = cast(ExternalFile, dto.icon)
|
|
60
|
+
return external_icon.external.url
|
|
61
|
+
elif dto.icon.type == IconType.FILE:
|
|
62
|
+
notion_file_icon = cast(NotionHostedFile, dto.icon)
|
|
63
|
+
return notion_file_icon.file.url
|
|
64
|
+
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _extract_cover_image_url(dto: EntityResponseDto) -> str | None:
|
|
69
|
+
if dto.cover is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
if dto.cover.type == FileType.EXTERNAL:
|
|
73
|
+
external_cover = cast(ExternalFile, dto.cover)
|
|
74
|
+
return external_cover.external.url
|
|
75
|
+
elif dto.cover.type == FileType.FILE:
|
|
76
|
+
notion_file_cover = cast(NotionHostedFile, dto.cover)
|
|
77
|
+
return notion_file_cover.file.url
|
|
78
|
+
|
|
79
|
+
return None
|
|
42
80
|
|
|
43
81
|
@classmethod
|
|
44
82
|
@abstractmethod
|
|
@@ -59,10 +97,7 @@ class Entity(LoggingMixin, ABC):
|
|
|
59
97
|
|
|
60
98
|
@property
|
|
61
99
|
@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
|
-
...
|
|
100
|
+
def _entity_metadata_update_client(self) -> EntityMetadataUpdateClient: ...
|
|
66
101
|
|
|
67
102
|
@property
|
|
68
103
|
def id(self) -> str:
|
|
@@ -93,12 +128,35 @@ class Entity(LoggingMixin, ABC):
|
|
|
93
128
|
return self._cover_image_url
|
|
94
129
|
|
|
95
130
|
@property
|
|
96
|
-
def
|
|
97
|
-
return self.
|
|
131
|
+
def url(self) -> str:
|
|
132
|
+
return self._url
|
|
98
133
|
|
|
99
134
|
@property
|
|
100
|
-
def
|
|
101
|
-
return self.
|
|
135
|
+
def public_url(self) -> str | None:
|
|
136
|
+
return self._public_url
|
|
137
|
+
|
|
138
|
+
def get_parent_database_id_if_present(self) -> str | None:
|
|
139
|
+
if self._parent.type == ParentType.DATABASE_ID:
|
|
140
|
+
return self._parent.database_id
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def get_parent_data_source_id_if_present(self) -> str | None:
|
|
144
|
+
if self._parent.type == ParentType.DATA_SOURCE_ID:
|
|
145
|
+
return self._parent.data_source_id
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
def get_parent_page_id_if_present(self) -> str | None:
|
|
149
|
+
if self._parent.type == ParentType.PAGE_ID:
|
|
150
|
+
return self._parent.page_id
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def get_parent_block_id_if_present(self) -> str | None:
|
|
154
|
+
if self._parent.type == ParentType.BLOCK_ID:
|
|
155
|
+
return self._parent.block_id
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
def is_workspace_parent(self) -> bool:
|
|
159
|
+
return self._parent.type == ParentType.WORKSPACE
|
|
102
160
|
|
|
103
161
|
async def get_created_by_user(self) -> BaseUser | None:
|
|
104
162
|
return await self._user_service.get_user_by_id(self._created_by.id)
|
|
@@ -108,15 +166,26 @@ class Entity(LoggingMixin, ABC):
|
|
|
108
166
|
|
|
109
167
|
async def set_emoji_icon(self, emoji: str) -> None:
|
|
110
168
|
entity_response = await self._entity_metadata_update_client.patch_emoji_icon(emoji)
|
|
111
|
-
self._emoji_icon =
|
|
169
|
+
self._emoji_icon = self._extract_emoji_icon(entity_response)
|
|
112
170
|
self._external_icon_url = None
|
|
113
171
|
|
|
114
172
|
async def set_external_icon(self, icon_url: str) -> None:
|
|
115
173
|
entity_response = await self._entity_metadata_update_client.patch_external_icon(icon_url)
|
|
116
174
|
self._emoji_icon = None
|
|
117
|
-
self._external_icon_url = (
|
|
118
|
-
|
|
119
|
-
|
|
175
|
+
self._external_icon_url = self._extract_external_icon_url(entity_response)
|
|
176
|
+
|
|
177
|
+
async def set_icon_from_file(self, file_path: Path, filename: str | None = None) -> None:
|
|
178
|
+
upload_response = await self._file_upload_service.upload_file(file_path, filename)
|
|
179
|
+
await self._set_icon_from_file_upload(upload_response.id)
|
|
180
|
+
|
|
181
|
+
async def set_icon_from_bytes(self, file_content: bytes, filename: str, content_type: str | None = None) -> None:
|
|
182
|
+
upload_response = await self._file_upload_service.upload_from_bytes(file_content, filename, content_type)
|
|
183
|
+
await self._set_icon_from_file_upload(upload_response.id)
|
|
184
|
+
|
|
185
|
+
async def _set_icon_from_file_upload(self, file_upload_id: str) -> None:
|
|
186
|
+
entity_response = await self._entity_metadata_update_client.patch_icon_from_file_upload(file_upload_id)
|
|
187
|
+
self._emoji_icon = None
|
|
188
|
+
self._external_icon_url = self._extract_external_icon_url(entity_response)
|
|
120
189
|
|
|
121
190
|
async def remove_icon(self) -> None:
|
|
122
191
|
await self._entity_metadata_update_client.remove_icon()
|
|
@@ -125,9 +194,21 @@ class Entity(LoggingMixin, ABC):
|
|
|
125
194
|
|
|
126
195
|
async def set_cover_image_by_url(self, image_url: str) -> None:
|
|
127
196
|
entity_response = await self._entity_metadata_update_client.patch_external_cover(image_url)
|
|
128
|
-
self._cover_image_url = (
|
|
129
|
-
|
|
130
|
-
|
|
197
|
+
self._cover_image_url = self._extract_cover_image_url(entity_response)
|
|
198
|
+
|
|
199
|
+
async def set_cover_image_from_file(self, file_path: Path, filename: str | None = None) -> None:
|
|
200
|
+
upload_response = await self._file_upload_service.upload_file(file_path, filename)
|
|
201
|
+
await self._set_cover_image_from_file_upload(upload_response.id)
|
|
202
|
+
|
|
203
|
+
async def set_cover_image_from_bytes(
|
|
204
|
+
self, file_content: bytes, filename: str, content_type: str | None = None
|
|
205
|
+
) -> None:
|
|
206
|
+
upload_response = await self._file_upload_service.upload_from_bytes(file_content, filename, content_type)
|
|
207
|
+
await self._set_cover_image_from_file_upload(upload_response.id)
|
|
208
|
+
|
|
209
|
+
async def _set_cover_image_from_file_upload(self, file_upload_id: str) -> None:
|
|
210
|
+
entity_response = await self._entity_metadata_update_client.patch_cover_from_file_upload(file_upload_id)
|
|
211
|
+
self._cover_image_url = self._extract_cover_image_url(entity_response)
|
|
131
212
|
|
|
132
213
|
async def set_random_gradient_cover(self) -> None:
|
|
133
214
|
random_cover_url = self._get_random_gradient_cover()
|
notionary/shared/models/file.py
CHANGED
|
@@ -1,21 +1,49 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
-
from typing import Literal, Self
|
|
2
|
+
from typing import Annotated, Literal, Self
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class FileType(StrEnum):
|
|
8
8
|
EXTERNAL = "external"
|
|
9
|
+
FILE = "file"
|
|
10
|
+
FILE_UPLOAD = "file_upload"
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
class
|
|
13
|
+
class ExternalFileData(BaseModel):
|
|
12
14
|
url: str
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class
|
|
17
|
+
class ExternalFile(BaseModel):
|
|
16
18
|
type: Literal[FileType.EXTERNAL] = FileType.EXTERNAL
|
|
17
|
-
external:
|
|
19
|
+
external: ExternalFileData
|
|
18
20
|
|
|
19
21
|
@classmethod
|
|
20
22
|
def from_url(cls, url: str) -> Self:
|
|
21
|
-
return cls(external=
|
|
23
|
+
return cls(external=ExternalFileData(url=url))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotionHostedFileData(BaseModel):
|
|
27
|
+
url: str
|
|
28
|
+
expiry_time: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NotionHostedFile(BaseModel):
|
|
32
|
+
type: Literal[FileType.FILE] = FileType.FILE
|
|
33
|
+
file: NotionHostedFileData
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FileUploadedFileData(BaseModel):
|
|
37
|
+
id: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FileUploadFile(BaseModel):
|
|
41
|
+
type: Literal[FileType.FILE_UPLOAD] = FileType.FILE_UPLOAD
|
|
42
|
+
file_upload: FileUploadedFileData
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_id(cls, id: str) -> Self:
|
|
46
|
+
return cls(file_upload=FileUploadedFileData(id=id))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
type File = Annotated[ExternalFile | NotionHostedFile | FileUploadFile, Field(discriminator="type")]
|
notionary/shared/models/icon.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
-
from typing import Literal
|
|
2
|
+
from typing import Literal
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
6
|
-
from notionary.shared.models.file import
|
|
6
|
+
from notionary.shared.models.file import File
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class IconType(StrEnum):
|
|
10
10
|
EMOJI = "emoji"
|
|
11
11
|
EXTERNAL = "external"
|
|
12
|
+
FILE = "file"
|
|
13
|
+
FILE_UPLOAD = "file_upload"
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class EmojiIcon(BaseModel):
|
|
@@ -16,13 +18,4 @@ class EmojiIcon(BaseModel):
|
|
|
16
18
|
emoji: str
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
|
|
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
|
|
21
|
+
type Icon = EmojiIcon | File
|
notionary/user/bot.py
CHANGED
|
@@ -20,6 +20,18 @@ class BotUser(BaseUser):
|
|
|
20
20
|
self._workspace_file_upload_limit_in_bytes = workspace_file_upload_limit_in_bytes
|
|
21
21
|
self._owner_type = owner_type
|
|
22
22
|
|
|
23
|
+
@property
|
|
24
|
+
def workspace_name(self) -> str | None:
|
|
25
|
+
return self._workspace_name
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def workspace_file_upload_limit_in_bytes(self) -> int:
|
|
29
|
+
return self._workspace_file_upload_limit_in_bytes
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def owner_type(self) -> WorkspaceOwnerType | None:
|
|
33
|
+
return self._owner_type
|
|
34
|
+
|
|
23
35
|
@classmethod
|
|
24
36
|
def _get_expected_user_type(cls) -> UserType:
|
|
25
37
|
return UserType.BOT
|
|
@@ -54,17 +66,5 @@ class BotUser(BaseUser):
|
|
|
54
66
|
owner_type=owner_type,
|
|
55
67
|
)
|
|
56
68
|
|
|
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
69
|
def __repr__(self) -> str:
|
|
70
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/utils/decorators.py
CHANGED
|
@@ -9,10 +9,10 @@ P = ParamSpec("P")
|
|
|
9
9
|
R = TypeVar("R")
|
|
10
10
|
T = TypeVar("T")
|
|
11
11
|
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
12
|
+
type _SyncFunc = Callable[P, R]
|
|
13
|
+
type _AsyncFunc = Callable[P, Coroutine[Any, Any, R]]
|
|
14
|
+
type _SyncDecorator = Callable[[_SyncFunc], _SyncFunc]
|
|
15
|
+
type _AsyncDecorator = Callable[[_AsyncFunc], _AsyncFunc]
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def singleton(cls):
|
|
@@ -26,8 +26,8 @@ def singleton(cls):
|
|
|
26
26
|
return wrapper
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def time_execution_sync(additional_text: str = "", min_duration_to_log: float = 0.25) ->
|
|
30
|
-
def decorator(func:
|
|
29
|
+
def time_execution_sync(additional_text: str = "", min_duration_to_log: float = 0.25) -> _SyncDecorator:
|
|
30
|
+
def decorator(func: _SyncFunc) -> _SyncFunc:
|
|
31
31
|
@functools.wraps(func)
|
|
32
32
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
33
33
|
start_time = time.perf_counter()
|
|
@@ -49,8 +49,8 @@ def time_execution_sync(additional_text: str = "", min_duration_to_log: float =
|
|
|
49
49
|
def time_execution_async(
|
|
50
50
|
additional_text: str = "",
|
|
51
51
|
min_duration_to_log: float = 0.25,
|
|
52
|
-
) ->
|
|
53
|
-
def decorator(func:
|
|
52
|
+
) -> _AsyncDecorator:
|
|
53
|
+
def decorator(func: _AsyncFunc) -> _AsyncFunc:
|
|
54
54
|
@functools.wraps(func)
|
|
55
55
|
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
56
56
|
start_time = time.perf_counter()
|