notionary 0.4.0__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 +44 -1
- notionary/blocks/client.py +37 -11
- 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 +2 -1
- notionary/comments/client.py +19 -6
- notionary/comments/factory.py +10 -3
- notionary/comments/schemas.py +9 -3
- notionary/comments/service.py +12 -4
- notionary/data_source/http/data_source_instance_client.py +59 -17
- notionary/data_source/properties/schemas.py +30 -10
- 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/service.py +74 -23
- notionary/database/client.py +27 -9
- notionary/database/database_metadata_update_client.py +12 -4
- notionary/database/service.py +11 -4
- notionary/exceptions/__init__.py +15 -3
- notionary/exceptions/block_parsing.py +6 -2
- notionary/exceptions/data_source/builder.py +11 -5
- notionary/exceptions/data_source/properties.py +3 -1
- notionary/exceptions/file_upload.py +12 -3
- notionary/exceptions/properties.py +3 -1
- notionary/exceptions/search.py +6 -2
- notionary/file_upload/client.py +5 -1
- notionary/file_upload/config/config.py +10 -3
- notionary/file_upload/query/builder.py +6 -2
- notionary/file_upload/schemas.py +3 -1
- notionary/file_upload/service.py +42 -14
- notionary/file_upload/validation/factory.py +3 -1
- notionary/file_upload/validation/impl/file_name_length.py +3 -1
- notionary/file_upload/validation/models.py +15 -5
- notionary/file_upload/validation/validators/file_extension.py +12 -3
- notionary/http/client.py +27 -8
- 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 +23 -8
- notionary/page/content/parser/parsers/audio.py +7 -2
- 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 -2
- notionary/page/content/parser/parsers/equation.py +8 -4
- notionary/page/content/parser/parsers/file.py +7 -2
- notionary/page/content/parser/parsers/file_like_block.py +30 -10
- notionary/page/content/parser/parsers/heading.py +31 -10
- notionary/page/content/parser/parsers/image.py +7 -2
- 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 +7 -2
- 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 +7 -2
- 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 +14 -5
- 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 +14 -5
- notionary/page/content/renderer/renderers/file_like_block.py +8 -4
- notionary/page/content/renderer/renderers/heading.py +22 -8
- notionary/page/content/renderer/renderers/image.py +13 -4
- 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 +14 -5
- 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 +14 -5
- 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 +45 -15
- notionary/page/properties/factory.py +6 -2
- notionary/page/properties/service.py +110 -36
- notionary/page/service.py +20 -6
- notionary/shared/entity/client.py +6 -2
- notionary/shared/entity/dto_parsers.py +3 -1
- notionary/shared/entity/entity_metadata_update_client.py +9 -3
- notionary/shared/entity/service.py +53 -22
- notionary/shared/models/file.py +3 -1
- notionary/user/base.py +6 -2
- notionary/user/bot.py +10 -2
- 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 +6 -2
- notionary/utils/fuzzy.py +6 -2
- notionary/utils/mixins/logging.py +3 -1
- notionary/utils/pagination.py +14 -4
- notionary/workspace/__init__.py +5 -1
- notionary/workspace/query/service.py +59 -16
- notionary/workspace/service.py +39 -11
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
- notionary-0.4.1.dist-info/RECORD +236 -0
- notionary/page/blocks/client.py +0 -1
- notionary/page/content/syntax/__init__.py +0 -5
- notionary/page/content/syntax/models.py +0 -66
- notionary/page/content/syntax/registry.py +0 -371
- notionary-0.4.0.dist-info/RECORD +0 -230
- /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
- {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,12 @@ from notionary.exceptions.base import NotionaryException
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class UnsupportedFileTypeException(NotionaryException):
|
|
5
|
-
def __init__(
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
extension: str,
|
|
8
|
+
filename: str,
|
|
9
|
+
supported_extensions_by_category: dict[str, list[str]],
|
|
10
|
+
):
|
|
6
11
|
supported_exts = []
|
|
7
12
|
for category, extensions in supported_extensions_by_category.items():
|
|
8
13
|
supported_exts.append(f"{category}: {', '.join(extensions[:5])}...")
|
|
@@ -44,7 +49,9 @@ class FileNotFoundError(NotionaryException):
|
|
|
44
49
|
|
|
45
50
|
class FilenameTooLongError(NotionaryException):
|
|
46
51
|
def __init__(self, filename: str, filename_bytes: int, max_filename_bytes: int):
|
|
47
|
-
super().__init__(
|
|
52
|
+
super().__init__(
|
|
53
|
+
f"Filename too long: {filename_bytes} bytes (max {max_filename_bytes}). Filename: {filename}"
|
|
54
|
+
)
|
|
48
55
|
self.filename = filename
|
|
49
56
|
self.filename_bytes = filename_bytes
|
|
50
57
|
self.max_filename_bytes = max_filename_bytes
|
|
@@ -62,6 +69,8 @@ class UploadFailedError(NotionaryException):
|
|
|
62
69
|
|
|
63
70
|
class UploadTimeoutError(NotionaryException):
|
|
64
71
|
def __init__(self, file_upload_id: str, timeout_seconds: int):
|
|
65
|
-
super().__init__(
|
|
72
|
+
super().__init__(
|
|
73
|
+
f"Upload timeout after {timeout_seconds}s for file_upload_id: {file_upload_id}"
|
|
74
|
+
)
|
|
66
75
|
self.file_upload_id = file_upload_id
|
|
67
76
|
self.timeout_seconds = timeout_seconds
|
|
@@ -50,7 +50,9 @@ class AccessPagePropertyWithoutDataSourceError(NotionaryException):
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
def __init__(self, parent_type: ParentType) -> None:
|
|
53
|
-
parent_desc = self._PARENT_DESCRIPTIONS.get(
|
|
53
|
+
parent_desc = self._PARENT_DESCRIPTIONS.get(
|
|
54
|
+
parent_type, f"its parent type is '{parent_type}'"
|
|
55
|
+
)
|
|
54
56
|
message = (
|
|
55
57
|
f"Cannot access properties other than title because this page's parent is {parent_desc}. "
|
|
56
58
|
"To use operations like property reading/writing, you need to use a page whose parent is a data source."
|
notionary/exceptions/search.py
CHANGED
|
@@ -2,7 +2,9 @@ from notionary.exceptions.base import NotionaryException
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class EntityNotFound(NotionaryException):
|
|
5
|
-
def __init__(
|
|
5
|
+
def __init__(
|
|
6
|
+
self, entity_type: str, query: str, available_titles: list[str] | None = None
|
|
7
|
+
) -> None:
|
|
6
8
|
self.entity_type = entity_type
|
|
7
9
|
self.query = query
|
|
8
10
|
self.available_titles = available_titles or []
|
|
@@ -41,7 +43,9 @@ class NoUsersInWorkspace(NotionaryException):
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class UserNotFound(NotionaryException):
|
|
44
|
-
def __init__(
|
|
46
|
+
def __init__(
|
|
47
|
+
self, user_type: str, query: str, available_names: list[str] | None = None
|
|
48
|
+
) -> None:
|
|
45
49
|
self.user_type = user_type
|
|
46
50
|
self.query = query
|
|
47
51
|
self.available_names = available_names or []
|
notionary/file_upload/client.py
CHANGED
|
@@ -11,7 +11,11 @@ from notionary.file_upload.schemas import (
|
|
|
11
11
|
UploadMode,
|
|
12
12
|
)
|
|
13
13
|
from notionary.http.client import NotionHttpClient
|
|
14
|
-
from notionary.utils.pagination import
|
|
14
|
+
from notionary.utils.pagination import (
|
|
15
|
+
PaginatedResponse,
|
|
16
|
+
paginate_notion_api,
|
|
17
|
+
paginate_notion_api_generator,
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
class FileUploadHttpClient(NotionHttpClient):
|
|
@@ -22,11 +22,18 @@ class FileUploadConfig(BaseModel):
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
max_upload_timeout: int = Field(
|
|
25
|
-
default=300,
|
|
25
|
+
default=300,
|
|
26
|
+
gt=0,
|
|
27
|
+
description="Maximum time in seconds to wait for an upload to complete.",
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
poll_interval: int = Field(
|
|
30
|
+
poll_interval: int = Field(
|
|
31
|
+
default=2,
|
|
32
|
+
gt=0,
|
|
33
|
+
description="Interval in seconds for polling the upload status.",
|
|
34
|
+
)
|
|
29
35
|
|
|
30
36
|
base_upload_path: Path | None = Field(
|
|
31
|
-
default=None,
|
|
37
|
+
default=None,
|
|
38
|
+
description="Optional default base path for resolving relative file uploads.",
|
|
32
39
|
)
|
|
@@ -42,12 +42,16 @@ class FileUploadQueryBuilder:
|
|
|
42
42
|
return value
|
|
43
43
|
|
|
44
44
|
def with_total_results_limit(self, total_results_limit: int) -> Self:
|
|
45
|
-
self._query.total_results_limit = self._validate_total_results_limit(
|
|
45
|
+
self._query.total_results_limit = self._validate_total_results_limit(
|
|
46
|
+
total_results_limit
|
|
47
|
+
)
|
|
46
48
|
return self
|
|
47
49
|
|
|
48
50
|
def _validate_total_results_limit(self, value: int) -> int:
|
|
49
51
|
if not (1 <= value <= 100):
|
|
50
|
-
raise ValueError(
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"total_results_limit must be between 1 and 100, got {value}"
|
|
54
|
+
)
|
|
51
55
|
return value
|
|
52
56
|
|
|
53
57
|
def build(self) -> FileUploadQuery:
|
notionary/file_upload/schemas.py
CHANGED
|
@@ -52,7 +52,9 @@ class FileUploadCreateRequest(BaseModel):
|
|
|
52
52
|
if self.mode == UploadMode.MULTI_PART and self.number_of_parts is None:
|
|
53
53
|
raise ValueError("number_of_parts is required when mode is 'multi_part'")
|
|
54
54
|
if self.mode == UploadMode.SINGLE_PART and self.number_of_parts is not None:
|
|
55
|
-
raise ValueError(
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"number_of_parts should not be provided for 'single_part' mode"
|
|
57
|
+
)
|
|
56
58
|
return self
|
|
57
59
|
|
|
58
60
|
def model_dump(self, **kwargs):
|
notionary/file_upload/service.py
CHANGED
|
@@ -27,7 +27,9 @@ class NotionFileUpload(LoggingMixin):
|
|
|
27
27
|
self._config = config or FileUploadConfig()
|
|
28
28
|
self._file_reader = file_reader or FileContentReader(config=self._config)
|
|
29
29
|
|
|
30
|
-
async def upload_file(
|
|
30
|
+
async def upload_file(
|
|
31
|
+
self, file_path: Path, filename: str | None = None
|
|
32
|
+
) -> FileUploadResponse:
|
|
31
33
|
file_path = Path(file_path)
|
|
32
34
|
|
|
33
35
|
if not file_path.is_absolute() and self._config.base_upload_path:
|
|
@@ -43,10 +45,15 @@ class NotionFileUpload(LoggingMixin):
|
|
|
43
45
|
|
|
44
46
|
if self._fits_in_single_part(file_size):
|
|
45
47
|
content = await self._file_reader.read_full_file(file_path)
|
|
46
|
-
return await self._upload_single_part_content(
|
|
48
|
+
return await self._upload_single_part_content(
|
|
49
|
+
content, filename, content_type
|
|
50
|
+
)
|
|
47
51
|
else:
|
|
48
52
|
return await self._upload_multi_part_content(
|
|
49
|
-
filename,
|
|
53
|
+
filename,
|
|
54
|
+
content_type,
|
|
55
|
+
file_size,
|
|
56
|
+
self._file_reader.read_file_chunks(file_path),
|
|
50
57
|
)
|
|
51
58
|
|
|
52
59
|
async def upload_from_bytes(
|
|
@@ -66,10 +73,15 @@ class NotionFileUpload(LoggingMixin):
|
|
|
66
73
|
content_type = content_type or self._guess_content_type(filename)
|
|
67
74
|
|
|
68
75
|
if self._fits_in_single_part(file_size):
|
|
69
|
-
return await self._upload_single_part_content(
|
|
76
|
+
return await self._upload_single_part_content(
|
|
77
|
+
file_content, filename, content_type
|
|
78
|
+
)
|
|
70
79
|
|
|
71
80
|
return await self._upload_multi_part_content(
|
|
72
|
-
filename,
|
|
81
|
+
filename,
|
|
82
|
+
content_type,
|
|
83
|
+
file_size,
|
|
84
|
+
self._file_reader.bytes_to_chunks(file_content),
|
|
73
85
|
)
|
|
74
86
|
|
|
75
87
|
async def _upload_single_part_content(
|
|
@@ -86,7 +98,10 @@ class NotionFileUpload(LoggingMixin):
|
|
|
86
98
|
filename=filename,
|
|
87
99
|
)
|
|
88
100
|
|
|
89
|
-
self.logger.info(
|
|
101
|
+
self.logger.info(
|
|
102
|
+
"Single-part content sent, waiting for completion... (ID: %s)",
|
|
103
|
+
file_upload.id,
|
|
104
|
+
)
|
|
90
105
|
return await self._wait_for_completion(file_upload.id)
|
|
91
106
|
|
|
92
107
|
async def _upload_multi_part_content(
|
|
@@ -108,7 +123,10 @@ class NotionFileUpload(LoggingMixin):
|
|
|
108
123
|
|
|
109
124
|
await self._client.complete_upload(file_upload.id)
|
|
110
125
|
|
|
111
|
-
self.logger.info(
|
|
126
|
+
self.logger.info(
|
|
127
|
+
"Multi-part content sent, waiting for completion... (ID: %s)",
|
|
128
|
+
file_upload.id,
|
|
129
|
+
)
|
|
112
130
|
return await self._wait_for_completion(file_upload.id)
|
|
113
131
|
|
|
114
132
|
async def _send_parts(
|
|
@@ -133,7 +151,8 @@ class NotionFileUpload(LoggingMixin):
|
|
|
133
151
|
|
|
134
152
|
except Exception as e:
|
|
135
153
|
raise UploadFailedError(
|
|
136
|
-
file_upload_id=file_upload_id,
|
|
154
|
+
file_upload_id=file_upload_id,
|
|
155
|
+
reason=f"Failed to upload part {part_number}/{total_parts}: {e}",
|
|
137
156
|
) from e
|
|
138
157
|
|
|
139
158
|
def _fits_in_single_part(self, file_size: int) -> bool:
|
|
@@ -144,7 +163,9 @@ class NotionFileUpload(LoggingMixin):
|
|
|
144
163
|
return content_type
|
|
145
164
|
|
|
146
165
|
def _calculate_part_count(self, file_size: int) -> int:
|
|
147
|
-
return (
|
|
166
|
+
return (
|
|
167
|
+
file_size + self._config.multi_part_chunk_size - 1
|
|
168
|
+
) // self._config.multi_part_chunk_size
|
|
148
169
|
|
|
149
170
|
async def get_upload_status(self, file_upload_id: str) -> str:
|
|
150
171
|
try:
|
|
@@ -161,12 +182,16 @@ class NotionFileUpload(LoggingMixin):
|
|
|
161
182
|
timeout = timeout_seconds or self._config.max_upload_timeout
|
|
162
183
|
|
|
163
184
|
try:
|
|
164
|
-
return await asyncio.wait_for(
|
|
185
|
+
return await asyncio.wait_for(
|
|
186
|
+
self._poll_status_until_complete(file_upload_id), timeout=timeout
|
|
187
|
+
)
|
|
165
188
|
|
|
166
189
|
except TimeoutError as e:
|
|
167
190
|
raise UploadTimeoutError(file_upload_id, timeout) from e
|
|
168
191
|
|
|
169
|
-
async def _poll_status_until_complete(
|
|
192
|
+
async def _poll_status_until_complete(
|
|
193
|
+
self, file_upload_id: str
|
|
194
|
+
) -> FileUploadResponse:
|
|
170
195
|
while True:
|
|
171
196
|
upload_info = await self._client.get_file_upload(file_upload_id)
|
|
172
197
|
|
|
@@ -182,7 +207,8 @@ class NotionFileUpload(LoggingMixin):
|
|
|
182
207
|
async def get_uploads(
|
|
183
208
|
self,
|
|
184
209
|
*,
|
|
185
|
-
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
210
|
+
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
211
|
+
| None = None,
|
|
186
212
|
query: FileUploadQuery | None = None,
|
|
187
213
|
) -> list[FileUploadResponse]:
|
|
188
214
|
resolved_query = self._resolve_query(filter_fn=filter_fn, query=query)
|
|
@@ -191,7 +217,8 @@ class NotionFileUpload(LoggingMixin):
|
|
|
191
217
|
async def iter_uploads(
|
|
192
218
|
self,
|
|
193
219
|
*,
|
|
194
|
-
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
220
|
+
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
221
|
+
| None = None,
|
|
195
222
|
query: FileUploadQuery | None = None,
|
|
196
223
|
) -> AsyncIterator[FileUploadResponse]:
|
|
197
224
|
resolved_query = self._resolve_query(filter_fn=filter_fn, query=query)
|
|
@@ -200,7 +227,8 @@ class NotionFileUpload(LoggingMixin):
|
|
|
200
227
|
|
|
201
228
|
def _resolve_query(
|
|
202
229
|
self,
|
|
203
|
-
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
230
|
+
filter_fn: Callable[[FileUploadQueryBuilder], FileUploadQueryBuilder]
|
|
231
|
+
| None = None,
|
|
204
232
|
query: FileUploadQuery | None = None,
|
|
205
233
|
) -> FileUploadQuery:
|
|
206
234
|
if filter_fn and query:
|
|
@@ -60,5 +60,7 @@ def _create_extension_validator(filename: str) -> FileExtensionValidator:
|
|
|
60
60
|
return FileExtensionValidator(filename=filename)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def _create_size_validator(
|
|
63
|
+
def _create_size_validator(
|
|
64
|
+
filename: str, file_size_bytes: int
|
|
65
|
+
) -> FileUploadLimitValidator:
|
|
64
66
|
return FileUploadLimitValidator(filename=filename, file_size_bytes=file_size_bytes)
|
|
@@ -6,7 +6,9 @@ from notionary.file_upload.validation.port import FileUploadValidator
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class FileNameLengthValidator(FileUploadValidator):
|
|
9
|
-
def __init__(
|
|
9
|
+
def __init__(
|
|
10
|
+
self, filename: str, file_upload_config: FileUploadConfig | None = None
|
|
11
|
+
) -> None:
|
|
10
12
|
self._filename = filename
|
|
11
13
|
|
|
12
14
|
file_upload_config = file_upload_config or FileUploadConfig()
|
|
@@ -53,14 +53,24 @@ class DocumentMimeType(StrEnum):
|
|
|
53
53
|
PLAIN_TEXT = "text/plain"
|
|
54
54
|
JSON = "application/json"
|
|
55
55
|
MSWORD = "application/msword"
|
|
56
|
-
WORD_DOCUMENT =
|
|
57
|
-
|
|
56
|
+
WORD_DOCUMENT = (
|
|
57
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
58
|
+
)
|
|
59
|
+
WORD_TEMPLATE = (
|
|
60
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.template"
|
|
61
|
+
)
|
|
58
62
|
EXCEL = "application/vnd.ms-excel"
|
|
59
63
|
EXCEL_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
60
|
-
EXCEL_TEMPLATE =
|
|
64
|
+
EXCEL_TEMPLATE = (
|
|
65
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.template"
|
|
66
|
+
)
|
|
61
67
|
POWERPOINT = "application/vnd.ms-powerpoint"
|
|
62
|
-
POWERPOINT_PRESENTATION =
|
|
63
|
-
|
|
68
|
+
POWERPOINT_PRESENTATION = (
|
|
69
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
70
|
+
)
|
|
71
|
+
POWERPOINT_TEMPLATE = (
|
|
72
|
+
"application/vnd.openxmlformats-officedocument.presentationml.template"
|
|
73
|
+
)
|
|
64
74
|
|
|
65
75
|
|
|
66
76
|
class ImageExtension(StrEnum):
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import ClassVar, override
|
|
3
3
|
|
|
4
|
-
from notionary.exceptions.file_upload import
|
|
4
|
+
from notionary.exceptions.file_upload import (
|
|
5
|
+
NoFileExtensionException,
|
|
6
|
+
UnsupportedFileTypeException,
|
|
7
|
+
)
|
|
5
8
|
from notionary.file_upload.validation.models import (
|
|
6
9
|
AudioExtension,
|
|
7
10
|
AudioMimeType,
|
|
@@ -94,7 +97,9 @@ class FileExtensionValidator(FileUploadValidator):
|
|
|
94
97
|
|
|
95
98
|
if not self._is_supported(extension):
|
|
96
99
|
supported_by_category = self._get_supported_extensions_by_category()
|
|
97
|
-
raise UnsupportedFileTypeException(
|
|
100
|
+
raise UnsupportedFileTypeException(
|
|
101
|
+
extension, self.filename, supported_by_category
|
|
102
|
+
)
|
|
98
103
|
|
|
99
104
|
@staticmethod
|
|
100
105
|
def _extract_extension(filename: str) -> str:
|
|
@@ -117,6 +122,10 @@ class FileExtensionValidator(FileUploadValidator):
|
|
|
117
122
|
def _get_supported_extensions_by_category(self) -> dict[str, list[str]]:
|
|
118
123
|
result = {}
|
|
119
124
|
for category in FileCategory:
|
|
120
|
-
extensions = [
|
|
125
|
+
extensions = [
|
|
126
|
+
ext
|
|
127
|
+
for ext, cat in self.EXTENSION_TO_CATEGORY.items()
|
|
128
|
+
if cat == category
|
|
129
|
+
]
|
|
121
130
|
result[category.value] = extensions
|
|
122
131
|
return result
|
notionary/http/client.py
CHANGED
|
@@ -50,13 +50,17 @@ class NotionHttpClient(LoggingMixin):
|
|
|
50
50
|
try:
|
|
51
51
|
loop = asyncio.get_event_loop()
|
|
52
52
|
if not loop.is_running():
|
|
53
|
-
self.logger.warning(
|
|
53
|
+
self.logger.warning(
|
|
54
|
+
"Event loop not running, could not auto-close NotionHttpClient"
|
|
55
|
+
)
|
|
54
56
|
return
|
|
55
57
|
|
|
56
58
|
loop.create_task(self.close())
|
|
57
59
|
self.logger.debug("Created cleanup task for NotionHttpClient")
|
|
58
60
|
except RuntimeError:
|
|
59
|
-
self.logger.warning(
|
|
61
|
+
self.logger.warning(
|
|
62
|
+
"No event loop available for auto-closing NotionHttpClient"
|
|
63
|
+
)
|
|
60
64
|
|
|
61
65
|
async def __aenter__(self):
|
|
62
66
|
await self._ensure_initialized()
|
|
@@ -74,13 +78,19 @@ class NotionHttpClient(LoggingMixin):
|
|
|
74
78
|
self._is_initialized = False
|
|
75
79
|
self.logger.debug("NotionHttpClient closed")
|
|
76
80
|
|
|
77
|
-
async def get(
|
|
81
|
+
async def get(
|
|
82
|
+
self, endpoint: str, params: JsonDict | None = None
|
|
83
|
+
) -> JsonDict | None:
|
|
78
84
|
return await self._make_request(HttpMethod.GET, endpoint, params=params)
|
|
79
85
|
|
|
80
|
-
async def post(
|
|
86
|
+
async def post(
|
|
87
|
+
self, endpoint: str, data: JsonDict | None = None
|
|
88
|
+
) -> JsonDict | None:
|
|
81
89
|
return await self._make_request(HttpMethod.POST, endpoint, data)
|
|
82
90
|
|
|
83
|
-
async def patch(
|
|
91
|
+
async def patch(
|
|
92
|
+
self, endpoint: str, data: JsonDict | None = None
|
|
93
|
+
) -> JsonDict | None:
|
|
84
94
|
return await self._make_request(HttpMethod.PATCH, endpoint, data)
|
|
85
95
|
|
|
86
96
|
async def delete(self, endpoint: str) -> JsonDict | None:
|
|
@@ -105,10 +115,15 @@ class NotionHttpClient(LoggingMixin):
|
|
|
105
115
|
if params:
|
|
106
116
|
request_kwargs["params"] = params
|
|
107
117
|
|
|
108
|
-
if
|
|
118
|
+
if (
|
|
119
|
+
method.value in [HttpMethod.POST.value, HttpMethod.PATCH.value]
|
|
120
|
+
and data is not None
|
|
121
|
+
):
|
|
109
122
|
request_kwargs["json"] = data
|
|
110
123
|
|
|
111
|
-
response: httpx.Response = await getattr(self.client, method.value)(
|
|
124
|
+
response: httpx.Response = await getattr(self.client, method.value)(
|
|
125
|
+
url, **request_kwargs
|
|
126
|
+
)
|
|
112
127
|
|
|
113
128
|
response.raise_for_status()
|
|
114
129
|
result_data = response.json()
|
|
@@ -173,7 +188,11 @@ class NotionHttpClient(LoggingMixin):
|
|
|
173
188
|
|
|
174
189
|
def _find_token(self) -> str | None:
|
|
175
190
|
token = next(
|
|
176
|
-
(
|
|
191
|
+
(
|
|
192
|
+
os.getenv(var)
|
|
193
|
+
for var in ("NOTION_SECRET", "NOTION_INTEGRATION_KEY", "NOTION_TOKEN")
|
|
194
|
+
if os.getenv(var)
|
|
195
|
+
),
|
|
177
196
|
None,
|
|
178
197
|
)
|
|
179
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
|