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.
Files changed (80) hide show
  1. notionary/__init__.py +6 -1
  2. notionary/blocks/enums.py +0 -6
  3. notionary/blocks/schemas.py +32 -78
  4. notionary/comments/schemas.py +2 -29
  5. notionary/data_source/properties/schemas.py +128 -107
  6. notionary/data_source/schemas.py +2 -2
  7. notionary/data_source/service.py +32 -23
  8. notionary/database/schemas.py +2 -2
  9. notionary/database/service.py +3 -5
  10. notionary/exceptions/__init__.py +6 -2
  11. notionary/exceptions/api.py +2 -2
  12. notionary/exceptions/base.py +1 -1
  13. notionary/exceptions/block_parsing.py +3 -3
  14. notionary/exceptions/data_source/builder.py +2 -2
  15. notionary/exceptions/data_source/properties.py +3 -3
  16. notionary/exceptions/file_upload.py +67 -0
  17. notionary/exceptions/properties.py +4 -4
  18. notionary/exceptions/search.py +4 -4
  19. notionary/file_upload/__init__.py +4 -0
  20. notionary/file_upload/client.py +124 -210
  21. notionary/file_upload/config/__init__.py +17 -0
  22. notionary/file_upload/config/config.py +32 -0
  23. notionary/file_upload/config/constants.py +16 -0
  24. notionary/file_upload/file/reader.py +28 -0
  25. notionary/file_upload/query/__init__.py +7 -0
  26. notionary/file_upload/query/builder.py +54 -0
  27. notionary/file_upload/query/models.py +37 -0
  28. notionary/file_upload/schemas.py +78 -0
  29. notionary/file_upload/service.py +152 -289
  30. notionary/file_upload/validation/factory.py +64 -0
  31. notionary/file_upload/validation/impl/file_name_length.py +23 -0
  32. notionary/file_upload/validation/models.py +124 -0
  33. notionary/file_upload/validation/port.py +7 -0
  34. notionary/file_upload/validation/service.py +17 -0
  35. notionary/file_upload/validation/validators/__init__.py +11 -0
  36. notionary/file_upload/validation/validators/file_exists.py +15 -0
  37. notionary/file_upload/validation/validators/file_extension.py +122 -0
  38. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  39. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  40. notionary/http/client.py +6 -22
  41. notionary/page/content/parser/factory.py +8 -5
  42. notionary/page/content/parser/parsers/audio.py +8 -33
  43. notionary/page/content/parser/parsers/embed.py +0 -2
  44. notionary/page/content/parser/parsers/file.py +8 -35
  45. notionary/page/content/parser/parsers/file_like_block.py +89 -0
  46. notionary/page/content/parser/parsers/image.py +8 -35
  47. notionary/page/content/parser/parsers/pdf.py +8 -35
  48. notionary/page/content/parser/parsers/video.py +8 -35
  49. notionary/page/content/renderer/renderers/audio.py +9 -21
  50. notionary/page/content/renderer/renderers/file.py +9 -21
  51. notionary/page/content/renderer/renderers/file_like_block.py +43 -0
  52. notionary/page/content/renderer/renderers/image.py +9 -21
  53. notionary/page/content/renderer/renderers/pdf.py +9 -21
  54. notionary/page/content/renderer/renderers/video.py +9 -21
  55. notionary/page/content/syntax/__init__.py +2 -1
  56. notionary/page/content/syntax/registry.py +38 -60
  57. notionary/page/properties/client.py +1 -1
  58. notionary/page/properties/{models.py → schemas.py} +93 -107
  59. notionary/page/properties/service.py +1 -1
  60. notionary/page/schemas.py +3 -3
  61. notionary/page/service.py +1 -1
  62. notionary/shared/entity/dto_parsers.py +1 -36
  63. notionary/shared/entity/entity_metadata_update_client.py +18 -4
  64. notionary/shared/entity/schemas.py +6 -6
  65. notionary/shared/entity/service.py +53 -30
  66. notionary/shared/models/file.py +34 -6
  67. notionary/shared/models/icon.py +5 -12
  68. notionary/user/bot.py +12 -12
  69. notionary/utils/decorators.py +8 -8
  70. notionary/workspace/__init__.py +2 -2
  71. notionary/workspace/query/__init__.py +2 -1
  72. notionary/workspace/query/service.py +3 -17
  73. notionary/workspace/service.py +45 -45
  74. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/METADATA +1 -1
  75. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/RECORD +77 -58
  76. notionary/file_upload/models.py +0 -69
  77. notionary/page/page_context.py +0 -50
  78. notionary/shared/models/cover.py +0 -20
  79. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
  80. {notionary-0.3.1.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  from pydantic import BaseModel
2
2
 
3
3
  from notionary.blocks.rich_text.models import RichText
4
- from notionary.data_source.properties.schemas import DiscriminatedDataSourceProperty
4
+ from notionary.data_source.properties.schemas import AnyDataSourceProperty
5
5
  from notionary.page.schemas import NotionPageDto
6
6
  from notionary.shared.entity.schemas import EntityResponseDto, NotionEntityUpdateDto
7
7
  from notionary.shared.models.parent import Parent
@@ -24,4 +24,4 @@ class DataSourceDto(EntityResponseDto):
24
24
  title: list[RichText]
25
25
  description: list[RichText]
26
26
  archived: bool
27
- properties: dict[str, DiscriminatedDataSourceProperty]
27
+ properties: dict[str, AnyDataSourceProperty]
@@ -20,7 +20,8 @@ from notionary.data_source.query import DataSourceQueryBuilder, DataSourceQueryP
20
20
  from notionary.data_source.schema.service import DataSourcePropertySchemaFormatter
21
21
  from notionary.data_source.schemas import DataSourceDto
22
22
  from notionary.exceptions.data_source.properties import DataSourcePropertyNotFound, DataSourcePropertyTypeError
23
- from notionary.page.properties.models import PageTitleProperty
23
+ from notionary.file_upload.service import NotionFileUpload
24
+ from notionary.page.properties.schemas import PageTitleProperty
24
25
  from notionary.page.schemas import NotionPageDto
25
26
  from notionary.shared.entity.dto_parsers import (
26
27
  extract_description,
@@ -28,6 +29,7 @@ from notionary.shared.entity.dto_parsers import (
28
29
  )
29
30
  from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
30
31
  from notionary.shared.entity.service import Entity
32
+ from notionary.user.service import UserService
31
33
  from notionary.workspace.query.service import WorkspaceQueryService
32
34
 
33
35
  if TYPE_CHECKING:
@@ -43,8 +45,10 @@ class NotionDataSource(Entity):
43
45
  properties: dict[str, DataSourceProperty],
44
46
  data_source_instance_client: DataSourceInstanceClient,
45
47
  query_resolver: QueryResolver | None = None,
48
+ user_service: UserService | None = None,
49
+ file_upload_service: NotionFileUpload | None = None,
46
50
  ) -> None:
47
- super().__init__(dto=dto)
51
+ super().__init__(dto=dto, user_service=user_service, file_upload_service=file_upload_service)
48
52
 
49
53
  self._parent_database: NotionDatabase | None = None
50
54
  self._title = title
@@ -116,6 +120,10 @@ class NotionDataSource(Entity):
116
120
  def properties(self) -> dict[str, DataSourceProperty]:
117
121
  return self._properties
118
122
 
123
+ @property
124
+ def data_source_query_builder(self) -> DataSourceQueryBuilder:
125
+ return DataSourceQueryBuilder(properties=self._properties)
126
+
119
127
  async def create_blank_page(self, title: str | None = None) -> NotionPage:
120
128
  return await self._data_source_client.create_blank_page(title=title)
121
129
 
@@ -222,44 +230,45 @@ class NotionDataSource(Entity):
222
230
 
223
231
  return prop
224
232
 
225
- def filter(self) -> DataSourceQueryBuilder:
233
+ def get_query_builder(self) -> DataSourceQueryBuilder:
226
234
  return DataSourceQueryBuilder(properties=self._properties)
227
235
 
228
- async def query_pages(
229
- self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
230
- ) -> list[NotionPage]:
231
- builder = DataSourceQueryBuilder(properties=self._properties)
232
- configured_builder = filter_fn(builder)
233
- query_params = configured_builder.build()
234
-
235
- return await self.get_pages(query_params)
236
-
237
- async def query_pages_stream(
238
- self, filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder]
239
- ) -> AsyncIterator[NotionPage]:
240
- builder = DataSourceQueryBuilder(properties=self._properties)
241
- configured_builder = filter_fn(builder)
242
- query_params = configured_builder.build()
243
-
244
- async for page in self.get_pages_stream(query_params):
245
- yield page
246
-
247
236
  async def get_pages(
248
237
  self,
238
+ *,
239
+ filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder] | None = None,
249
240
  query_params: DataSourceQueryParams | None = None,
250
241
  ) -> list[NotionPage]:
251
242
  from notionary import NotionPage
252
243
 
244
+ if filter_fn is not None and query_params is not None:
245
+ raise ValueError("Use either filter_fn OR query_params, not both")
246
+
247
+ if filter_fn is not None:
248
+ builder = DataSourceQueryBuilder(properties=self._properties)
249
+ configured_builder = filter_fn(builder)
250
+ query_params = configured_builder.build()
251
+
253
252
  resolved_params = await self._resolve_query_params_if_needed(query_params)
254
253
  query_response = await self._data_source_client.query(query_params=resolved_params)
255
254
  return [await NotionPage.from_id(page.id) for page in query_response.results]
256
255
 
257
- async def get_pages_stream(
256
+ async def iter_pages(
258
257
  self,
258
+ *,
259
+ filter_fn: Callable[[DataSourceQueryBuilder], DataSourceQueryBuilder] | None = None,
259
260
  query_params: DataSourceQueryParams | None = None,
260
261
  ) -> AsyncIterator[NotionPage]:
261
262
  from notionary import NotionPage
262
263
 
264
+ if filter_fn is not None and query_params is not None:
265
+ raise ValueError("Use either filter_fn OR query_params, not both")
266
+
267
+ if filter_fn is not None:
268
+ builder = DataSourceQueryBuilder(properties=self._properties)
269
+ configured_builder = filter_fn(builder)
270
+ query_params = configured_builder.build()
271
+
263
272
  resolved_params = await self._resolve_query_params_if_needed(query_params)
264
273
 
265
274
  async for page in self._data_source_client.query_stream(query_params=resolved_params):
@@ -2,7 +2,7 @@ from pydantic import BaseModel, Field
2
2
 
3
3
  from notionary.blocks.rich_text.models import RichText
4
4
  from notionary.shared.entity.schemas import EntityResponseDto
5
- from notionary.shared.models.cover import NotionCover
5
+ from notionary.shared.models.file import File
6
6
  from notionary.shared.models.icon import Icon
7
7
 
8
8
 
@@ -24,6 +24,6 @@ class NotionDatabaseDto(EntityResponseDto):
24
24
  class NotionDatabaseUpdateDto(BaseModel):
25
25
  title: list[RichText] | None = None
26
26
  icon: Icon | None = None
27
- cover: NotionCover | None = None
27
+ cover: File | None = None
28
28
  archived: bool | None = None
29
29
  description: list[RichText] | None = None
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  import asyncio
4
2
  from collections.abc import Awaitable, Callable
5
3
  from typing import Self
@@ -16,7 +14,7 @@ from notionary.shared.entity.dto_parsers import (
16
14
  from notionary.shared.entity.service import Entity
17
15
  from notionary.workspace.query.service import WorkspaceQueryService
18
16
 
19
- type DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
17
+ type _DataSourceFactory = Callable[[str], Awaitable[NotionDataSource]]
20
18
 
21
19
 
22
20
  class NotionDatabase(Entity):
@@ -104,7 +102,7 @@ class NotionDatabase(Entity):
104
102
 
105
103
  async def get_data_sources(
106
104
  self,
107
- data_source_factory: DataSourceFactory = NotionDataSource.from_id,
105
+ data_source_factory: _DataSourceFactory = NotionDataSource.from_id,
108
106
  ) -> list[NotionDataSource]:
109
107
  if self._data_sources is None:
110
108
  self._data_sources = await self._load_data_sources(data_source_factory)
@@ -112,7 +110,7 @@ class NotionDatabase(Entity):
112
110
 
113
111
  async def _load_data_sources(
114
112
  self,
115
- data_source_factory: DataSourceFactory,
113
+ data_source_factory: _DataSourceFactory,
116
114
  ) -> list[NotionDataSource]:
117
115
  tasks = [data_source_factory(ds_id) for ds_id in self._data_source_ids]
118
116
  return list(await asyncio.gather(*tasks))
@@ -7,9 +7,10 @@ from .api import (
7
7
  NotionServerError,
8
8
  NotionValidationError,
9
9
  )
10
- from .base import NotionaryError
10
+ from .base import NotionaryException
11
11
  from .block_parsing import InsufficientColumnsError, InvalidColumnRatioSumError, UnsupportedVideoFormatError
12
12
  from .data_source import DataSourcePropertyNotFound, DataSourcePropertyTypeError
13
+ from .file_upload import FileSizeException, NoFileExtensionException, UnsupportedFileTypeException
13
14
  from .properties import AccessPagePropertyWithoutDataSourceError, PagePropertyNotFoundError, PagePropertyTypeError
14
15
  from .search import DatabaseNotFound, DataSourceNotFound, EntityNotFound, PageNotFound
15
16
 
@@ -20,8 +21,10 @@ __all__ = [
20
21
  "DataSourcePropertyTypeError",
21
22
  "DatabaseNotFound",
22
23
  "EntityNotFound",
24
+ "FileSizeException",
23
25
  "InsufficientColumnsError",
24
26
  "InvalidColumnRatioSumError",
27
+ "NoFileExtensionException",
25
28
  "NotionApiError",
26
29
  "NotionAuthenticationError",
27
30
  "NotionConnectionError",
@@ -29,9 +32,10 @@ __all__ = [
29
32
  "NotionResourceNotFoundError",
30
33
  "NotionServerError",
31
34
  "NotionValidationError",
32
- "NotionaryError",
35
+ "NotionaryException",
33
36
  "PageNotFound",
34
37
  "PagePropertyNotFoundError",
35
38
  "PagePropertyTypeError",
39
+ "UnsupportedFileTypeException",
36
40
  "UnsupportedVideoFormatError",
37
41
  ]
@@ -1,7 +1,7 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
- class NotionApiError(NotionaryError):
4
+ class NotionApiError(NotionaryException):
5
5
  def __init__(
6
6
  self,
7
7
  message: str,
@@ -1,2 +1,2 @@
1
- class NotionaryError(Exception):
1
+ class NotionaryException(Exception):
2
2
  pass
@@ -1,15 +1,15 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
  RATIO_TOLERANCE = 0.0001
4
4
 
5
5
 
6
- class InsufficientColumnsError(NotionaryError):
6
+ class InsufficientColumnsError(NotionaryException):
7
7
  def __init__(self, column_count: int) -> None:
8
8
  self.column_count = column_count
9
9
  super().__init__(f"Columns container must contain at least 2 column blocks, but only {column_count} found")
10
10
 
11
11
 
12
- class InvalidColumnRatioSumError(NotionaryError):
12
+ class InvalidColumnRatioSumError(NotionaryException):
13
13
  def __init__(self, total: float, tolerance: float = RATIO_TOLERANCE) -> None:
14
14
  self.total = total
15
15
  self.tolerance = tolerance
@@ -8,11 +8,11 @@ from notionary.data_source.query.schema import (
8
8
  Operator,
9
9
  StringOperator,
10
10
  )
11
- from notionary.exceptions.base import NotionaryError
11
+ from notionary.exceptions.base import NotionaryException
12
12
  from notionary.shared.properties.type import PropertyType
13
13
 
14
14
 
15
- class QueryBuilderError(NotionaryError):
15
+ class QueryBuilderError(NotionaryException):
16
16
  def __init__(self, message: str, property_name: str | None = None) -> None:
17
17
  self.property_name = property_name
18
18
  super().__init__(message)
@@ -1,9 +1,9 @@
1
1
  import difflib
2
2
 
3
- from notionary.exceptions.base import NotionaryError
3
+ from notionary.exceptions.base import NotionaryException
4
4
 
5
5
 
6
- class DataSourcePropertyNotFound(NotionaryError):
6
+ class DataSourcePropertyNotFound(NotionaryException):
7
7
  def __init__(
8
8
  self,
9
9
  property_name: str,
@@ -28,7 +28,7 @@ class DataSourcePropertyNotFound(NotionaryError):
28
28
  super().__init__(message)
29
29
 
30
30
 
31
- class DataSourcePropertyTypeError(NotionaryError):
31
+ class DataSourcePropertyTypeError(NotionaryException):
32
32
  def __init__(self, property_name: str, expected_type: str, actual_type: str) -> None:
33
33
  message = f"Property '{property_name}' has the wrong type. Expected: '{expected_type}', found: '{actual_type}'."
34
34
  super().__init__(message)
@@ -0,0 +1,67 @@
1
+ from notionary.exceptions.base import NotionaryException
2
+
3
+
4
+ class UnsupportedFileTypeException(NotionaryException):
5
+ def __init__(self, extension: str, filename: str, supported_extensions_by_category: dict[str, list[str]]):
6
+ supported_exts = []
7
+ for category, extensions in supported_extensions_by_category.items():
8
+ supported_exts.append(f"{category}: {', '.join(extensions[:5])}...")
9
+
10
+ supported_info = "\n ".join(supported_exts)
11
+ super().__init__(
12
+ f"File '{filename}' has unsupported extension '{extension}'.\n"
13
+ f"Supported file types by category:\n {supported_info}"
14
+ )
15
+ self.extension = extension
16
+ self.filename = filename
17
+
18
+
19
+ class NoFileExtensionException(NotionaryException):
20
+ def __init__(self, filename: str):
21
+ super().__init__(
22
+ f"File '{filename}' has no extension. Files must have a valid extension to determine their type."
23
+ )
24
+ self.filename = filename
25
+
26
+
27
+ class FileSizeException(NotionaryException):
28
+ def __init__(self, filename: str, file_size_bytes: int, max_size_bytes: int):
29
+ file_size_mb = file_size_bytes / (1024 * 1024)
30
+ max_size_mb = max_size_bytes / (1024 * 1024)
31
+ super().__init__(
32
+ f"File '{filename}' is too large ({file_size_mb:.2f} MB). Maximum allowed size is {max_size_mb:.2f} MB."
33
+ )
34
+ self.filename = filename
35
+ self.file_size_bytes = file_size_bytes
36
+ self.max_size_bytes = max_size_bytes
37
+
38
+
39
+ class FileNotFoundError(NotionaryException):
40
+ def __init__(self, file_path: str):
41
+ super().__init__(f"File does not exist: {file_path}")
42
+ self.file_path = file_path
43
+
44
+
45
+ class FilenameTooLongError(NotionaryException):
46
+ def __init__(self, filename: str, filename_bytes: int, max_filename_bytes: int):
47
+ super().__init__(f"Filename too long: {filename_bytes} bytes (max {max_filename_bytes}). Filename: {filename}")
48
+ self.filename = filename
49
+ self.filename_bytes = filename_bytes
50
+ self.max_filename_bytes = max_filename_bytes
51
+
52
+
53
+ class UploadFailedError(NotionaryException):
54
+ def __init__(self, file_upload_id: str, reason: str | None = None):
55
+ message = f"Upload failed for file_upload_id: {file_upload_id}"
56
+ if reason:
57
+ message += f". Reason: {reason}"
58
+ super().__init__(message)
59
+ self.file_upload_id = file_upload_id
60
+ self.reason = reason
61
+
62
+
63
+ class UploadTimeoutError(NotionaryException):
64
+ def __init__(self, file_upload_id: str, timeout_seconds: int):
65
+ super().__init__(f"Upload timeout after {timeout_seconds}s for file_upload_id: {file_upload_id}")
66
+ self.file_upload_id = file_upload_id
67
+ self.timeout_seconds = timeout_seconds
@@ -1,11 +1,11 @@
1
1
  import difflib
2
2
  from typing import ClassVar
3
3
 
4
- from notionary.exceptions.base import NotionaryError
4
+ from notionary.exceptions.base import NotionaryException
5
5
  from notionary.shared.models.parent import ParentType
6
6
 
7
7
 
8
- class PagePropertyNotFoundError(NotionaryError):
8
+ class PagePropertyNotFoundError(NotionaryException):
9
9
  def __init__(
10
10
  self,
11
11
  page_url: str,
@@ -31,7 +31,7 @@ class PagePropertyNotFoundError(NotionaryError):
31
31
  super().__init__(message)
32
32
 
33
33
 
34
- class PagePropertyTypeError(NotionaryError):
34
+ class PagePropertyTypeError(NotionaryException):
35
35
  def __init__(
36
36
  self,
37
37
  property_name: str,
@@ -41,7 +41,7 @@ class PagePropertyTypeError(NotionaryError):
41
41
  super().__init__(message)
42
42
 
43
43
 
44
- class AccessPagePropertyWithoutDataSourceError(NotionaryError):
44
+ class AccessPagePropertyWithoutDataSourceError(NotionaryException):
45
45
  _PARENT_DESCRIPTIONS: ClassVar[dict[ParentType, str]] = {
46
46
  ParentType.WORKSPACE: "the workspace itself",
47
47
  ParentType.PAGE_ID: "another page",
@@ -1,7 +1,7 @@
1
- from notionary.exceptions.base import NotionaryError
1
+ from notionary.exceptions.base import NotionaryException
2
2
 
3
3
 
4
- class EntityNotFound(NotionaryError):
4
+ class EntityNotFound(NotionaryException):
5
5
  def __init__(self, entity_type: str, query: str, available_titles: list[str] | None = None) -> None:
6
6
  self.entity_type = entity_type
7
7
  self.query = query
@@ -33,14 +33,14 @@ class DatabaseNotFound(EntityNotFound):
33
33
  super().__init__("database", query, available_titles)
34
34
 
35
35
 
36
- class NoUsersInWorkspace(NotionaryError):
36
+ class NoUsersInWorkspace(NotionaryException):
37
37
  def __init__(self, user_type: str) -> None:
38
38
  self.user_type = user_type
39
39
  message = f"No '{user_type}' users found in the workspace."
40
40
  super().__init__(message)
41
41
 
42
42
 
43
- class UserNotFound(NotionaryError):
43
+ class UserNotFound(NotionaryException):
44
44
  def __init__(self, user_type: str, query: str, available_names: list[str] | None = None) -> None:
45
45
  self.user_type = user_type
46
46
  self.query = query
@@ -0,0 +1,4 @@
1
+ from .query import FileUploadQuery, FileUploadQueryBuilder
2
+ from .service import NotionFileUpload
3
+
4
+ __all__ = ["FileUploadQuery", "FileUploadQueryBuilder", "NotionFileUpload"]