notionary 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. notionary/__init__.py +14 -2
  2. notionary/blocks/enums.py +27 -6
  3. notionary/blocks/schemas.py +32 -78
  4. notionary/comments/client.py +6 -9
  5. notionary/comments/schemas.py +2 -29
  6. notionary/data_source/http/data_source_instance_client.py +4 -4
  7. notionary/data_source/properties/schemas.py +128 -107
  8. notionary/data_source/query/__init__.py +9 -0
  9. notionary/data_source/query/builder.py +12 -3
  10. notionary/data_source/query/schema.py +5 -0
  11. notionary/data_source/schemas.py +2 -2
  12. notionary/data_source/service.py +43 -132
  13. notionary/database/schemas.py +2 -2
  14. notionary/database/service.py +19 -63
  15. notionary/exceptions/__init__.py +10 -2
  16. notionary/exceptions/api.py +2 -2
  17. notionary/exceptions/base.py +1 -1
  18. notionary/exceptions/block_parsing.py +24 -3
  19. notionary/exceptions/data_source/builder.py +2 -2
  20. notionary/exceptions/data_source/properties.py +3 -3
  21. notionary/exceptions/file_upload.py +67 -0
  22. notionary/exceptions/properties.py +4 -4
  23. notionary/exceptions/search.py +4 -4
  24. notionary/file_upload/__init__.py +4 -0
  25. notionary/file_upload/client.py +124 -210
  26. notionary/file_upload/config/__init__.py +17 -0
  27. notionary/file_upload/config/config.py +32 -0
  28. notionary/file_upload/config/constants.py +16 -0
  29. notionary/file_upload/file/reader.py +28 -0
  30. notionary/file_upload/query/__init__.py +7 -0
  31. notionary/file_upload/query/builder.py +54 -0
  32. notionary/file_upload/query/models.py +37 -0
  33. notionary/file_upload/schemas.py +78 -0
  34. notionary/file_upload/service.py +152 -289
  35. notionary/file_upload/validation/factory.py +64 -0
  36. notionary/file_upload/validation/impl/file_name_length.py +23 -0
  37. notionary/file_upload/validation/models.py +124 -0
  38. notionary/file_upload/validation/port.py +7 -0
  39. notionary/file_upload/validation/service.py +17 -0
  40. notionary/file_upload/validation/validators/__init__.py +11 -0
  41. notionary/file_upload/validation/validators/file_exists.py +15 -0
  42. notionary/file_upload/validation/validators/file_extension.py +122 -0
  43. notionary/file_upload/validation/validators/file_name_length.py +21 -0
  44. notionary/file_upload/validation/validators/upload_limit.py +31 -0
  45. notionary/http/client.py +7 -23
  46. notionary/page/content/factory.py +2 -0
  47. notionary/page/content/parser/factory.py +8 -5
  48. notionary/page/content/parser/parsers/audio.py +8 -33
  49. notionary/page/content/parser/parsers/embed.py +0 -2
  50. notionary/page/content/parser/parsers/file.py +8 -35
  51. notionary/page/content/parser/parsers/file_like_block.py +89 -0
  52. notionary/page/content/parser/parsers/image.py +8 -35
  53. notionary/page/content/parser/parsers/pdf.py +8 -35
  54. notionary/page/content/parser/parsers/video.py +8 -35
  55. notionary/page/content/parser/pre_processsing/handlers/__init__.py +2 -0
  56. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +12 -8
  57. notionary/page/content/parser/pre_processsing/handlers/indentation.py +2 -0
  58. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
  59. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +2 -0
  60. notionary/page/content/renderer/renderers/audio.py +9 -21
  61. notionary/page/content/renderer/renderers/file.py +9 -21
  62. notionary/page/content/renderer/renderers/file_like_block.py +43 -0
  63. notionary/page/content/renderer/renderers/image.py +9 -21
  64. notionary/page/content/renderer/renderers/pdf.py +9 -21
  65. notionary/page/content/renderer/renderers/video.py +9 -21
  66. notionary/page/content/syntax/__init__.py +2 -1
  67. notionary/page/content/syntax/registry.py +38 -60
  68. notionary/page/properties/client.py +3 -3
  69. notionary/page/properties/{models.py → schemas.py} +93 -107
  70. notionary/page/properties/service.py +15 -4
  71. notionary/page/schemas.py +3 -3
  72. notionary/page/service.py +18 -79
  73. notionary/shared/entity/dto_parsers.py +1 -36
  74. notionary/shared/entity/entity_metadata_update_client.py +18 -4
  75. notionary/shared/entity/schemas.py +6 -6
  76. notionary/shared/entity/service.py +121 -40
  77. notionary/shared/models/file.py +34 -6
  78. notionary/shared/models/icon.py +5 -12
  79. notionary/user/bot.py +12 -12
  80. notionary/utils/decorators.py +8 -8
  81. notionary/utils/pagination.py +36 -32
  82. notionary/workspace/__init__.py +2 -2
  83. notionary/workspace/client.py +2 -0
  84. notionary/workspace/query/__init__.py +3 -2
  85. notionary/workspace/query/builder.py +25 -1
  86. notionary/workspace/query/models.py +9 -1
  87. notionary/workspace/query/service.py +15 -11
  88. notionary/workspace/service.py +46 -36
  89. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/METADATA +9 -5
  90. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/RECORD +92 -71
  91. notionary/file_upload/models.py +0 -69
  92. notionary/page/page_context.py +0 -50
  93. notionary/shared/models/cover.py +0 -20
  94. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/WHEEL +0 -0
  95. {notionary-0.3.0.dist-info → notionary-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,89 +12,93 @@ class PaginatedResponse(BaseModel):
12
12
 
13
13
  async def _fetch_data(
14
14
  api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
15
- page_size: int | None = None,
15
+ total_results_limit: int | None = None,
16
16
  **kwargs,
17
17
  ) -> AsyncGenerator[PaginatedResponse]:
18
- next_cursor = None
19
- has_more = True
20
- total_fetched = 0
18
+ next_cursor: str | None = None
19
+ has_more: bool = True
20
+ total_fetched: int = 0
21
+ api_page_size: int = kwargs.get("page_size", 100)
21
22
 
22
- while has_more and _should_continue_fetching(page_size, total_fetched):
23
- request_params = _build_request_params(kwargs, next_cursor, page_size)
23
+ while has_more and _should_continue_fetching(total_results_limit, total_fetched):
24
+ request_params = _build_request_params(kwargs, next_cursor)
24
25
  response = await api_call(**request_params)
25
26
 
26
- limited_results = _apply_result_limit(response.results, page_size, total_fetched)
27
+ limited_results = _apply_result_limit(response.results, total_results_limit, total_fetched)
27
28
  total_fetched += len(limited_results)
28
29
 
29
- yield _create_limited_response(response, limited_results)
30
+ yield _create_limited_response(response, limited_results, api_page_size)
30
31
 
31
- if _has_reached_limit(page_size, total_fetched):
32
+ if _has_reached_limit(total_results_limit, total_fetched):
32
33
  break
33
34
 
34
35
  has_more = response.has_more
35
36
  next_cursor = response.next_cursor
36
37
 
37
38
 
38
- def _should_continue_fetching(page_size: int | None, total_fetched: int) -> bool:
39
- if page_size is None:
39
+ def _should_continue_fetching(total_limit: int | None, total_fetched: int) -> bool:
40
+ if total_limit is None:
40
41
  return True
41
- return total_fetched < page_size
42
+ return total_fetched < total_limit
42
43
 
43
44
 
44
45
  def _build_request_params(
45
46
  base_kwargs: dict[str, Any],
46
47
  cursor: str | None,
47
- page_size: int | None,
48
48
  ) -> dict[str, Any]:
49
49
  params = base_kwargs.copy()
50
-
51
50
  if cursor:
52
51
  params["start_cursor"] = cursor
53
-
54
- if page_size:
55
- params["page_size"] = page_size
56
-
57
52
  return params
58
53
 
59
54
 
60
- def _apply_result_limit(results: list[Any], page_size: int | None, total_fetched: int) -> list[Any]:
61
- if page_size is None:
55
+ def _apply_result_limit(results: list[Any], total_limit: int | None, total_fetched: int) -> list[Any]:
56
+ if total_limit is None:
62
57
  return results
63
58
 
64
- remaining = page_size - total_fetched
65
- return results[:remaining]
59
+ remaining_space = total_limit - total_fetched
60
+ return results[:remaining_space]
66
61
 
67
62
 
68
- def _has_reached_limit(page_size: int | None, total_fetched: int) -> bool:
69
- if page_size is None:
63
+ def _has_reached_limit(total_limit: int | None, total_fetched: int) -> bool:
64
+ if total_limit is None:
70
65
  return False
71
- return total_fetched >= page_size
66
+ return total_fetched >= total_limit
67
+
68
+
69
+ def _create_limited_response(
70
+ original: PaginatedResponse,
71
+ limited_results: list[Any],
72
+ api_page_size: int,
73
+ ) -> PaginatedResponse:
74
+ results_were_limited_by_client = len(limited_results) < len(original.results)
75
+ api_returned_full_page = len(original.results) == api_page_size
72
76
 
77
+ has_more_after_limit = original.has_more and not results_were_limited_by_client and api_returned_full_page
73
78
 
74
- def _create_limited_response(original: PaginatedResponse, limited_results: list[Any]) -> PaginatedResponse:
75
79
  return PaginatedResponse(
76
80
  results=limited_results,
77
- has_more=original.has_more and len(limited_results) == len(original.results),
78
- next_cursor=original.next_cursor,
81
+ has_more=has_more_after_limit,
82
+ next_cursor=original.next_cursor if has_more_after_limit else None,
79
83
  )
80
84
 
81
85
 
82
86
  async def paginate_notion_api(
83
87
  api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
84
- page_size: int | None = None,
88
+ total_results_limit: int | None = None,
85
89
  **kwargs,
86
90
  ) -> list[Any]:
87
91
  all_results = []
88
- async for page in _fetch_data(api_call, page_size=page_size, **kwargs):
92
+ async for page in _fetch_data(api_call, total_results_limit=total_results_limit, **kwargs):
89
93
  all_results.extend(page.results)
90
94
  return all_results
91
95
 
92
96
 
93
97
  async def paginate_notion_api_generator(
94
98
  api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
95
- page_size: int | None = None,
99
+ total_results_limit: int | None = None,
96
100
  **kwargs,
97
101
  ) -> AsyncGenerator[Any]:
98
- async for page in _fetch_data(api_call, page_size=page_size, **kwargs):
102
+ async for page in _fetch_data(api_call, total_results_limit, **kwargs):
99
103
  for item in page.results:
100
104
  yield item
@@ -1,4 +1,4 @@
1
- from .query import WorkspaceQueryConfigBuilder
1
+ from .query import NotionWorkspaceQueryConfigBuilder, WorkspaceQueryConfig
2
2
  from .service import NotionWorkspace
3
3
 
4
- __all__ = ["NotionWorkspace", "WorkspaceQueryConfigBuilder"]
4
+ __all__ = ["NotionWorkspace", "NotionWorkspaceQueryConfigBuilder", "WorkspaceQueryConfig"]
@@ -22,6 +22,7 @@ class WorkspaceClient:
22
22
  async for page in paginate_notion_api_generator(
23
23
  self._query_pages,
24
24
  search_config=search_config,
25
+ total_results_limit=search_config.total_results_limit,
25
26
  ):
26
27
  yield page
27
28
 
@@ -32,6 +33,7 @@ class WorkspaceClient:
32
33
  async for data_source in paginate_notion_api_generator(
33
34
  self._query_data_sources,
34
35
  search_config=search_config,
36
+ total_results_limit=search_config.total_results_limit,
35
37
  ):
36
38
  yield data_source
37
39
 
@@ -1,3 +1,4 @@
1
- from .builder import WorkspaceQueryConfigBuilder
1
+ from .builder import NotionWorkspaceQueryConfigBuilder
2
+ from .models import WorkspaceQueryConfig
2
3
 
3
- __all__ = ["WorkspaceQueryConfigBuilder"]
4
+ __all__ = ["NotionWorkspaceQueryConfigBuilder", "WorkspaceQueryConfig"]
@@ -8,7 +8,7 @@ from notionary.workspace.query.models import (
8
8
  )
9
9
 
10
10
 
11
- class WorkspaceQueryConfigBuilder:
11
+ class NotionWorkspaceQueryConfigBuilder:
12
12
  def __init__(self, config: WorkspaceQueryConfig = None) -> None:
13
13
  self.config = config or WorkspaceQueryConfig()
14
14
 
@@ -16,6 +16,10 @@ class WorkspaceQueryConfigBuilder:
16
16
  self.config.query = query
17
17
  return self
18
18
 
19
+ def with_total_results_limit(self, limit: int) -> Self:
20
+ self.config.total_results_limit = limit
21
+ return self
22
+
19
23
  def with_pages_only(self) -> Self:
20
24
  self.config.object_type = WorkspaceQueryObjectType.PAGE
21
25
  return self
@@ -44,6 +48,26 @@ class WorkspaceQueryConfigBuilder:
44
48
  def with_sort_by_last_edited(self) -> Self:
45
49
  return self.with_sort_timestamp(SortTimestamp.LAST_EDITED_TIME)
46
50
 
51
+ def with_sort_by_created_time_ascending(self) -> Self:
52
+ self.config.sort_timestamp = SortTimestamp.CREATED_TIME
53
+ self.config.sort_direction = SortDirection.ASCENDING
54
+ return self
55
+
56
+ def with_sort_by_created_time_descending(self) -> Self:
57
+ self.config.sort_timestamp = SortTimestamp.CREATED_TIME
58
+ self.config.sort_direction = SortDirection.DESCENDING
59
+ return self
60
+
61
+ def with_sort_by_last_edited_ascending(self) -> Self:
62
+ self.config.sort_timestamp = SortTimestamp.LAST_EDITED_TIME
63
+ self.config.sort_direction = SortDirection.ASCENDING
64
+ return self
65
+
66
+ def with_sort_by_last_edited_descending(self) -> Self:
67
+ self.config.sort_timestamp = SortTimestamp.LAST_EDITED_TIME
68
+ self.config.sort_direction = SortDirection.DESCENDING
69
+ return self
70
+
47
71
  def with_page_size(self, size: int) -> Self:
48
72
  self.config.page_size = min(size, 100)
49
73
  return self
@@ -1,10 +1,15 @@
1
1
  from enum import StrEnum
2
+ from typing import Protocol
2
3
 
3
4
  from pydantic import BaseModel, Field, field_validator, model_serializer
4
5
 
5
6
  from notionary.shared.typings import JsonDict
6
7
 
7
8
 
9
+ class SearchableEntity(Protocol):
10
+ title: str
11
+
12
+
8
13
  class SortDirection(StrEnum):
9
14
  ASCENDING = "ascending"
10
15
  DESCENDING = "descending"
@@ -25,9 +30,12 @@ class WorkspaceQueryConfig(BaseModel):
25
30
  object_type: WorkspaceQueryObjectType | None = None
26
31
  sort_direction: SortDirection = SortDirection.DESCENDING
27
32
  sort_timestamp: SortTimestamp = SortTimestamp.LAST_EDITED_TIME
33
+
28
34
  page_size: int = Field(default=100, ge=1, le=100)
29
35
  start_cursor: str | None = None
30
36
 
37
+ total_results_limit: int | None = None
38
+
31
39
  @field_validator("query")
32
40
  @classmethod
33
41
  def replace_empty_query_with_none(cls, value: str | None) -> str | None:
@@ -36,7 +44,7 @@ class WorkspaceQueryConfig(BaseModel):
36
44
  return value
37
45
 
38
46
  @model_serializer
39
- def serialize_model(self) -> JsonDict:
47
+ def to_api_params(self) -> JsonDict:
40
48
  search_dict: JsonDict = {}
41
49
 
42
50
  if self.query:
@@ -2,22 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import AsyncIterator
5
- from typing import TYPE_CHECKING, Protocol
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from notionary.exceptions.search import DatabaseNotFound, DataSourceNotFound, PageNotFound
8
8
  from notionary.utils.fuzzy import find_all_matches
9
9
  from notionary.workspace.client import WorkspaceClient
10
- from notionary.workspace.query.builder import WorkspaceQueryConfigBuilder
11
- from notionary.workspace.query.models import WorkspaceQueryConfig
10
+ from notionary.workspace.query.builder import NotionWorkspaceQueryConfigBuilder
11
+ from notionary.workspace.query.models import SearchableEntity, WorkspaceQueryConfig
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from notionary import NotionDatabase, NotionDataSource, NotionPage
15
15
 
16
16
 
17
- class SearchableEntity(Protocol):
18
- title: str
19
-
20
-
21
17
  class WorkspaceQueryService:
22
18
  def __init__(self, client: WorkspaceClient | None = None) -> None:
23
19
  self._client = client or WorkspaceClient()
@@ -49,20 +45,28 @@ class WorkspaceQueryService:
49
45
  return await asyncio.gather(*data_source_tasks)
50
46
 
51
47
  async def find_data_source(self, query: str) -> NotionDataSource:
52
- config = WorkspaceQueryConfigBuilder().with_query(query).with_data_sources_only().with_page_size(100).build()
48
+ config = (
49
+ NotionWorkspaceQueryConfigBuilder().with_query(query).with_data_sources_only().with_page_size(100).build()
50
+ )
53
51
  data_sources = await self.get_data_sources(config)
54
52
  return self._find_exact_match(data_sources, query, DataSourceNotFound)
55
53
 
56
54
  async def find_page(self, query: str) -> NotionPage:
57
- config = WorkspaceQueryConfigBuilder().with_query(query).with_pages_only().with_page_size(100).build()
55
+ config = NotionWorkspaceQueryConfigBuilder().with_query(query).with_pages_only().with_page_size(100).build()
58
56
  pages = await self.get_pages(config)
59
57
  return self._find_exact_match(pages, query, PageNotFound)
60
58
 
61
59
  async def find_database(self, query: str) -> NotionDatabase:
62
- config = WorkspaceQueryConfigBuilder().with_query(query).with_data_sources_only().with_page_size(100).build()
60
+ config = (
61
+ NotionWorkspaceQueryConfigBuilder().with_query(query).with_data_sources_only().with_page_size(100).build()
62
+ )
63
63
  data_sources = await self.get_data_sources(config)
64
64
 
65
- parent_database_tasks = [data_source.get_parent_database() for data_source in data_sources]
65
+ parent_database_ids = [data_sources.get_parent_database_id_if_present() for data_sources in data_sources]
66
+ # filter none values which should not happen but for safety
67
+ parent_database_ids = [id for id in parent_database_ids if id is not None]
68
+
69
+ parent_database_tasks = [NotionDatabase.from_id(db_id) for db_id in parent_database_ids]
66
70
  parent_databases = await asyncio.gather(*parent_database_tasks)
67
71
  potential_databases = [database for database in parent_databases if database is not None]
68
72
 
@@ -4,17 +4,14 @@ from collections.abc import AsyncIterator, Callable
4
4
  from typing import TYPE_CHECKING, Self
5
5
 
6
6
  from notionary.user.service import UserService
7
- from notionary.workspace.query.builder import WorkspaceQueryConfigBuilder
7
+ from notionary.workspace.query.builder import NotionWorkspaceQueryConfigBuilder
8
8
  from notionary.workspace.query.models import WorkspaceQueryConfig, WorkspaceQueryObjectType
9
9
  from notionary.workspace.query.service import WorkspaceQueryService
10
10
 
11
11
  if TYPE_CHECKING:
12
- from notionary.data_source.service import NotionDataSource
13
- from notionary.page.service import NotionPage
12
+ from notionary import NotionDataSource, NotionPage
14
13
  from notionary.user import BotUser, PersonUser
15
14
 
16
- type _QueryConfigInput = WorkspaceQueryConfig | Callable[[WorkspaceQueryConfigBuilder], WorkspaceQueryConfigBuilder]
17
-
18
15
 
19
16
  class NotionWorkspace:
20
17
  def __init__(
@@ -41,58 +38,71 @@ class NotionWorkspace:
41
38
 
42
39
  async def get_pages(
43
40
  self,
44
- config: _QueryConfigInput | None = None,
41
+ *,
42
+ filter_fn: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder] | None = None,
43
+ query_config: WorkspaceQueryConfig | None = None,
45
44
  ) -> list[NotionPage]:
46
- query_config = self._resolve_config(config, default_object_type_to_query=WorkspaceQueryObjectType.PAGE)
47
- return await self._query_service.get_pages(query_config)
45
+ if filter_fn is not None and query_config is not None:
46
+ raise ValueError("Use either filter_fn OR query_config, not both")
47
+
48
+ resolved_config = self._resolve_query_config(filter_fn, query_config, WorkspaceQueryObjectType.PAGE)
49
+ return await self._query_service.get_pages(resolved_config)
48
50
 
49
51
  async def get_pages_stream(
50
52
  self,
51
- config: _QueryConfigInput | None = None,
53
+ *,
54
+ filter_fn: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder] | None = None,
55
+ query_config: WorkspaceQueryConfig | None = None,
52
56
  ) -> AsyncIterator[NotionPage]:
53
- query_config = self._resolve_config(config, default_object_type_to_query=WorkspaceQueryObjectType.PAGE)
54
- async for page in self._query_service.get_pages_stream(query_config):
57
+ if filter_fn is not None and query_config is not None:
58
+ raise ValueError("Use either filter_fn OR query_config, not both")
59
+
60
+ resolved_config = self._resolve_query_config(filter_fn, query_config, WorkspaceQueryObjectType.PAGE)
61
+ async for page in self._query_service.get_pages_stream(resolved_config):
55
62
  yield page
56
63
 
57
64
  async def get_data_sources(
58
65
  self,
59
- config: _QueryConfigInput | None = None,
66
+ *,
67
+ filter_fn: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder] | None = None,
68
+ query_config: WorkspaceQueryConfig | None = None,
60
69
  ) -> list[NotionDataSource]:
61
- query_config = self._resolve_config(config, default_object_type_to_query=WorkspaceQueryObjectType.DATA_SOURCE)
62
- return await self._query_service.get_data_sources(query_config)
70
+ if filter_fn is not None and query_config is not None:
71
+ raise ValueError("Use either filter_fn OR query_config, not both")
72
+
73
+ resolved_config = self._resolve_query_config(filter_fn, query_config, WorkspaceQueryObjectType.DATA_SOURCE)
74
+ return await self._query_service.get_data_sources(resolved_config)
63
75
 
64
76
  async def get_data_sources_stream(
65
77
  self,
66
- config: _QueryConfigInput | None = None,
78
+ *,
79
+ filter_fn: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder] | None = None,
80
+ query_config: WorkspaceQueryConfig | None = None,
67
81
  ) -> AsyncIterator[NotionDataSource]:
68
- query_config = self._resolve_config(config, default_object_type_to_query=WorkspaceQueryObjectType.DATA_SOURCE)
69
- async for data_source in self._query_service.get_data_sources_stream(query_config):
82
+ if filter_fn is not None and query_config is not None:
83
+ raise ValueError("Use either filter_fn OR query_config, not both")
84
+
85
+ resolved_config = self._resolve_query_config(filter_fn, query_config, WorkspaceQueryObjectType.DATA_SOURCE)
86
+ async for data_source in self._query_service.get_data_sources_stream(resolved_config):
70
87
  yield data_source
71
88
 
72
- def _resolve_config(
89
+ def _resolve_query_config(
73
90
  self,
74
- config: _QueryConfigInput | None,
75
- default_object_type_to_query: WorkspaceQueryObjectType,
91
+ filter_fn: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder] | None,
92
+ query_config: WorkspaceQueryConfig | None,
93
+ expected_object_type: WorkspaceQueryObjectType,
76
94
  ) -> WorkspaceQueryConfig:
77
- if isinstance(config, WorkspaceQueryConfig):
78
- return config
79
-
80
- builder = self._create_builder_with_defaults(default_object_type_to_query)
81
-
82
- if callable(config):
83
- config(builder)
84
-
85
- return builder.build()
86
-
87
- def _create_builder_with_defaults(self, object_type: WorkspaceQueryObjectType) -> WorkspaceQueryConfigBuilder:
88
- builder = WorkspaceQueryConfigBuilder()
95
+ if filter_fn is not None:
96
+ builder = NotionWorkspaceQueryConfigBuilder()
97
+ configured_builder = filter_fn(builder)
98
+ query_config = configured_builder.build()
89
99
 
90
- if object_type == WorkspaceQueryObjectType.PAGE:
91
- builder.with_pages_only()
100
+ if query_config is None:
101
+ query_config = WorkspaceQueryConfig(object_type=expected_object_type)
92
102
  else:
93
- builder.with_data_sources_only()
103
+ query_config.object_type = expected_object_type
94
104
 
95
- return builder
105
+ return query_config
96
106
 
97
107
  async def get_users(self) -> list[PersonUser]:
98
108
  return [user async for user in self._user_service.list_users_stream()]
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: notionary
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support
5
5
  Project-URL: Homepage, https://github.com/mathisarends/notionary
6
6
  Author-email: Mathis Arends <mathisarends27@gmail.com>
7
7
  License: MIT
8
8
  License-File: LICENSE
9
- Requires-Python: >=3.11
9
+ Requires-Python: >=3.12
10
10
  Requires-Dist: aiofiles<25.0.0,>=24.1.0
11
11
  Requires-Dist: httpx>=0.28.0
12
12
  Requires-Dist: pydantic>=2.11.4
@@ -23,9 +23,13 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  <div align="center">
25
25
 
26
- [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
27
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
28
- [![Documentation](https://img.shields.io/badge/docs-mathisarends.github.io-blue.svg)](https://mathisarends.github.io/notionary/)
26
+ [![PyPI version](https://badge.fury.io/py/notionary.svg)](https://badge.fury.io/py/notionary)
27
+ [![Python Version](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/downloads/)
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
29
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/mathisarends/fc0568b66a20fbbaa5018205861c0da9/raw/notionary-coverage.json)](https://github.com/mathisarends/notionary)
30
+ [![Downloads](https://img.shields.io/pypi/dm/notionary?color=blue)](https://pypi.org/project/notionary/)
31
+ [![Documentation](https://img.shields.io/badge/docs-notionary-blue?style=flat&logo=readthedocs)](https://mathisarends.github.io/notionary/)
32
+ [![Notion API](https://img.shields.io/badge/Notion%20API-Official-000000?logo=notion&logoColor=white)](https://developers.notion.com/)
29
33
 
30
34
  **Transform complex Notion API interactions into simple, Pythonic code.**
31
35
  Perfect for developers building AI agents, automation workflows, and dynamic content systems.