notionary 0.2.28__py3-none-any.whl → 0.3.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 +9 -2
- notionary/blocks/__init__.py +5 -0
- notionary/blocks/client.py +6 -4
- notionary/blocks/enums.py +28 -1
- notionary/blocks/rich_text/markdown_rich_text_converter.py +14 -0
- notionary/blocks/rich_text/models.py +14 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +2 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +12 -0
- notionary/blocks/rich_text/rich_text_patterns.py +3 -0
- notionary/blocks/schemas.py +42 -10
- notionary/comments/__init__.py +5 -0
- notionary/comments/client.py +7 -10
- notionary/comments/factory.py +4 -6
- notionary/data_source/http/data_source_instance_client.py +14 -4
- notionary/data_source/properties/{models.py → schemas.py} +4 -8
- notionary/data_source/query/__init__.py +9 -0
- notionary/data_source/query/builder.py +38 -10
- notionary/data_source/query/schema.py +13 -10
- notionary/data_source/query/validator.py +11 -11
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +1 -1
- notionary/data_source/service.py +29 -103
- notionary/database/service.py +17 -60
- notionary/exceptions/__init__.py +5 -1
- notionary/exceptions/block_parsing.py +21 -0
- notionary/exceptions/search.py +24 -0
- notionary/http/client.py +9 -10
- notionary/http/models.py +5 -4
- notionary/page/content/factory.py +10 -3
- notionary/page/content/markdown/builder.py +76 -154
- notionary/page/content/markdown/nodes/__init__.py +0 -2
- notionary/page/content/markdown/nodes/audio.py +1 -1
- notionary/page/content/markdown/nodes/base.py +1 -1
- notionary/page/content/markdown/nodes/bookmark.py +1 -1
- notionary/page/content/markdown/nodes/breadcrumb.py +1 -1
- notionary/page/content/markdown/nodes/bulleted_list.py +31 -8
- notionary/page/content/markdown/nodes/callout.py +12 -10
- notionary/page/content/markdown/nodes/code.py +3 -5
- notionary/page/content/markdown/nodes/columns.py +39 -21
- notionary/page/content/markdown/nodes/container.py +64 -0
- notionary/page/content/markdown/nodes/divider.py +1 -1
- notionary/page/content/markdown/nodes/embed.py +1 -1
- notionary/page/content/markdown/nodes/equation.py +1 -1
- notionary/page/content/markdown/nodes/file.py +1 -1
- notionary/page/content/markdown/nodes/heading.py +26 -6
- notionary/page/content/markdown/nodes/image.py +1 -1
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +1 -1
- notionary/page/content/markdown/nodes/numbered_list.py +28 -5
- notionary/page/content/markdown/nodes/paragraph.py +1 -1
- notionary/page/content/markdown/nodes/pdf.py +1 -1
- notionary/page/content/markdown/nodes/quote.py +17 -5
- notionary/page/content/markdown/nodes/space.py +1 -1
- notionary/page/content/markdown/nodes/table.py +1 -1
- notionary/page/content/markdown/nodes/table_of_contents.py +1 -1
- notionary/page/content/markdown/nodes/todo.py +23 -7
- notionary/page/content/markdown/nodes/toggle.py +13 -14
- notionary/page/content/markdown/nodes/video.py +1 -1
- notionary/page/content/parser/context.py +98 -21
- notionary/page/content/parser/factory.py +1 -10
- notionary/page/content/parser/parsers/__init__.py +0 -2
- notionary/page/content/parser/parsers/audio.py +1 -1
- notionary/page/content/parser/parsers/base.py +1 -1
- notionary/page/content/parser/parsers/bookmark.py +1 -1
- notionary/page/content/parser/parsers/breadcrumb.py +1 -1
- notionary/page/content/parser/parsers/bulleted_list.py +52 -8
- notionary/page/content/parser/parsers/callout.py +55 -84
- notionary/page/content/parser/parsers/caption.py +1 -1
- notionary/page/content/parser/parsers/code.py +5 -5
- notionary/page/content/parser/parsers/column.py +23 -64
- notionary/page/content/parser/parsers/column_list.py +45 -45
- notionary/page/content/parser/parsers/divider.py +1 -1
- notionary/page/content/parser/parsers/embed.py +1 -1
- notionary/page/content/parser/parsers/equation.py +1 -1
- notionary/page/content/parser/parsers/file.py +1 -1
- notionary/page/content/parser/parsers/heading.py +65 -8
- notionary/page/content/parser/parsers/image.py +1 -1
- notionary/page/content/parser/parsers/numbered_list.py +52 -8
- notionary/page/content/parser/parsers/paragraph.py +3 -2
- notionary/page/content/parser/parsers/pdf.py +1 -1
- notionary/page/content/parser/parsers/quote.py +75 -15
- notionary/page/content/parser/parsers/space.py +14 -8
- notionary/page/content/parser/parsers/table.py +1 -1
- notionary/page/content/parser/parsers/table_of_contents.py +1 -1
- notionary/page/content/parser/parsers/todo.py +57 -19
- notionary/page/content/parser/parsers/toggle.py +17 -74
- notionary/page/content/parser/parsers/video.py +1 -1
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +6 -4
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +43 -22
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +4 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +108 -54
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +86 -0
- notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +66 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +14 -7
- notionary/page/content/parser/service.py +9 -0
- notionary/page/content/renderer/context.py +5 -2
- notionary/page/content/renderer/factory.py +2 -11
- notionary/page/content/renderer/post_processing/handlers/__init__.py +2 -2
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
- notionary/page/content/renderer/renderers/__init__.py +0 -2
- notionary/page/content/renderer/renderers/base.py +1 -1
- notionary/page/content/renderer/renderers/bulleted_list.py +1 -1
- notionary/page/content/renderer/renderers/callout.py +6 -21
- notionary/page/content/renderer/renderers/captioned_block.py +1 -1
- notionary/page/content/renderer/renderers/column.py +28 -19
- notionary/page/content/renderer/renderers/column_list.py +24 -11
- notionary/page/content/renderer/renderers/heading.py +53 -27
- notionary/page/content/renderer/renderers/numbered_list.py +6 -5
- notionary/page/content/renderer/renderers/quote.py +1 -1
- notionary/page/content/renderer/renderers/todo.py +1 -1
- notionary/page/content/renderer/renderers/toggle.py +6 -7
- notionary/page/content/service.py +4 -1
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +0 -2
- notionary/page/content/syntax/{service.py → registry.py} +31 -91
- notionary/page/properties/client.py +3 -3
- notionary/page/properties/models.py +3 -2
- notionary/page/properties/service.py +18 -3
- notionary/page/service.py +22 -80
- notionary/shared/entity/service.py +94 -36
- notionary/shared/models/cover.py +1 -1
- notionary/shared/typings.py +3 -0
- notionary/user/base.py +60 -11
- notionary/user/factory.py +0 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +18 -6
- notionary/utils/mixins/logging.py +38 -27
- notionary/utils/pagination.py +70 -16
- notionary/workspace/__init__.py +2 -1
- notionary/workspace/client.py +4 -2
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +25 -1
- notionary/workspace/query/models.py +12 -3
- notionary/workspace/query/service.py +57 -32
- notionary/workspace/service.py +31 -21
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/METADATA +35 -105
- notionary-0.3.1.dist-info/RECORD +211 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +0 -35
- notionary/page/content/parser/parsers/toggleable_heading.py +0 -150
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +0 -62
- notionary/page/content/renderer/renderers/toggleable_heading.py +0 -78
- notionary/utils/async_retry.py +0 -39
- notionary/utils/singleton.py +0 -13
- notionary-0.2.28.dist-info/RECORD +0 -200
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/WHEEL +0 -0
- {notionary-0.2.28.dist-info → notionary-0.3.1.dist-info}/licenses/LICENSE +0 -0
notionary/workspace/client.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from collections.abc import AsyncGenerator
|
|
2
|
-
from typing import Any
|
|
3
2
|
|
|
4
3
|
from notionary.data_source.schemas import DataSourceDto
|
|
5
4
|
from notionary.http.client import NotionHttpClient
|
|
6
5
|
from notionary.page.schemas import NotionPageDto
|
|
6
|
+
from notionary.shared.typings import JsonDict
|
|
7
7
|
from notionary.utils.pagination import paginate_notion_api_generator
|
|
8
8
|
from notionary.workspace.query.models import WorkspaceQueryConfig
|
|
9
9
|
from notionary.workspace.schemas import DataSourceSearchResponse, PageSearchResponse
|
|
@@ -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
|
|
|
@@ -57,6 +59,6 @@ class WorkspaceClient:
|
|
|
57
59
|
response = await self._execute_search(search_config)
|
|
58
60
|
return DataSourceSearchResponse.model_validate(response)
|
|
59
61
|
|
|
60
|
-
async def _execute_search(self, config: WorkspaceQueryConfig) ->
|
|
62
|
+
async def _execute_search(self, config: WorkspaceQueryConfig) -> JsonDict:
|
|
61
63
|
serialized_config = config.model_dump(exclude_none=True, by_alias=True)
|
|
62
64
|
return await self._http_client.post("search", serialized_config)
|
|
@@ -8,7 +8,7 @@ from notionary.workspace.query.models import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
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,8 +1,14 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Protocol
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, Field, field_validator, model_serializer
|
|
5
5
|
|
|
6
|
+
from notionary.shared.typings import JsonDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SearchableEntity(Protocol):
|
|
10
|
+
title: str
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
class SortDirection(StrEnum):
|
|
8
14
|
ASCENDING = "ascending"
|
|
@@ -24,9 +30,12 @@ class WorkspaceQueryConfig(BaseModel):
|
|
|
24
30
|
object_type: WorkspaceQueryObjectType | None = None
|
|
25
31
|
sort_direction: SortDirection = SortDirection.DESCENDING
|
|
26
32
|
sort_timestamp: SortTimestamp = SortTimestamp.LAST_EDITED_TIME
|
|
33
|
+
|
|
27
34
|
page_size: int = Field(default=100, ge=1, le=100)
|
|
28
35
|
start_cursor: str | None = None
|
|
29
36
|
|
|
37
|
+
total_results_limit: int | None = None
|
|
38
|
+
|
|
30
39
|
@field_validator("query")
|
|
31
40
|
@classmethod
|
|
32
41
|
def replace_empty_query_with_none(cls, value: str | None) -> str | None:
|
|
@@ -35,8 +44,8 @@ class WorkspaceQueryConfig(BaseModel):
|
|
|
35
44
|
return value
|
|
36
45
|
|
|
37
46
|
@model_serializer
|
|
38
|
-
def
|
|
39
|
-
search_dict:
|
|
47
|
+
def to_api_params(self) -> JsonDict:
|
|
48
|
+
search_dict: JsonDict = {}
|
|
40
49
|
|
|
41
50
|
if self.query:
|
|
42
51
|
search_dict["query"] = 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
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from notionary.exceptions.search import DatabaseNotFound, DataSourceNotFound, PageNotFound
|
|
8
|
-
from notionary.utils.fuzzy import
|
|
8
|
+
from notionary.utils.fuzzy import find_all_matches
|
|
9
9
|
from notionary.workspace.client import WorkspaceClient
|
|
10
|
-
from notionary.workspace.query.builder import
|
|
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()
|
|
@@ -48,46 +44,75 @@ class WorkspaceQueryService:
|
|
|
48
44
|
data_source_tasks = [NotionDataSource.from_id(dto.id) for dto in data_source_dtos]
|
|
49
45
|
return await asyncio.gather(*data_source_tasks)
|
|
50
46
|
|
|
51
|
-
async def find_data_source(self, query: str
|
|
52
|
-
config =
|
|
47
|
+
async def find_data_source(self, query: str) -> NotionDataSource:
|
|
48
|
+
config = (
|
|
49
|
+
NotionWorkspaceQueryConfigBuilder()
|
|
50
|
+
.with_query(query)
|
|
51
|
+
.with_data_sources_only()
|
|
52
|
+
.with_page_size(100)
|
|
53
|
+
.build()
|
|
54
|
+
)
|
|
53
55
|
data_sources = await self.get_data_sources(config)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
return self._find_exact_match(data_sources, query, DataSourceNotFound)
|
|
57
|
+
|
|
58
|
+
async def find_page(self, query: str) -> NotionPage:
|
|
59
|
+
config = (
|
|
60
|
+
NotionWorkspaceQueryConfigBuilder()
|
|
61
|
+
.with_query(query)
|
|
62
|
+
.with_pages_only()
|
|
63
|
+
.with_page_size(100)
|
|
64
|
+
.build()
|
|
57
65
|
)
|
|
58
|
-
|
|
59
|
-
async def find_page(self, query: str, min_similarity: float = 0.6) -> NotionPage:
|
|
60
|
-
config = WorkspaceQueryConfigBuilder().with_query(query).with_pages_only().with_page_size(5).build()
|
|
61
66
|
pages = await self.get_pages(config)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
return self._find_exact_match(pages, query, PageNotFound)
|
|
68
|
+
|
|
69
|
+
async def find_database(self, query: str) -> NotionDatabase:
|
|
70
|
+
config = (
|
|
71
|
+
NotionWorkspaceQueryConfigBuilder()
|
|
72
|
+
.with_query(query)
|
|
73
|
+
.with_data_sources_only()
|
|
74
|
+
.with_page_size(100)
|
|
75
|
+
.build()
|
|
76
|
+
)
|
|
67
77
|
data_sources = await self.get_data_sources(config)
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
parent_database_ids = [data_sources.get_parent_database_id_if_present() for data_sources in data_sources]
|
|
80
|
+
# filter none values which should not happen but for safety
|
|
81
|
+
parent_database_ids = [id for id in parent_database_ids if id is not None]
|
|
82
|
+
|
|
83
|
+
parent_database_tasks = [NotionDatabase.from_id(db_id) for db_id in parent_database_ids]
|
|
70
84
|
parent_databases = await asyncio.gather(*parent_database_tasks)
|
|
71
85
|
potential_databases = [database for database in parent_databases if database is not None]
|
|
72
86
|
|
|
73
|
-
return self.
|
|
87
|
+
return self._find_exact_match(potential_databases, query, DatabaseNotFound)
|
|
74
88
|
|
|
75
|
-
def
|
|
89
|
+
def _find_exact_match(
|
|
76
90
|
self,
|
|
77
91
|
search_results: list[SearchableEntity],
|
|
78
92
|
query: str,
|
|
79
93
|
exception_class: type[Exception],
|
|
80
|
-
min_similarity: float | None = None,
|
|
81
94
|
) -> SearchableEntity:
|
|
82
|
-
|
|
95
|
+
if not search_results:
|
|
96
|
+
raise exception_class(query, [])
|
|
97
|
+
|
|
98
|
+
query_lower = query.lower()
|
|
99
|
+
exact_matches = [result for result in search_results if result.title.lower() == query_lower]
|
|
100
|
+
|
|
101
|
+
if exact_matches:
|
|
102
|
+
return exact_matches[0]
|
|
103
|
+
|
|
104
|
+
suggestions = self._get_fuzzy_suggestions(search_results, query)
|
|
105
|
+
raise exception_class(query, suggestions)
|
|
106
|
+
|
|
107
|
+
def _get_fuzzy_suggestions(self, search_results: list[SearchableEntity], query: str) -> list[str]:
|
|
108
|
+
sorted_by_similarity = find_all_matches(
|
|
83
109
|
query=query,
|
|
84
110
|
items=search_results,
|
|
85
|
-
text_extractor=lambda
|
|
86
|
-
min_similarity=
|
|
111
|
+
text_extractor=lambda entity: entity.title,
|
|
112
|
+
min_similarity=0.6,
|
|
87
113
|
)
|
|
88
114
|
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
raise exception_class(query, available_titles)
|
|
115
|
+
if sorted_by_similarity:
|
|
116
|
+
return [result.title for result in sorted_by_similarity[:5]]
|
|
92
117
|
|
|
93
|
-
return
|
|
118
|
+
return [result.title for result in search_results[:5]]
|
notionary/workspace/service.py
CHANGED
|
@@ -4,7 +4,7 @@ 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
|
|
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
|
|
|
@@ -13,7 +13,9 @@ if TYPE_CHECKING:
|
|
|
13
13
|
from notionary.page.service import NotionPage
|
|
14
14
|
from notionary.user import BotUser, PersonUser
|
|
15
15
|
|
|
16
|
-
type _QueryConfigInput =
|
|
16
|
+
type _QueryConfigInput = (
|
|
17
|
+
WorkspaceQueryConfig | Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder]
|
|
18
|
+
)
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class NotionWorkspace:
|
|
@@ -43,14 +45,14 @@ class NotionWorkspace:
|
|
|
43
45
|
self,
|
|
44
46
|
config: _QueryConfigInput | None = None,
|
|
45
47
|
) -> list[NotionPage]:
|
|
46
|
-
query_config = self._resolve_config(config,
|
|
48
|
+
query_config = self._resolve_config(config, WorkspaceQueryObjectType.PAGE)
|
|
47
49
|
return await self._query_service.get_pages(query_config)
|
|
48
50
|
|
|
49
51
|
async def get_pages_stream(
|
|
50
52
|
self,
|
|
51
53
|
config: _QueryConfigInput | None = None,
|
|
52
54
|
) -> AsyncIterator[NotionPage]:
|
|
53
|
-
query_config = self._resolve_config(config,
|
|
55
|
+
query_config = self._resolve_config(config, WorkspaceQueryObjectType.PAGE)
|
|
54
56
|
async for page in self._query_service.get_pages_stream(query_config):
|
|
55
57
|
yield page
|
|
56
58
|
|
|
@@ -58,41 +60,49 @@ class NotionWorkspace:
|
|
|
58
60
|
self,
|
|
59
61
|
config: _QueryConfigInput | None = None,
|
|
60
62
|
) -> list[NotionDataSource]:
|
|
61
|
-
query_config = self._resolve_config(config,
|
|
63
|
+
query_config = self._resolve_config(config, WorkspaceQueryObjectType.DATA_SOURCE)
|
|
62
64
|
return await self._query_service.get_data_sources(query_config)
|
|
63
65
|
|
|
64
66
|
async def get_data_sources_stream(
|
|
65
67
|
self,
|
|
66
68
|
config: _QueryConfigInput | None = None,
|
|
67
69
|
) -> AsyncIterator[NotionDataSource]:
|
|
68
|
-
query_config = self._resolve_config(config,
|
|
70
|
+
query_config = self._resolve_config(config, WorkspaceQueryObjectType.DATA_SOURCE)
|
|
69
71
|
async for data_source in self._query_service.get_data_sources_stream(query_config):
|
|
70
72
|
yield data_source
|
|
71
73
|
|
|
72
74
|
def _resolve_config(
|
|
73
75
|
self,
|
|
74
76
|
config: _QueryConfigInput | None,
|
|
75
|
-
|
|
77
|
+
expected_object_type: WorkspaceQueryObjectType,
|
|
76
78
|
) -> WorkspaceQueryConfig:
|
|
77
|
-
if
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
builder = self._create_builder_with_defaults(default_object_type_to_query)
|
|
79
|
+
if config is None:
|
|
80
|
+
return self._create_default_config(expected_object_type)
|
|
81
81
|
|
|
82
|
-
if
|
|
83
|
-
config
|
|
82
|
+
if isinstance(config, WorkspaceQueryConfig):
|
|
83
|
+
return self._ensure_correct_object_type(config, expected_object_type)
|
|
84
84
|
|
|
85
|
-
return
|
|
85
|
+
return self._build_config_from_callable(config, expected_object_type)
|
|
86
86
|
|
|
87
|
-
def
|
|
88
|
-
|
|
87
|
+
def _create_default_config(self, object_type: WorkspaceQueryObjectType) -> WorkspaceQueryConfig:
|
|
88
|
+
return WorkspaceQueryConfig(object_type=object_type)
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
def _build_config_from_callable(
|
|
91
|
+
self,
|
|
92
|
+
config_callable: Callable[[NotionWorkspaceQueryConfigBuilder], NotionWorkspaceQueryConfigBuilder],
|
|
93
|
+
expected_object_type: WorkspaceQueryObjectType,
|
|
94
|
+
) -> WorkspaceQueryConfig:
|
|
95
|
+
builder = NotionWorkspaceQueryConfigBuilder()
|
|
96
|
+
config_callable(builder)
|
|
97
|
+
return self._ensure_correct_object_type(builder.build(), expected_object_type)
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
def _ensure_correct_object_type(
|
|
100
|
+
self,
|
|
101
|
+
config: WorkspaceQueryConfig,
|
|
102
|
+
expected_object_type: WorkspaceQueryObjectType,
|
|
103
|
+
) -> WorkspaceQueryConfig:
|
|
104
|
+
config.object_type = expected_object_type
|
|
105
|
+
return 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
|
+
Version: 0.3.1
|
|
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.
|
|
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
|
-
[](https://badge.fury.io/py/notionary)
|
|
27
|
+
[](https://www.python.org/downloads/)
|
|
28
|
+
[](https://opensource.org/licenses/MIT)
|
|
29
|
+
[](https://github.com/mathisarends/notionary)
|
|
30
|
+
[](https://pypi.org/project/notionary/)
|
|
31
|
+
[](https://mathisarends.github.io/notionary/)
|
|
32
|
+
[](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.
|
|
@@ -61,17 +65,9 @@ export NOTION_SECRET=your_integration_key
|
|
|
61
65
|
|
|
62
66
|
## See It in Action
|
|
63
67
|
|
|
64
|
-
### Creating Rich Database Entries
|
|
65
|
-
|
|
66
68
|
https://github.com/user-attachments/assets/da8b4691-bee4-4b0f-801e-dccacb630398
|
|
67
69
|
|
|
68
|
-
_Create
|
|
69
|
-
|
|
70
|
-
### Local File Uploads (Videos & Images)
|
|
71
|
-
|
|
72
|
-
https://github.com/user-attachments/assets/a079ec01-bb56-4c65-8260-7b1fca42ac68
|
|
73
|
-
|
|
74
|
-
_Upload videos and images using simple markdown syntax - files are automatically uploaded to Notion_
|
|
70
|
+
_Create rich database entries with properties, content, and beautiful formatting_
|
|
75
71
|
|
|
76
72
|
---
|
|
77
73
|
|
|
@@ -80,50 +76,30 @@ _Upload videos and images using simple markdown syntax - files are automatically
|
|
|
80
76
|
### Find → Create → Update Flow
|
|
81
77
|
|
|
82
78
|
```python
|
|
83
|
-
import
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.h2("Project Status")
|
|
108
|
-
.callout("Milestone reached!", "🎉")
|
|
109
|
-
.columns(
|
|
110
|
-
lambda col: col.h3("Completed").bulleted_list([
|
|
111
|
-
"API design", "Database setup", "Authentication"
|
|
112
|
-
]),
|
|
113
|
-
lambda col: col.h3("In Progress").bulleted_list([
|
|
114
|
-
"Frontend UI", "Testing", "Documentation"
|
|
115
|
-
]),
|
|
116
|
-
width_ratios=[0.6, 0.4]
|
|
117
|
-
)
|
|
118
|
-
.toggle("Budget Details", lambda t: t
|
|
119
|
-
.table(["Item", "Cost", "Status"], [
|
|
120
|
-
["Development", "$15,000", "Paid"],
|
|
121
|
-
["Design", "$8,000", "Pending"]
|
|
122
|
-
])
|
|
123
|
-
)
|
|
124
|
-
))
|
|
125
|
-
|
|
126
|
-
asyncio.run(main())
|
|
79
|
+
from notionary import NotionPage
|
|
80
|
+
|
|
81
|
+
# Find pages by name with fuzzy matching
|
|
82
|
+
page = await NotionPage.from_title("Meeting Notes")
|
|
83
|
+
|
|
84
|
+
# Define rich content with extended markdown
|
|
85
|
+
content = """
|
|
86
|
+
## Action Items
|
|
87
|
+
- [x] Review proposal
|
|
88
|
+
- [ ] Schedule meeting
|
|
89
|
+
|
|
90
|
+
[callout](Key decision made! "💡")
|
|
91
|
+
|
|
92
|
+
| Task | Owner | Deadline |
|
|
93
|
+
|------|-------|----------|
|
|
94
|
+
| Design Review | Alice | 2024-03-15 |
|
|
95
|
+
| Implementation | Bob | 2024-03-22 |
|
|
96
|
+
|
|
97
|
+
+++ Budget Details
|
|
98
|
+
See attached spreadsheet...
|
|
99
|
+
+++
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
await page.append_markdown(content)
|
|
127
103
|
```
|
|
128
104
|
|
|
129
105
|
### Complete Block Support
|
|
@@ -132,36 +108,13 @@ Every Notion block type with extended syntax:
|
|
|
132
108
|
|
|
133
109
|
| Block Type | Markdown Syntax | Use Case |
|
|
134
110
|
| ------------- | -------------------------------------------- | ---------------------------- |
|
|
135
|
-
| **Callouts** | `[callout](Text "🔥")` | Highlighting key information |
|
|
136
111
|
| **Toggles** | `+++ Title\nContent\n+++` | Collapsible sections |
|
|
137
112
|
| **Columns** | `::: columns\n::: column\nContent\n:::\n:::` | Side-by-side layouts |
|
|
138
113
|
| **Tables** | Standard markdown tables | Structured data |
|
|
139
114
|
| **Media** | `[video](./file.mp4)(caption:Description)` | Auto-uploading files |
|
|
140
115
|
| **Code** | Standard code fences with captions | Code snippets |
|
|
141
116
|
| **Equations** | `$LaTeX$` | Mathematical expressions |
|
|
142
|
-
| **TOC** | `[toc]
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## What You Can Build 💡
|
|
147
|
-
|
|
148
|
-
### **AI Content Systems**
|
|
149
|
-
|
|
150
|
-
- **Report Generation**: AI agents that create structured reports, documentation, and analysis
|
|
151
|
-
- **Content Pipelines**: Automated workflows that process data and generate Notion pages
|
|
152
|
-
- **Knowledge Management**: AI-powered documentation systems with smart categorization
|
|
153
|
-
|
|
154
|
-
### **Workflow Automation**
|
|
155
|
-
|
|
156
|
-
- **Project Management**: Sync project status, update timelines, generate progress reports
|
|
157
|
-
- **Data Integration**: Connect external APIs and databases to Notion workspaces
|
|
158
|
-
- **Template Systems**: Dynamic page generation from templates and data sources
|
|
159
|
-
|
|
160
|
-
### **Content Management**
|
|
161
|
-
|
|
162
|
-
- **Bulk Operations**: Mass page updates, content migration, and database management
|
|
163
|
-
- **Media Handling**: Automated image/video uploads with proper organization
|
|
164
|
-
- **Cross-Platform**: Sync content between Notion and other platforms
|
|
117
|
+
| **TOC** | `[toc]` | Auto-generated navigation |
|
|
165
118
|
|
|
166
119
|
---
|
|
167
120
|
|
|
@@ -222,29 +175,6 @@ Every Notion block type with extended syntax:
|
|
|
222
175
|
|
|
223
176
|
[**mathisarends.github.io/notionary**](https://mathisarends.github.io/notionary/) - Complete API reference, guides, and tutorials
|
|
224
177
|
|
|
225
|
-
### Quick Links
|
|
226
|
-
|
|
227
|
-
- [**Getting Started**](https://mathisarends.github.io/notionary/get-started/) - Setup and first steps
|
|
228
|
-
- [**Page Management**](https://mathisarends.github.io/notionary/page/) - Content and properties
|
|
229
|
-
- [**Database Operations**](https://mathisarends.github.io/notionary/database/) - Queries and management
|
|
230
|
-
- [**Block Types Reference**](https://mathisarends.github.io/notionary/blocks/) - Complete syntax guide
|
|
231
|
-
|
|
232
|
-
### Hands-On Examples
|
|
233
|
-
|
|
234
|
-
**Core Functionality:**
|
|
235
|
-
|
|
236
|
-
- [Page Management](examples/page_example.py) - Create, update, and manage pages
|
|
237
|
-
- [Database Operations](examples/database.py) - Connect and query databases
|
|
238
|
-
- [Workspace Discovery](examples/workspace_discovery.py) - Explore your workspace
|
|
239
|
-
|
|
240
|
-
**Extended Markdown:**
|
|
241
|
-
|
|
242
|
-
- [Basic Formatting](examples/markdown/basic.py) - Text, lists, and links
|
|
243
|
-
- [Callouts & Highlights](examples/markdown/callout.py) - Information boxes
|
|
244
|
-
- [Toggle Sections](examples/markdown/toggle.py) - Collapsible content
|
|
245
|
-
- [Multi-Column Layouts](examples/markdown/columns.py) - Side-by-side design
|
|
246
|
-
- [Tables & Data](examples/markdown/table.py) - Structured presentations
|
|
247
|
-
|
|
248
178
|
---
|
|
249
179
|
|
|
250
180
|
## Contributing
|