notionary 0.3.1__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 +6 -1
- notionary/blocks/enums.py +0 -6
- notionary/blocks/schemas.py +32 -78
- notionary/comments/schemas.py +2 -29
- notionary/data_source/properties/schemas.py +128 -107
- notionary/data_source/schemas.py +2 -2
- notionary/data_source/service.py +32 -23
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +3 -5
- notionary/exceptions/__init__.py +6 -2
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +3 -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 +6 -22
- 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/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 +1 -1
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +1 -1
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +1 -1
- 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 +53 -30
- 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/workspace/__init__.py +2 -2
- notionary/workspace/query/__init__.py +2 -1
- notionary/workspace/query/service.py +3 -17
- notionary/workspace/service.py +45 -45
- {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/METADATA +1 -1
- {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/RECORD +77 -58
- notionary/file_upload/models.py +0 -69
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, Literal, TypeVar
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
5
|
|
|
6
6
|
from notionary.blocks.rich_text.models import RichText
|
|
7
|
+
from notionary.shared.models.file import File
|
|
7
8
|
from notionary.shared.properties.type import PropertyType
|
|
8
9
|
from notionary.shared.typings import JsonDict
|
|
9
10
|
from notionary.user.schemas import PersonUserResponseDto, UserResponseDto
|
|
@@ -38,38 +39,6 @@ class DateValue(BaseModel):
|
|
|
38
39
|
time_zone: str | None = None
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
# ============================================================================
|
|
42
|
-
# File Models
|
|
43
|
-
# ============================================================================
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class FileType(StrEnum):
|
|
47
|
-
EXTERNAL = "external"
|
|
48
|
-
FILE = "file"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class ExternalFile(BaseModel):
|
|
52
|
-
"""External file hosted outside of Notion."""
|
|
53
|
-
|
|
54
|
-
url: str
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class NotionFile(BaseModel):
|
|
58
|
-
"""File uploaded to Notion with expiration."""
|
|
59
|
-
|
|
60
|
-
url: str
|
|
61
|
-
expiry_time: str
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class FileObject(BaseModel):
|
|
65
|
-
"""File object can be external or uploaded to Notion."""
|
|
66
|
-
|
|
67
|
-
name: str
|
|
68
|
-
type: FileType
|
|
69
|
-
external: ExternalFile | None = None
|
|
70
|
-
file: NotionFile | None = None
|
|
71
|
-
|
|
72
|
-
|
|
73
42
|
# ============================================================================
|
|
74
43
|
# Formula Models
|
|
75
44
|
# ============================================================================
|
|
@@ -142,25 +111,9 @@ class VerificationValue(BaseModel):
|
|
|
142
111
|
# ============================================================================
|
|
143
112
|
|
|
144
113
|
|
|
145
|
-
class
|
|
146
|
-
type: Literal[PropertyType.
|
|
147
|
-
|
|
148
|
-
options: list[StatusOption] = Field(default_factory=list)
|
|
149
|
-
|
|
150
|
-
@property
|
|
151
|
-
def option_names(self) -> list[str]:
|
|
152
|
-
return [option.name for option in self.options]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class PageRelationProperty(PageProperty):
|
|
156
|
-
type: Literal[PropertyType.RELATION] = PropertyType.RELATION
|
|
157
|
-
relation: list[RelationItem] = Field(default_factory=list)
|
|
158
|
-
has_more: bool = False
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class PageURLProperty(PageProperty):
|
|
162
|
-
type: Literal[PropertyType.URL] = PropertyType.URL
|
|
163
|
-
url: str | None = None
|
|
114
|
+
class PageTitleProperty(PageProperty):
|
|
115
|
+
type: Literal[PropertyType.TITLE] = PropertyType.TITLE
|
|
116
|
+
title: list[RichText] = Field(default_factory=list)
|
|
164
117
|
|
|
165
118
|
|
|
166
119
|
class PageRichTextProperty(PageProperty):
|
|
@@ -168,6 +121,16 @@ class PageRichTextProperty(PageProperty):
|
|
|
168
121
|
rich_text: list[RichText] = Field(default_factory=list)
|
|
169
122
|
|
|
170
123
|
|
|
124
|
+
class PageSelectProperty(PageProperty):
|
|
125
|
+
type: Literal[PropertyType.SELECT] = PropertyType.SELECT
|
|
126
|
+
select: SelectOption | None = None
|
|
127
|
+
options: list[SelectOption] = Field(default_factory=list)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def option_names(self) -> list[str]:
|
|
131
|
+
return [option.name for option in self.options]
|
|
132
|
+
|
|
133
|
+
|
|
171
134
|
class PageMultiSelectProperty(PageProperty):
|
|
172
135
|
type: Literal[PropertyType.MULTI_SELECT] = PropertyType.MULTI_SELECT
|
|
173
136
|
multi_select: list[SelectOption] = Field(default_factory=list)
|
|
@@ -178,19 +141,19 @@ class PageMultiSelectProperty(PageProperty):
|
|
|
178
141
|
return [option.name for option in self.options]
|
|
179
142
|
|
|
180
143
|
|
|
181
|
-
class
|
|
182
|
-
type: Literal[PropertyType.
|
|
183
|
-
|
|
184
|
-
options: list[
|
|
144
|
+
class PageStatusProperty(PageProperty):
|
|
145
|
+
type: Literal[PropertyType.STATUS] = PropertyType.STATUS
|
|
146
|
+
status: StatusOption | None = None
|
|
147
|
+
options: list[StatusOption] = Field(default_factory=list)
|
|
185
148
|
|
|
186
149
|
@property
|
|
187
150
|
def option_names(self) -> list[str]:
|
|
188
151
|
return [option.name for option in self.options]
|
|
189
152
|
|
|
190
153
|
|
|
191
|
-
class
|
|
192
|
-
type: Literal[PropertyType.
|
|
193
|
-
|
|
154
|
+
class PageNumberProperty(PageProperty):
|
|
155
|
+
type: Literal[PropertyType.NUMBER] = PropertyType.NUMBER
|
|
156
|
+
number: float | None = None
|
|
194
157
|
|
|
195
158
|
|
|
196
159
|
class PageDateProperty(PageProperty):
|
|
@@ -198,21 +161,16 @@ class PageDateProperty(PageProperty):
|
|
|
198
161
|
date: DateValue | None = None
|
|
199
162
|
|
|
200
163
|
|
|
201
|
-
class PageTitleProperty(PageProperty):
|
|
202
|
-
type: Literal[PropertyType.TITLE] = PropertyType.TITLE
|
|
203
|
-
title: list[RichText] = Field(default_factory=list)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class PageNumberProperty(PageProperty):
|
|
207
|
-
type: Literal[PropertyType.NUMBER] = PropertyType.NUMBER
|
|
208
|
-
number: float | None = None
|
|
209
|
-
|
|
210
|
-
|
|
211
164
|
class PageCheckboxProperty(PageProperty):
|
|
212
165
|
type: Literal[PropertyType.CHECKBOX] = PropertyType.CHECKBOX
|
|
213
166
|
checkbox: bool = False
|
|
214
167
|
|
|
215
168
|
|
|
169
|
+
class PageURLProperty(PageProperty):
|
|
170
|
+
type: Literal[PropertyType.URL] = PropertyType.URL
|
|
171
|
+
url: str | None = None
|
|
172
|
+
|
|
173
|
+
|
|
216
174
|
class PageEmailProperty(PageProperty):
|
|
217
175
|
type: Literal[PropertyType.EMAIL] = PropertyType.EMAIL
|
|
218
176
|
email: str | None = None
|
|
@@ -223,29 +181,34 @@ class PagePhoneNumberProperty(PageProperty):
|
|
|
223
181
|
phone_number: str | None = None
|
|
224
182
|
|
|
225
183
|
|
|
226
|
-
class
|
|
227
|
-
type: Literal[PropertyType.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
class PageLastEditedTimeProperty(PageProperty):
|
|
232
|
-
type: Literal[PropertyType.LAST_EDITED_TIME] = PropertyType.LAST_EDITED_TIME
|
|
233
|
-
last_edited_time: str # ISO 8601 datetime - read-only
|
|
184
|
+
class PagePeopleProperty(PageProperty):
|
|
185
|
+
type: Literal[PropertyType.PEOPLE] = PropertyType.PEOPLE
|
|
186
|
+
people: list[PersonUserResponseDto] = Field(default_factory=list)
|
|
234
187
|
|
|
235
188
|
|
|
236
189
|
class PageCreatedByProperty(PageProperty):
|
|
237
190
|
type: Literal[PropertyType.CREATED_BY] = PropertyType.CREATED_BY
|
|
238
|
-
created_by: UserResponseDto
|
|
191
|
+
created_by: UserResponseDto
|
|
239
192
|
|
|
240
193
|
|
|
241
194
|
class PageLastEditedByProperty(PageProperty):
|
|
242
195
|
type: Literal[PropertyType.LAST_EDITED_BY] = PropertyType.LAST_EDITED_BY
|
|
243
|
-
last_edited_by: UserResponseDto
|
|
196
|
+
last_edited_by: UserResponseDto
|
|
244
197
|
|
|
245
198
|
|
|
246
|
-
class
|
|
247
|
-
type: Literal[PropertyType.
|
|
248
|
-
|
|
199
|
+
class PageCreatedTimeProperty(PageProperty):
|
|
200
|
+
type: Literal[PropertyType.CREATED_TIME] = PropertyType.CREATED_TIME
|
|
201
|
+
created_time: str
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PageLastEditedTimeProperty(PageProperty):
|
|
205
|
+
type: Literal[PropertyType.LAST_EDITED_TIME] = PropertyType.LAST_EDITED_TIME
|
|
206
|
+
last_edited_time: str
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class PageLastVisitedTimeProperty(PageProperty):
|
|
210
|
+
type: Literal[PropertyType.LAST_VISITED_TIME] = PropertyType.LAST_VISITED_TIME
|
|
211
|
+
last_visited_time: str | None = None
|
|
249
212
|
|
|
250
213
|
|
|
251
214
|
class PageFormulaProperty(PageProperty):
|
|
@@ -258,14 +221,15 @@ class PageRollupProperty(PageProperty):
|
|
|
258
221
|
rollup: RollupValue
|
|
259
222
|
|
|
260
223
|
|
|
261
|
-
class
|
|
262
|
-
type: Literal[PropertyType.
|
|
263
|
-
|
|
224
|
+
class PageFilesProperty(PageProperty):
|
|
225
|
+
type: Literal[PropertyType.FILES] = PropertyType.FILES
|
|
226
|
+
files: list[File] = Field(default_factory=list)
|
|
264
227
|
|
|
265
228
|
|
|
266
|
-
class
|
|
267
|
-
type: Literal[PropertyType.
|
|
268
|
-
|
|
229
|
+
class PageRelationProperty(PageProperty):
|
|
230
|
+
type: Literal[PropertyType.RELATION] = PropertyType.RELATION
|
|
231
|
+
relation: list[RelationItem] = Field(default_factory=list)
|
|
232
|
+
has_more: bool = False
|
|
269
233
|
|
|
270
234
|
|
|
271
235
|
class PageButtonProperty(PageProperty):
|
|
@@ -273,36 +237,58 @@ class PageButtonProperty(PageProperty):
|
|
|
273
237
|
button: JsonDict = Field(default_factory=dict)
|
|
274
238
|
|
|
275
239
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
240
|
+
class PageLocationProperty(PageProperty):
|
|
241
|
+
type: Literal[PropertyType.LOCATION] = PropertyType.LOCATION
|
|
242
|
+
location: JsonDict | None = None
|
|
279
243
|
|
|
280
244
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
|
284
|
-
|
|
245
|
+
class PagePlaceProperty(PageProperty):
|
|
246
|
+
type: Literal[PropertyType.PLACE] = PropertyType.PLACE
|
|
247
|
+
place: JsonDict | None = None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class PageVerificationProperty(PageProperty):
|
|
251
|
+
type: Literal[PropertyType.VERIFICATION] = PropertyType.VERIFICATION
|
|
252
|
+
verification: VerificationValue
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class PageUniqueIdProperty(PageProperty):
|
|
256
|
+
type: Literal[PropertyType.UNIQUE_ID] = PropertyType.UNIQUE_ID
|
|
257
|
+
unique_id: UniqueIdValue
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class PageUnknownProperty(PageProperty):
|
|
261
|
+
model_config = ConfigDict(extra="allow")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
type AnyPageProperty = (
|
|
265
|
+
PageTitleProperty
|
|
285
266
|
| PageRichTextProperty
|
|
286
|
-
| PageMultiSelectProperty
|
|
287
267
|
| PageSelectProperty
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
| PageTitleProperty
|
|
268
|
+
| PageMultiSelectProperty
|
|
269
|
+
| PageStatusProperty
|
|
291
270
|
| PageNumberProperty
|
|
271
|
+
| PageDateProperty
|
|
292
272
|
| PageCheckboxProperty
|
|
273
|
+
| PageURLProperty
|
|
293
274
|
| PageEmailProperty
|
|
294
275
|
| PagePhoneNumberProperty
|
|
295
|
-
|
|
|
296
|
-
| PageLastEditedTimeProperty
|
|
276
|
+
| PagePeopleProperty
|
|
297
277
|
| PageCreatedByProperty
|
|
298
278
|
| PageLastEditedByProperty
|
|
299
|
-
|
|
|
279
|
+
| PageCreatedTimeProperty
|
|
280
|
+
| PageLastEditedTimeProperty
|
|
281
|
+
| PageLastVisitedTimeProperty
|
|
300
282
|
| PageFormulaProperty
|
|
301
283
|
| PageRollupProperty
|
|
302
|
-
|
|
|
284
|
+
| PageFilesProperty
|
|
285
|
+
| PageRelationProperty
|
|
286
|
+
| PageButtonProperty
|
|
287
|
+
| PageLocationProperty
|
|
288
|
+
| PagePlaceProperty
|
|
303
289
|
| PageVerificationProperty
|
|
304
|
-
|
|
|
305
|
-
|
|
306
|
-
|
|
290
|
+
| PageUniqueIdProperty
|
|
291
|
+
| PageUnknownProperty
|
|
292
|
+
)
|
|
307
293
|
|
|
308
294
|
PagePropertyT = TypeVar("PagePropertyT", bound=PageProperty)
|
|
@@ -10,7 +10,7 @@ from notionary.exceptions.properties import (
|
|
|
10
10
|
PagePropertyTypeError,
|
|
11
11
|
)
|
|
12
12
|
from notionary.page.properties.client import PagePropertyHttpClient
|
|
13
|
-
from notionary.page.properties.
|
|
13
|
+
from notionary.page.properties.schemas import (
|
|
14
14
|
PageCheckboxProperty,
|
|
15
15
|
PageCreatedTimeProperty,
|
|
16
16
|
PageDateProperty,
|
notionary/page/schemas.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
|
|
3
|
-
from notionary.page.properties.
|
|
3
|
+
from notionary.page.properties.schemas import AnyPageProperty
|
|
4
4
|
from notionary.shared.entity.schemas import EntityResponseDto
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class NotionPageDto(EntityResponseDto):
|
|
8
8
|
archived: bool
|
|
9
|
-
properties: dict[str,
|
|
9
|
+
properties: dict[str, AnyPageProperty]
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class PgePropertiesUpdateDto(BaseModel):
|
|
13
|
-
properties: dict[str,
|
|
13
|
+
properties: dict[str, AnyPageProperty]
|
notionary/page/service.py
CHANGED
|
@@ -11,7 +11,7 @@ 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
17
|
from notionary.shared.entity.service import Entity
|
|
@@ -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,12 +1,14 @@
|
|
|
1
1
|
import random
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Self, cast
|
|
5
6
|
|
|
7
|
+
from notionary.file_upload.service import NotionFileUpload
|
|
6
8
|
from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
|
|
7
9
|
from notionary.shared.entity.schemas import EntityResponseDto
|
|
8
|
-
from notionary.shared.models.
|
|
9
|
-
from notionary.shared.models.icon import IconType
|
|
10
|
+
from notionary.shared.models.file import ExternalFile, FileType, NotionHostedFile
|
|
11
|
+
from notionary.shared.models.icon import EmojiIcon, IconType
|
|
10
12
|
from notionary.shared.models.parent import ParentType
|
|
11
13
|
from notionary.user.base import BaseUser
|
|
12
14
|
from notionary.user.service import UserService
|
|
@@ -19,6 +21,7 @@ class Entity(LoggingMixin, ABC):
|
|
|
19
21
|
self,
|
|
20
22
|
dto: EntityResponseDto,
|
|
21
23
|
user_service: UserService | None = None,
|
|
24
|
+
file_upload_service: NotionFileUpload | None = None,
|
|
22
25
|
) -> None:
|
|
23
26
|
self._id = dto.id
|
|
24
27
|
self._created_time = dto.created_time
|
|
@@ -35,6 +38,7 @@ class Entity(LoggingMixin, ABC):
|
|
|
35
38
|
self._cover_image_url = self._extract_cover_image_url(dto)
|
|
36
39
|
|
|
37
40
|
self._user_service = user_service or UserService()
|
|
41
|
+
self._file_upload_service = file_upload_service or NotionFileUpload()
|
|
38
42
|
|
|
39
43
|
@staticmethod
|
|
40
44
|
def _extract_emoji_icon(dto: EntityResponseDto) -> str | None:
|
|
@@ -43,25 +47,36 @@ class Entity(LoggingMixin, ABC):
|
|
|
43
47
|
if dto.icon.type is not IconType.EMOJI:
|
|
44
48
|
return None
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
emoji_icon = cast(EmojiIcon, dto.icon)
|
|
51
|
+
return emoji_icon.emoji
|
|
47
52
|
|
|
48
53
|
@staticmethod
|
|
49
54
|
def _extract_external_icon_url(dto: EntityResponseDto) -> str | None:
|
|
50
55
|
if dto.icon is None:
|
|
51
56
|
return None
|
|
52
|
-
if dto.icon.type is not IconType.EXTERNAL:
|
|
53
|
-
return None
|
|
54
57
|
|
|
55
|
-
|
|
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
|
|
56
66
|
|
|
57
67
|
@staticmethod
|
|
58
68
|
def _extract_cover_image_url(dto: EntityResponseDto) -> str | None:
|
|
59
69
|
if dto.cover is None:
|
|
60
70
|
return None
|
|
61
|
-
if dto.cover.type is not CoverType.EXTERNAL:
|
|
62
|
-
return None
|
|
63
71
|
|
|
64
|
-
|
|
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
|
|
65
80
|
|
|
66
81
|
@classmethod
|
|
67
82
|
@abstractmethod
|
|
@@ -82,10 +97,7 @@ class Entity(LoggingMixin, ABC):
|
|
|
82
97
|
|
|
83
98
|
@property
|
|
84
99
|
@abstractmethod
|
|
85
|
-
def _entity_metadata_update_client(self) -> EntityMetadataUpdateClient:
|
|
86
|
-
# functionality for updating properties like title, icon, cover, archive status depends on interface for template like implementation
|
|
87
|
-
# has to be implementated by inheritants to correctly use the methods below
|
|
88
|
-
...
|
|
100
|
+
def _entity_metadata_update_client(self) -> EntityMetadataUpdateClient: ...
|
|
89
101
|
|
|
90
102
|
@property
|
|
91
103
|
def id(self) -> str:
|
|
@@ -123,10 +135,6 @@ class Entity(LoggingMixin, ABC):
|
|
|
123
135
|
def public_url(self) -> str | None:
|
|
124
136
|
return self._public_url
|
|
125
137
|
|
|
126
|
-
# =========================================================================
|
|
127
|
-
# Parent ID Getters
|
|
128
|
-
# =========================================================================
|
|
129
|
-
|
|
130
138
|
def get_parent_database_id_if_present(self) -> str | None:
|
|
131
139
|
if self._parent.type == ParentType.DATABASE_ID:
|
|
132
140
|
return self._parent.database_id
|
|
@@ -150,20 +158,12 @@ class Entity(LoggingMixin, ABC):
|
|
|
150
158
|
def is_workspace_parent(self) -> bool:
|
|
151
159
|
return self._parent.type == ParentType.WORKSPACE
|
|
152
160
|
|
|
153
|
-
# =========================================================================
|
|
154
|
-
# User Methods
|
|
155
|
-
# =========================================================================
|
|
156
|
-
|
|
157
161
|
async def get_created_by_user(self) -> BaseUser | None:
|
|
158
162
|
return await self._user_service.get_user_by_id(self._created_by.id)
|
|
159
163
|
|
|
160
164
|
async def get_last_edited_by_user(self) -> BaseUser | None:
|
|
161
165
|
return await self._user_service.get_user_by_id(self._last_edited_by.id)
|
|
162
166
|
|
|
163
|
-
# =========================================================================
|
|
164
|
-
# Icon & Cover Methods
|
|
165
|
-
# =========================================================================
|
|
166
|
-
|
|
167
167
|
async def set_emoji_icon(self, emoji: str) -> None:
|
|
168
168
|
entity_response = await self._entity_metadata_update_client.patch_emoji_icon(emoji)
|
|
169
169
|
self._emoji_icon = self._extract_emoji_icon(entity_response)
|
|
@@ -174,6 +174,19 @@ class Entity(LoggingMixin, ABC):
|
|
|
174
174
|
self._emoji_icon = None
|
|
175
175
|
self._external_icon_url = self._extract_external_icon_url(entity_response)
|
|
176
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)
|
|
189
|
+
|
|
177
190
|
async def remove_icon(self) -> None:
|
|
178
191
|
await self._entity_metadata_update_client.remove_icon()
|
|
179
192
|
self._emoji_icon = None
|
|
@@ -183,6 +196,20 @@ class Entity(LoggingMixin, ABC):
|
|
|
183
196
|
entity_response = await self._entity_metadata_update_client.patch_external_cover(image_url)
|
|
184
197
|
self._cover_image_url = self._extract_cover_image_url(entity_response)
|
|
185
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)
|
|
212
|
+
|
|
186
213
|
async def set_random_gradient_cover(self) -> None:
|
|
187
214
|
random_cover_url = self._get_random_gradient_cover()
|
|
188
215
|
await self.set_cover_image_by_url(random_cover_url)
|
|
@@ -191,10 +218,6 @@ class Entity(LoggingMixin, ABC):
|
|
|
191
218
|
await self._entity_metadata_update_client.remove_cover()
|
|
192
219
|
self._cover_image_url = None
|
|
193
220
|
|
|
194
|
-
# =========================================================================
|
|
195
|
-
# Trash Methods
|
|
196
|
-
# =========================================================================
|
|
197
|
-
|
|
198
221
|
async def move_to_trash(self) -> None:
|
|
199
222
|
if self._in_trash:
|
|
200
223
|
self.logger.warning("Entity is already in trash.")
|