notionary 0.3.1__py3-none-any.whl → 0.4.1__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 +49 -1
- notionary/blocks/client.py +37 -11
- notionary/blocks/enums.py +0 -6
- notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
- notionary/blocks/rich_text/models.py +13 -4
- notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
- notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
- notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
- notionary/blocks/schemas.py +33 -78
- notionary/comments/client.py +19 -6
- notionary/comments/factory.py +10 -3
- notionary/comments/schemas.py +10 -31
- notionary/comments/service.py +12 -4
- notionary/data_source/http/data_source_instance_client.py +59 -17
- notionary/data_source/properties/schemas.py +156 -115
- notionary/data_source/query/builder.py +67 -18
- notionary/data_source/query/resolver.py +16 -5
- notionary/data_source/query/schema.py +24 -6
- notionary/data_source/query/validator.py +18 -6
- notionary/data_source/schema/registry.py +31 -12
- notionary/data_source/schema/service.py +66 -20
- notionary/data_source/schemas.py +2 -2
- notionary/data_source/service.py +103 -43
- notionary/database/client.py +27 -9
- notionary/database/database_metadata_update_client.py +12 -4
- notionary/database/schemas.py +2 -2
- notionary/database/service.py +14 -9
- notionary/exceptions/__init__.py +20 -4
- notionary/exceptions/api.py +2 -2
- notionary/exceptions/base.py +1 -1
- notionary/exceptions/block_parsing.py +9 -5
- notionary/exceptions/data_source/builder.py +13 -7
- notionary/exceptions/data_source/properties.py +6 -4
- notionary/exceptions/file_upload.py +76 -0
- notionary/exceptions/properties.py +7 -5
- notionary/exceptions/search.py +10 -6
- notionary/file_upload/__init__.py +4 -0
- notionary/file_upload/client.py +128 -210
- notionary/file_upload/config/__init__.py +17 -0
- notionary/file_upload/config/config.py +39 -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 +58 -0
- notionary/file_upload/query/models.py +37 -0
- notionary/file_upload/schemas.py +80 -0
- notionary/file_upload/service.py +182 -291
- notionary/file_upload/validation/factory.py +66 -0
- notionary/file_upload/validation/impl/file_name_length.py +25 -0
- notionary/file_upload/validation/models.py +134 -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 +131 -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 +33 -30
- notionary/page/content/__init__.py +9 -0
- notionary/page/content/factory.py +21 -7
- notionary/page/content/markdown/builder.py +85 -23
- notionary/page/content/markdown/nodes/audio.py +8 -4
- notionary/page/content/markdown/nodes/base.py +3 -3
- notionary/page/content/markdown/nodes/bookmark.py +5 -3
- notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
- notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
- notionary/page/content/markdown/nodes/callout.py +2 -2
- notionary/page/content/markdown/nodes/code.py +5 -3
- notionary/page/content/markdown/nodes/columns.py +3 -3
- notionary/page/content/markdown/nodes/container.py +9 -5
- notionary/page/content/markdown/nodes/divider.py +2 -2
- notionary/page/content/markdown/nodes/embed.py +8 -4
- notionary/page/content/markdown/nodes/equation.py +4 -2
- notionary/page/content/markdown/nodes/file.py +8 -4
- notionary/page/content/markdown/nodes/heading.py +2 -2
- notionary/page/content/markdown/nodes/image.py +8 -4
- notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
- notionary/page/content/markdown/nodes/numbered_list.py +5 -3
- notionary/page/content/markdown/nodes/paragraph.py +4 -2
- notionary/page/content/markdown/nodes/pdf.py +8 -4
- notionary/page/content/markdown/nodes/quote.py +2 -2
- notionary/page/content/markdown/nodes/space.py +2 -2
- notionary/page/content/markdown/nodes/table.py +8 -5
- notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
- notionary/page/content/markdown/nodes/todo.py +15 -7
- notionary/page/content/markdown/nodes/toggle.py +2 -2
- notionary/page/content/markdown/nodes/video.py +8 -4
- notionary/page/content/markdown/structured_output/__init__.py +73 -0
- notionary/page/content/markdown/structured_output/models.py +391 -0
- notionary/page/content/markdown/structured_output/service.py +211 -0
- notionary/page/content/parser/context.py +1 -1
- notionary/page/content/parser/factory.py +26 -8
- notionary/page/content/parser/parsers/audio.py +12 -32
- notionary/page/content/parser/parsers/base.py +2 -2
- notionary/page/content/parser/parsers/bookmark.py +2 -2
- notionary/page/content/parser/parsers/breadcrumb.py +2 -2
- notionary/page/content/parser/parsers/bulleted_list.py +19 -6
- notionary/page/content/parser/parsers/callout.py +15 -5
- notionary/page/content/parser/parsers/caption.py +9 -3
- notionary/page/content/parser/parsers/code.py +21 -7
- notionary/page/content/parser/parsers/column.py +8 -4
- notionary/page/content/parser/parsers/column_list.py +19 -7
- notionary/page/content/parser/parsers/divider.py +2 -2
- notionary/page/content/parser/parsers/embed.py +2 -4
- notionary/page/content/parser/parsers/equation.py +8 -4
- notionary/page/content/parser/parsers/file.py +12 -34
- notionary/page/content/parser/parsers/file_like_block.py +109 -0
- notionary/page/content/parser/parsers/heading.py +31 -10
- notionary/page/content/parser/parsers/image.py +12 -34
- notionary/page/content/parser/parsers/numbered_list.py +18 -6
- notionary/page/content/parser/parsers/paragraph.py +3 -1
- notionary/page/content/parser/parsers/pdf.py +12 -34
- notionary/page/content/parser/parsers/quote.py +28 -9
- notionary/page/content/parser/parsers/space.py +2 -2
- notionary/page/content/parser/parsers/table.py +31 -10
- notionary/page/content/parser/parsers/table_of_contents.py +7 -3
- notionary/page/content/parser/parsers/todo.py +15 -5
- notionary/page/content/parser/parsers/toggle.py +15 -5
- notionary/page/content/parser/parsers/video.py +12 -34
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
- notionary/page/content/parser/post_processing/service.py +3 -1
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
- notionary/page/content/parser/service.py +4 -1
- notionary/page/content/renderer/context.py +15 -5
- notionary/page/content/renderer/factory.py +12 -6
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
- notionary/page/content/renderer/renderers/audio.py +20 -23
- notionary/page/content/renderer/renderers/base.py +3 -3
- notionary/page/content/renderer/renderers/bookmark.py +3 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
- notionary/page/content/renderer/renderers/callout.py +19 -7
- notionary/page/content/renderer/renderers/captioned_block.py +11 -5
- notionary/page/content/renderer/renderers/code.py +6 -2
- notionary/page/content/renderer/renderers/column.py +3 -1
- notionary/page/content/renderer/renderers/column_list.py +3 -1
- notionary/page/content/renderer/renderers/embed.py +3 -1
- notionary/page/content/renderer/renderers/equation.py +3 -1
- notionary/page/content/renderer/renderers/file.py +20 -23
- notionary/page/content/renderer/renderers/file_like_block.py +47 -0
- notionary/page/content/renderer/renderers/heading.py +22 -8
- notionary/page/content/renderer/renderers/image.py +20 -23
- notionary/page/content/renderer/renderers/numbered_list.py +8 -3
- notionary/page/content/renderer/renderers/paragraph.py +12 -4
- notionary/page/content/renderer/renderers/pdf.py +20 -23
- notionary/page/content/renderer/renderers/quote.py +14 -6
- notionary/page/content/renderer/renderers/table.py +15 -5
- notionary/page/content/renderer/renderers/todo.py +16 -6
- notionary/page/content/renderer/renderers/toggle.py +8 -4
- notionary/page/content/renderer/renderers/video.py +20 -23
- notionary/page/content/renderer/service.py +9 -3
- notionary/page/content/service.py +21 -7
- notionary/page/content/syntax/definition/__init__.py +11 -0
- notionary/page/content/syntax/definition/models.py +57 -0
- notionary/page/content/syntax/definition/registry.py +371 -0
- notionary/page/content/syntax/prompts/__init__.py +4 -0
- notionary/page/content/syntax/prompts/models.py +11 -0
- notionary/page/content/syntax/prompts/registry.py +703 -0
- notionary/page/page_metadata_update_client.py +12 -4
- notionary/page/properties/client.py +46 -16
- notionary/page/properties/factory.py +6 -2
- notionary/page/properties/{models.py → schemas.py} +93 -107
- notionary/page/properties/service.py +111 -37
- notionary/page/schemas.py +3 -3
- notionary/page/service.py +21 -7
- notionary/shared/entity/client.py +6 -2
- notionary/shared/entity/dto_parsers.py +4 -37
- notionary/shared/entity/entity_metadata_update_client.py +25 -5
- notionary/shared/entity/schemas.py +6 -6
- notionary/shared/entity/service.py +89 -35
- notionary/shared/models/file.py +36 -6
- notionary/shared/models/icon.py +5 -12
- notionary/user/base.py +6 -2
- notionary/user/bot.py +22 -14
- notionary/user/client.py +3 -1
- notionary/user/person.py +3 -1
- notionary/user/schemas.py +3 -1
- notionary/user/service.py +6 -2
- notionary/utils/decorators.py +13 -9
- notionary/utils/fuzzy.py +6 -2
- notionary/utils/mixins/logging.py +3 -1
- notionary/utils/pagination.py +14 -4
- notionary/workspace/__init__.py +6 -2
- notionary/workspace/query/__init__.py +2 -1
- notionary/workspace/query/service.py +42 -13
- notionary/workspace/service.py +74 -46
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
- notionary-0.4.1.dist-info/RECORD +236 -0
- notionary/file_upload/models.py +0 -69
- notionary/page/blocks/client.py +0 -1
- notionary/page/content/syntax/__init__.py +0 -4
- notionary/page/content/syntax/models.py +0 -66
- notionary/page/content/syntax/registry.py +0 -393
- notionary/page/page_context.py +0 -50
- notionary/shared/models/cover.py +0 -20
- notionary-0.3.1.dist-info/RECORD +0 -211
- /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
- {notionary-0.3.1.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AudioExtension(StrEnum):
|
|
5
|
+
AAC = ".aac"
|
|
6
|
+
ADTS = ".adts"
|
|
7
|
+
MID = ".mid"
|
|
8
|
+
MIDI = ".midi"
|
|
9
|
+
MP3 = ".mp3"
|
|
10
|
+
MPGA = ".mpga"
|
|
11
|
+
M4A = ".m4a"
|
|
12
|
+
M4B = ".m4b"
|
|
13
|
+
MP4 = ".mp4"
|
|
14
|
+
OGA = ".oga"
|
|
15
|
+
OGG = ".ogg"
|
|
16
|
+
WAV = ".wav"
|
|
17
|
+
WMA = ".wma"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AudioMimeType(StrEnum):
|
|
21
|
+
AAC = "audio/aac"
|
|
22
|
+
MIDI = "audio/midi"
|
|
23
|
+
MPEG = "audio/mpeg"
|
|
24
|
+
MP4 = "audio/mp4"
|
|
25
|
+
OGG = "audio/ogg"
|
|
26
|
+
WAV = "audio/wav"
|
|
27
|
+
WMA = "audio/x-ms-wma"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DocumentExtension(StrEnum):
|
|
31
|
+
PDF = ".pdf"
|
|
32
|
+
TXT = ".txt"
|
|
33
|
+
JSON = ".json"
|
|
34
|
+
DOC = ".doc"
|
|
35
|
+
DOT = ".dot"
|
|
36
|
+
DOCX = ".docx"
|
|
37
|
+
DOTX = ".dotx"
|
|
38
|
+
XLS = ".xls"
|
|
39
|
+
XLT = ".xlt"
|
|
40
|
+
XLA = ".xla"
|
|
41
|
+
XLSX = ".xlsx"
|
|
42
|
+
XLTX = ".xltx"
|
|
43
|
+
PPT = ".ppt"
|
|
44
|
+
POT = ".pot"
|
|
45
|
+
PPS = ".pps"
|
|
46
|
+
PPA = ".ppa"
|
|
47
|
+
PPTX = ".pptx"
|
|
48
|
+
POTX = ".potx"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DocumentMimeType(StrEnum):
|
|
52
|
+
PDF = "application/pdf"
|
|
53
|
+
PLAIN_TEXT = "text/plain"
|
|
54
|
+
JSON = "application/json"
|
|
55
|
+
MSWORD = "application/msword"
|
|
56
|
+
WORD_DOCUMENT = (
|
|
57
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
58
|
+
)
|
|
59
|
+
WORD_TEMPLATE = (
|
|
60
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.template"
|
|
61
|
+
)
|
|
62
|
+
EXCEL = "application/vnd.ms-excel"
|
|
63
|
+
EXCEL_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
64
|
+
EXCEL_TEMPLATE = (
|
|
65
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.template"
|
|
66
|
+
)
|
|
67
|
+
POWERPOINT = "application/vnd.ms-powerpoint"
|
|
68
|
+
POWERPOINT_PRESENTATION = (
|
|
69
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
70
|
+
)
|
|
71
|
+
POWERPOINT_TEMPLATE = (
|
|
72
|
+
"application/vnd.openxmlformats-officedocument.presentationml.template"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ImageExtension(StrEnum):
|
|
77
|
+
GIF = ".gif"
|
|
78
|
+
HEIC = ".heic"
|
|
79
|
+
JPEG = ".jpeg"
|
|
80
|
+
JPG = ".jpg"
|
|
81
|
+
PNG = ".png"
|
|
82
|
+
SVG = ".svg"
|
|
83
|
+
TIF = ".tif"
|
|
84
|
+
TIFF = ".tiff"
|
|
85
|
+
WEBP = ".webp"
|
|
86
|
+
ICO = ".ico"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ImageMimeType(StrEnum):
|
|
90
|
+
GIF = "image/gif"
|
|
91
|
+
HEIC = "image/heic"
|
|
92
|
+
JPEG = "image/jpeg"
|
|
93
|
+
PNG = "image/png"
|
|
94
|
+
SVG = "image/svg+xml"
|
|
95
|
+
TIFF = "image/tiff"
|
|
96
|
+
WEBP = "image/webp"
|
|
97
|
+
ICON = "image/vnd.microsoft.icon"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class VideoExtension(StrEnum):
|
|
101
|
+
AMV = ".amv"
|
|
102
|
+
ASF = ".asf"
|
|
103
|
+
WMV = ".wmv"
|
|
104
|
+
AVI = ".avi"
|
|
105
|
+
F4V = ".f4v"
|
|
106
|
+
FLV = ".flv"
|
|
107
|
+
GIFV = ".gifv"
|
|
108
|
+
M4V = ".m4v"
|
|
109
|
+
MP4 = ".mp4"
|
|
110
|
+
MKV = ".mkv"
|
|
111
|
+
WEBM = ".webm"
|
|
112
|
+
MOV = ".mov"
|
|
113
|
+
QT = ".qt"
|
|
114
|
+
MPEG = ".mpeg"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class VideoMimeType(StrEnum):
|
|
118
|
+
AMV = "video/x-amv"
|
|
119
|
+
ASF = "video/x-ms-asf"
|
|
120
|
+
AVI = "video/x-msvideo"
|
|
121
|
+
F4V = "video/x-f4v"
|
|
122
|
+
FLV = "video/x-flv"
|
|
123
|
+
MP4 = "video/mp4"
|
|
124
|
+
MKV = "video/x-matroska"
|
|
125
|
+
WEBM = "video/webm"
|
|
126
|
+
QUICKTIME = "video/quicktime"
|
|
127
|
+
MPEG = "video/mpeg"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FileCategory(StrEnum):
|
|
131
|
+
AUDIO = "audio"
|
|
132
|
+
DOCUMENT = "document"
|
|
133
|
+
IMAGE = "image"
|
|
134
|
+
VIDEO = "video"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from notionary.file_upload.validation.port import FileUploadValidator
|
|
2
|
+
from notionary.utils.decorators import time_execution_async
|
|
3
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileUploadValidationService(LoggingMixin):
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self._validators: list[FileUploadValidator] = []
|
|
9
|
+
|
|
10
|
+
def register(self, validator: FileUploadValidator) -> None:
|
|
11
|
+
self._validators.append(validator)
|
|
12
|
+
|
|
13
|
+
@time_execution_async()
|
|
14
|
+
async def validate(self) -> None:
|
|
15
|
+
for validator in self._validators:
|
|
16
|
+
self.logger.info("Validating with %s", validator.__class__.__name__)
|
|
17
|
+
await validator.validate()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .file_exists import FileExistsValidator
|
|
2
|
+
from .file_extension import FileExtensionValidator
|
|
3
|
+
from .file_name_length import FileNameLengthValidator
|
|
4
|
+
from .upload_limit import FileUploadLimitValidator
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"FileExistsValidator",
|
|
8
|
+
"FileExtensionValidator",
|
|
9
|
+
"FileNameLengthValidator",
|
|
10
|
+
"FileUploadLimitValidator",
|
|
11
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import override
|
|
3
|
+
|
|
4
|
+
from notionary.exceptions.file_upload import FileNotFoundError
|
|
5
|
+
from notionary.file_upload.validation.port import FileUploadValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileExistsValidator(FileUploadValidator):
|
|
9
|
+
def __init__(self, file_path: Path) -> None:
|
|
10
|
+
self.file_path = file_path
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
async def validate(self) -> None:
|
|
14
|
+
if not self.file_path.exists():
|
|
15
|
+
raise FileNotFoundError(str(self.file_path))
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import ClassVar, override
|
|
3
|
+
|
|
4
|
+
from notionary.exceptions.file_upload import (
|
|
5
|
+
NoFileExtensionException,
|
|
6
|
+
UnsupportedFileTypeException,
|
|
7
|
+
)
|
|
8
|
+
from notionary.file_upload.validation.models import (
|
|
9
|
+
AudioExtension,
|
|
10
|
+
AudioMimeType,
|
|
11
|
+
DocumentExtension,
|
|
12
|
+
DocumentMimeType,
|
|
13
|
+
FileCategory,
|
|
14
|
+
ImageExtension,
|
|
15
|
+
ImageMimeType,
|
|
16
|
+
VideoExtension,
|
|
17
|
+
VideoMimeType,
|
|
18
|
+
)
|
|
19
|
+
from notionary.file_upload.validation.port import FileUploadValidator
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileExtensionValidator(FileUploadValidator):
|
|
23
|
+
EXTENSION_TO_MIME: ClassVar[dict[str, str]] = {
|
|
24
|
+
AudioExtension.AAC: AudioMimeType.AAC,
|
|
25
|
+
AudioExtension.ADTS: AudioMimeType.AAC,
|
|
26
|
+
AudioExtension.MID: AudioMimeType.MIDI,
|
|
27
|
+
AudioExtension.MIDI: AudioMimeType.MIDI,
|
|
28
|
+
AudioExtension.MP3: AudioMimeType.MPEG,
|
|
29
|
+
AudioExtension.MPGA: AudioMimeType.MPEG,
|
|
30
|
+
AudioExtension.M4A: AudioMimeType.MP4,
|
|
31
|
+
AudioExtension.M4B: AudioMimeType.MP4,
|
|
32
|
+
AudioExtension.MP4: AudioMimeType.MP4,
|
|
33
|
+
AudioExtension.OGA: AudioMimeType.OGG,
|
|
34
|
+
AudioExtension.OGG: AudioMimeType.OGG,
|
|
35
|
+
AudioExtension.WAV: AudioMimeType.WAV,
|
|
36
|
+
AudioExtension.WMA: AudioMimeType.WMA,
|
|
37
|
+
DocumentExtension.PDF: DocumentMimeType.PDF,
|
|
38
|
+
DocumentExtension.TXT: DocumentMimeType.PLAIN_TEXT,
|
|
39
|
+
DocumentExtension.JSON: DocumentMimeType.JSON,
|
|
40
|
+
DocumentExtension.DOC: DocumentMimeType.MSWORD,
|
|
41
|
+
DocumentExtension.DOT: DocumentMimeType.MSWORD,
|
|
42
|
+
DocumentExtension.DOCX: DocumentMimeType.WORD_DOCUMENT,
|
|
43
|
+
DocumentExtension.DOTX: DocumentMimeType.WORD_TEMPLATE,
|
|
44
|
+
DocumentExtension.XLS: DocumentMimeType.EXCEL,
|
|
45
|
+
DocumentExtension.XLT: DocumentMimeType.EXCEL,
|
|
46
|
+
DocumentExtension.XLA: DocumentMimeType.EXCEL,
|
|
47
|
+
DocumentExtension.XLSX: DocumentMimeType.EXCEL_SHEET,
|
|
48
|
+
DocumentExtension.XLTX: DocumentMimeType.EXCEL_TEMPLATE,
|
|
49
|
+
DocumentExtension.PPT: DocumentMimeType.POWERPOINT,
|
|
50
|
+
DocumentExtension.POT: DocumentMimeType.POWERPOINT,
|
|
51
|
+
DocumentExtension.PPS: DocumentMimeType.POWERPOINT,
|
|
52
|
+
DocumentExtension.PPA: DocumentMimeType.POWERPOINT,
|
|
53
|
+
DocumentExtension.PPTX: DocumentMimeType.POWERPOINT_PRESENTATION,
|
|
54
|
+
DocumentExtension.POTX: DocumentMimeType.POWERPOINT_TEMPLATE,
|
|
55
|
+
ImageExtension.GIF: ImageMimeType.GIF,
|
|
56
|
+
ImageExtension.HEIC: ImageMimeType.HEIC,
|
|
57
|
+
ImageExtension.JPEG: ImageMimeType.JPEG,
|
|
58
|
+
ImageExtension.JPG: ImageMimeType.JPEG,
|
|
59
|
+
ImageExtension.PNG: ImageMimeType.PNG,
|
|
60
|
+
ImageExtension.SVG: ImageMimeType.SVG,
|
|
61
|
+
ImageExtension.TIF: ImageMimeType.TIFF,
|
|
62
|
+
ImageExtension.TIFF: ImageMimeType.TIFF,
|
|
63
|
+
ImageExtension.WEBP: ImageMimeType.WEBP,
|
|
64
|
+
ImageExtension.ICO: ImageMimeType.ICON,
|
|
65
|
+
VideoExtension.AMV: VideoMimeType.AMV,
|
|
66
|
+
VideoExtension.ASF: VideoMimeType.ASF,
|
|
67
|
+
VideoExtension.WMV: VideoMimeType.ASF,
|
|
68
|
+
VideoExtension.AVI: VideoMimeType.AVI,
|
|
69
|
+
VideoExtension.F4V: VideoMimeType.F4V,
|
|
70
|
+
VideoExtension.FLV: VideoMimeType.FLV,
|
|
71
|
+
VideoExtension.GIFV: VideoMimeType.WEBM,
|
|
72
|
+
VideoExtension.M4V: VideoMimeType.MP4,
|
|
73
|
+
VideoExtension.MP4: VideoMimeType.MP4,
|
|
74
|
+
VideoExtension.MKV: VideoMimeType.MKV,
|
|
75
|
+
VideoExtension.WEBM: VideoMimeType.WEBM,
|
|
76
|
+
VideoExtension.MOV: VideoMimeType.QUICKTIME,
|
|
77
|
+
VideoExtension.QT: VideoMimeType.QUICKTIME,
|
|
78
|
+
VideoExtension.MPEG: VideoMimeType.MPEG,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
EXTENSION_TO_CATEGORY: ClassVar[dict[str, FileCategory]] = {
|
|
82
|
+
**{ext.value: FileCategory.AUDIO for ext in AudioExtension},
|
|
83
|
+
**{ext.value: FileCategory.DOCUMENT for ext in DocumentExtension},
|
|
84
|
+
**{ext.value: FileCategory.IMAGE for ext in ImageExtension},
|
|
85
|
+
**{ext.value: FileCategory.VIDEO for ext in VideoExtension},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def __init__(self, filename: str | Path) -> None:
|
|
89
|
+
self.filename = Path(filename).name if isinstance(filename, Path) else filename
|
|
90
|
+
|
|
91
|
+
@override
|
|
92
|
+
async def validate(self) -> None:
|
|
93
|
+
extension = self._extract_extension(self.filename)
|
|
94
|
+
|
|
95
|
+
if not extension:
|
|
96
|
+
raise NoFileExtensionException(self.filename)
|
|
97
|
+
|
|
98
|
+
if not self._is_supported(extension):
|
|
99
|
+
supported_by_category = self._get_supported_extensions_by_category()
|
|
100
|
+
raise UnsupportedFileTypeException(
|
|
101
|
+
extension, self.filename, supported_by_category
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _extract_extension(filename: str) -> str:
|
|
106
|
+
import os
|
|
107
|
+
|
|
108
|
+
_, ext = os.path.splitext(filename)
|
|
109
|
+
return ext.lower()
|
|
110
|
+
|
|
111
|
+
def _is_supported(self, extension: str) -> bool:
|
|
112
|
+
normalized = self._normalize_extension(extension)
|
|
113
|
+
return normalized in self.EXTENSION_TO_MIME
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _normalize_extension(extension: str) -> str:
|
|
117
|
+
extension = extension.lower()
|
|
118
|
+
if not extension.startswith("."):
|
|
119
|
+
extension = f".{extension}"
|
|
120
|
+
return extension
|
|
121
|
+
|
|
122
|
+
def _get_supported_extensions_by_category(self) -> dict[str, list[str]]:
|
|
123
|
+
result = {}
|
|
124
|
+
for category in FileCategory:
|
|
125
|
+
extensions = [
|
|
126
|
+
ext
|
|
127
|
+
for ext, cat in self.EXTENSION_TO_CATEGORY.items()
|
|
128
|
+
if cat == category
|
|
129
|
+
]
|
|
130
|
+
result[category.value] = extensions
|
|
131
|
+
return result
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.exceptions.file_upload import FilenameTooLongError
|
|
4
|
+
from notionary.file_upload.config.constants import NOTION_MAX_FILENAME_BYTES
|
|
5
|
+
from notionary.file_upload.validation.port import FileUploadValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileNameLengthValidator(FileUploadValidator):
|
|
9
|
+
def __init__(self, filename: str) -> None:
|
|
10
|
+
self._filename = filename
|
|
11
|
+
self._max_filename_bytes = NOTION_MAX_FILENAME_BYTES
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
async def validate(self) -> None:
|
|
15
|
+
filename_bytes = len(self._filename.encode("utf-8"))
|
|
16
|
+
if filename_bytes > self._max_filename_bytes:
|
|
17
|
+
raise FilenameTooLongError(
|
|
18
|
+
filename=self._filename,
|
|
19
|
+
filename_bytes=filename_bytes,
|
|
20
|
+
max_filename_bytes=self._max_filename_bytes,
|
|
21
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, override
|
|
5
|
+
|
|
6
|
+
from notionary.exceptions.file_upload import FileSizeException
|
|
7
|
+
from notionary.file_upload.validation.port import FileUploadValidator
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from notionary.user import BotUser
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FileUploadLimitValidator(FileUploadValidator):
|
|
14
|
+
def __init__(self, filename: str | Path, file_size_bytes: int) -> None:
|
|
15
|
+
self.filename = Path(filename).name if isinstance(filename, Path) else filename
|
|
16
|
+
self.file_size_bytes = file_size_bytes
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
async def validate(self, integration: BotUser | None = None) -> None:
|
|
20
|
+
from notionary.user import BotUser
|
|
21
|
+
|
|
22
|
+
integration = integration or await BotUser.from_current_integration()
|
|
23
|
+
|
|
24
|
+
max_file_size_in_bytes = integration.workspace_file_upload_limit_in_bytes
|
|
25
|
+
|
|
26
|
+
if self.file_size_bytes > max_file_size_in_bytes:
|
|
27
|
+
raise FileSizeException(
|
|
28
|
+
filename=self.filename,
|
|
29
|
+
file_size_bytes=self.file_size_bytes,
|
|
30
|
+
max_size_bytes=max_file_size_in_bytes,
|
|
31
|
+
)
|
notionary/http/client.py
CHANGED
|
@@ -18,7 +18,7 @@ from notionary.http.models import HttpMethod
|
|
|
18
18
|
from notionary.shared.typings import JsonDict
|
|
19
19
|
from notionary.utils.mixins.logging import LoggingMixin
|
|
20
20
|
|
|
21
|
-
load_dotenv()
|
|
21
|
+
load_dotenv(override=True)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class NotionHttpClient(LoggingMixin):
|
|
@@ -44,34 +44,32 @@ class NotionHttpClient(LoggingMixin):
|
|
|
44
44
|
self._is_initialized = False
|
|
45
45
|
|
|
46
46
|
def __del__(self):
|
|
47
|
-
"""Auto-cleanup when client is destroyed."""
|
|
48
47
|
if not hasattr(self, "client") or not self.client:
|
|
49
48
|
return
|
|
50
49
|
|
|
51
50
|
try:
|
|
52
51
|
loop = asyncio.get_event_loop()
|
|
53
52
|
if not loop.is_running():
|
|
54
|
-
self.logger.warning(
|
|
53
|
+
self.logger.warning(
|
|
54
|
+
"Event loop not running, could not auto-close NotionHttpClient"
|
|
55
|
+
)
|
|
55
56
|
return
|
|
56
57
|
|
|
57
58
|
loop.create_task(self.close())
|
|
58
59
|
self.logger.debug("Created cleanup task for NotionHttpClient")
|
|
59
60
|
except RuntimeError:
|
|
60
|
-
self.logger.warning(
|
|
61
|
+
self.logger.warning(
|
|
62
|
+
"No event loop available for auto-closing NotionHttpClient"
|
|
63
|
+
)
|
|
61
64
|
|
|
62
65
|
async def __aenter__(self):
|
|
63
|
-
"""Async context manager entry."""
|
|
64
66
|
await self._ensure_initialized()
|
|
65
67
|
return self
|
|
66
68
|
|
|
67
69
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
68
|
-
"""Async context manager exit."""
|
|
69
70
|
await self.close()
|
|
70
71
|
|
|
71
72
|
async def close(self) -> None:
|
|
72
|
-
"""
|
|
73
|
-
Closes the HTTP client and releases resources.
|
|
74
|
-
"""
|
|
75
73
|
if not hasattr(self, "client") or not self.client:
|
|
76
74
|
return
|
|
77
75
|
|
|
@@ -80,34 +78,31 @@ class NotionHttpClient(LoggingMixin):
|
|
|
80
78
|
self._is_initialized = False
|
|
81
79
|
self.logger.debug("NotionHttpClient closed")
|
|
82
80
|
|
|
83
|
-
async def get(
|
|
84
|
-
|
|
81
|
+
async def get(
|
|
82
|
+
self, endpoint: str, params: JsonDict | None = None
|
|
83
|
+
) -> JsonDict | None:
|
|
84
|
+
return await self._make_request(HttpMethod.GET, endpoint, params=params)
|
|
85
85
|
|
|
86
|
-
async def post(
|
|
87
|
-
|
|
86
|
+
async def post(
|
|
87
|
+
self, endpoint: str, data: JsonDict | None = None
|
|
88
|
+
) -> JsonDict | None:
|
|
89
|
+
return await self._make_request(HttpMethod.POST, endpoint, data)
|
|
88
90
|
|
|
89
|
-
async def patch(
|
|
90
|
-
|
|
91
|
+
async def patch(
|
|
92
|
+
self, endpoint: str, data: JsonDict | None = None
|
|
93
|
+
) -> JsonDict | None:
|
|
94
|
+
return await self._make_request(HttpMethod.PATCH, endpoint, data)
|
|
91
95
|
|
|
92
96
|
async def delete(self, endpoint: str) -> JsonDict | None:
|
|
93
|
-
return await self.
|
|
97
|
+
return await self._make_request(HttpMethod.DELETE, endpoint)
|
|
94
98
|
|
|
95
|
-
async def
|
|
99
|
+
async def _make_request(
|
|
96
100
|
self,
|
|
97
101
|
method: HttpMethod,
|
|
98
102
|
endpoint: str,
|
|
99
103
|
data: JsonDict | None = None,
|
|
100
104
|
params: JsonDict | None = None,
|
|
101
105
|
) -> JsonDict | None:
|
|
102
|
-
"""
|
|
103
|
-
Executes an HTTP request and returns the data or None on error.
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
method: HTTP method to use
|
|
107
|
-
endpoint: API endpoint
|
|
108
|
-
data: Request body data (for POST/PATCH)
|
|
109
|
-
params: Query parameters (for GET requests)
|
|
110
|
-
"""
|
|
111
106
|
await self._ensure_initialized()
|
|
112
107
|
|
|
113
108
|
url = f"{self.BASE_URL}/{endpoint.lstrip('/')}"
|
|
@@ -120,10 +115,15 @@ class NotionHttpClient(LoggingMixin):
|
|
|
120
115
|
if params:
|
|
121
116
|
request_kwargs["params"] = params
|
|
122
117
|
|
|
123
|
-
if
|
|
118
|
+
if (
|
|
119
|
+
method.value in [HttpMethod.POST.value, HttpMethod.PATCH.value]
|
|
120
|
+
and data is not None
|
|
121
|
+
):
|
|
124
122
|
request_kwargs["json"] = data
|
|
125
123
|
|
|
126
|
-
response: httpx.Response = await getattr(self.client, method.value)(
|
|
124
|
+
response: httpx.Response = await getattr(self.client, method.value)(
|
|
125
|
+
url, **request_kwargs
|
|
126
|
+
)
|
|
127
127
|
|
|
128
128
|
response.raise_for_status()
|
|
129
129
|
result_data = response.json()
|
|
@@ -141,7 +141,6 @@ class NotionHttpClient(LoggingMixin):
|
|
|
141
141
|
status_code = e.response.status_code
|
|
142
142
|
response_text = e.response.text
|
|
143
143
|
|
|
144
|
-
# Map HTTP status codes to specific business exceptions
|
|
145
144
|
if status_code == 401:
|
|
146
145
|
raise NotionAuthenticationError(
|
|
147
146
|
"Invalid or missing API key. Please check your Notion integration token.",
|
|
@@ -189,7 +188,11 @@ class NotionHttpClient(LoggingMixin):
|
|
|
189
188
|
|
|
190
189
|
def _find_token(self) -> str | None:
|
|
191
190
|
token = next(
|
|
192
|
-
(
|
|
191
|
+
(
|
|
192
|
+
os.getenv(var)
|
|
193
|
+
for var in ("NOTION_SECRET", "NOTION_INTEGRATION_KEY", "NOTION_TOKEN")
|
|
194
|
+
if os.getenv(var)
|
|
195
|
+
),
|
|
193
196
|
None,
|
|
194
197
|
)
|
|
195
198
|
if token:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from notionary.blocks.client import NotionBlockHttpClient
|
|
2
2
|
from notionary.page.content.parser.factory import ConverterChainFactory
|
|
3
|
-
from notionary.page.content.parser.post_processing.handlers import
|
|
3
|
+
from notionary.page.content.parser.post_processing.handlers import (
|
|
4
|
+
RichTextLengthTruncationPostProcessor,
|
|
5
|
+
)
|
|
4
6
|
from notionary.page.content.parser.post_processing.service import BlockPostProcessor
|
|
5
7
|
from notionary.page.content.parser.pre_processsing.handlers import (
|
|
6
8
|
ColumnSyntaxPreProcessor,
|
|
@@ -11,8 +13,12 @@ from notionary.page.content.parser.pre_processsing.handlers import (
|
|
|
11
13
|
from notionary.page.content.parser.pre_processsing.service import MarkdownPreProcessor
|
|
12
14
|
from notionary.page.content.parser.service import MarkdownToNotionConverter
|
|
13
15
|
from notionary.page.content.renderer.factory import RendererChainFactory
|
|
14
|
-
from notionary.page.content.renderer.post_processing.handlers import
|
|
15
|
-
|
|
16
|
+
from notionary.page.content.renderer.post_processing.handlers import (
|
|
17
|
+
NumberedListPlaceholderReplacerPostProcessor,
|
|
18
|
+
)
|
|
19
|
+
from notionary.page.content.renderer.post_processing.service import (
|
|
20
|
+
MarkdownRenderingPostProcessor,
|
|
21
|
+
)
|
|
16
22
|
from notionary.page.content.renderer.service import NotionToMarkdownConverter
|
|
17
23
|
from notionary.page.content.service import PageContentService
|
|
18
24
|
|
|
@@ -23,10 +29,14 @@ class PageContentServiceFactory:
|
|
|
23
29
|
converter_chain_factory: ConverterChainFactory | None = None,
|
|
24
30
|
renderer_chain_factory: RendererChainFactory | None = None,
|
|
25
31
|
) -> None:
|
|
26
|
-
self._converter_chain_factory =
|
|
32
|
+
self._converter_chain_factory = (
|
|
33
|
+
converter_chain_factory or ConverterChainFactory()
|
|
34
|
+
)
|
|
27
35
|
self._renderer_chain_factory = renderer_chain_factory or RendererChainFactory()
|
|
28
36
|
|
|
29
|
-
def create(
|
|
37
|
+
def create(
|
|
38
|
+
self, page_id: str, block_client: NotionBlockHttpClient
|
|
39
|
+
) -> PageContentService:
|
|
30
40
|
markdown_converter = self._create_markdown_to_notion_converter()
|
|
31
41
|
notion_to_markdown_converter = self._create_notion_to_markdown_converter()
|
|
32
42
|
|
|
@@ -50,7 +60,9 @@ class PageContentServiceFactory:
|
|
|
50
60
|
|
|
51
61
|
def _create_notion_to_markdown_converter(self) -> NotionToMarkdownConverter:
|
|
52
62
|
renderer_chain = self._renderer_chain_factory.create()
|
|
53
|
-
markdown_rendering_post_processor =
|
|
63
|
+
markdown_rendering_post_processor = (
|
|
64
|
+
self._create_markdown_rendering_post_processor()
|
|
65
|
+
)
|
|
54
66
|
return NotionToMarkdownConverter(
|
|
55
67
|
renderer_chain=renderer_chain,
|
|
56
68
|
post_processor=markdown_rendering_post_processor,
|
|
@@ -69,7 +81,9 @@ class PageContentServiceFactory:
|
|
|
69
81
|
post_processor.register(RichTextLengthTruncationPostProcessor())
|
|
70
82
|
return post_processor
|
|
71
83
|
|
|
72
|
-
def _create_markdown_rendering_post_processor(
|
|
84
|
+
def _create_markdown_rendering_post_processor(
|
|
85
|
+
self,
|
|
86
|
+
) -> MarkdownRenderingPostProcessor:
|
|
73
87
|
post_processor = MarkdownRenderingPostProcessor()
|
|
74
88
|
post_processor.register(NumberedListPlaceholderReplacerPostProcessor())
|
|
75
89
|
return post_processor
|