notionary 0.1.11__py3-none-any.whl → 0.1.13__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 +21 -6
- notionary/{core/converters → converters}/elements/audio_element.py +7 -5
- notionary/{core/converters → converters}/elements/bookmark_element.py +1 -1
- notionary/{core/converters → converters}/elements/callout_element.py +2 -2
- notionary/{core/converters → converters}/elements/code_block_element.py +1 -1
- notionary/{core/converters → converters}/elements/column_element.py +1 -1
- notionary/{core/converters → converters}/elements/divider_element.py +1 -1
- notionary/{core/converters → converters}/elements/embed_element.py +3 -5
- notionary/{core/converters → converters}/elements/heading_element.py +2 -2
- notionary/{core/converters → converters}/elements/image_element.py +1 -1
- notionary/{core/converters → converters}/elements/list_element.py +2 -2
- notionary/{core/converters → converters}/elements/paragraph_element.py +2 -2
- notionary/{core/converters → converters}/elements/qoute_element.py +1 -1
- notionary/{core/converters → converters}/elements/table_element.py +2 -2
- notionary/{core/converters → converters}/elements/todo_lists.py +2 -2
- notionary/{core/converters → converters}/elements/toggle_element.py +24 -21
- notionary/{core/converters → converters}/elements/video_element.py +1 -1
- notionary/{core/converters → converters}/markdown_to_notion_converter.py +72 -111
- notionary/{core/converters → converters}/notion_to_markdown_converter.py +2 -2
- notionary/{core/converters → converters}/registry/block_element_registry.py +5 -5
- notionary/{core/converters → converters}/registry/block_element_registry_builder.py +18 -18
- notionary/database/database_discovery.py +142 -0
- notionary/{core/database → database}/database_info_service.py +1 -1
- notionary/{core/database/notion_database_manager.py → database/notion_database.py} +33 -57
- notionary/{core/database/notion_database_manager_factory.py → database/notion_database_factory.py} +18 -16
- notionary/{core/notion_client.py → notion_client.py} +4 -2
- notionary/page/content/notion_page_content_chunker.py +84 -0
- notionary/{core/page → page}/content/page_content_manager.py +29 -13
- notionary/{core/page → page}/metadata/metadata_editor.py +59 -46
- notionary/{core/page → page}/metadata/notion_icon_manager.py +10 -12
- notionary/{core/page → page}/metadata/notion_page_cover_manager.py +16 -21
- notionary/page/notion_page.py +504 -0
- notionary/page/notion_page_factory.py +256 -0
- notionary/{core/page → page}/properites/database_property_service.py +115 -99
- notionary/{core/page → page}/properites/page_property_manager.py +81 -52
- notionary/{core/page → page}/properites/property_formatter.py +1 -1
- notionary/{core/page → page}/properites/property_operation_result.py +43 -30
- notionary/{core/page → page}/properites/property_value_extractor.py +26 -8
- notionary/{core/page → page}/relations/notion_page_relation_manager.py +72 -53
- notionary/{core/page → page}/relations/notion_page_title_resolver.py +12 -12
- notionary/{core/page → page}/relations/page_database_relation.py +15 -15
- notionary/{core/page → page}/relations/relation_operation_result.py +50 -41
- notionary/util/page_id_utils.py +14 -8
- {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/METADATA +1 -1
- notionary-0.1.13.dist-info/RECORD +56 -0
- notionary/core/database/notion_database_schema.py +0 -104
- notionary/core/page/notion_page_manager.py +0 -322
- notionary-0.1.11.dist-info/RECORD +0 -54
- /notionary/{core/converters → converters}/__init__.py +0 -0
- /notionary/{core/converters → converters}/elements/notion_block_element.py +0 -0
- /notionary/{core/converters → converters}/elements/text_inline_formatter.py +0 -0
- /notionary/{core/database → database}/models/page_result.py +0 -0
- {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/WHEEL +0 -0
- {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/top_level.txt +0 -0
@@ -1,40 +1,53 @@
|
|
1
1
|
from typing import Dict, Any, List, Optional
|
2
|
-
from notionary.
|
3
|
-
from notionary.
|
4
|
-
from notionary.
|
5
|
-
|
6
|
-
|
7
|
-
from notionary.
|
8
|
-
|
2
|
+
from notionary.notion_client import NotionClient
|
3
|
+
from notionary.page.metadata.metadata_editor import MetadataEditor
|
4
|
+
from notionary.page.properites.property_operation_result import (
|
5
|
+
PropertyOperationResult,
|
6
|
+
)
|
7
|
+
from notionary.page.relations.notion_page_title_resolver import (
|
8
|
+
NotionPageTitleResolver,
|
9
|
+
)
|
10
|
+
from notionary.page.properites.database_property_service import (
|
11
|
+
DatabasePropertyService,
|
12
|
+
)
|
13
|
+
from notionary.page.relations.page_database_relation import PageDatabaseRelation
|
14
|
+
from notionary.page.properites.property_value_extractor import (
|
15
|
+
PropertyValueExtractor,
|
16
|
+
)
|
9
17
|
from notionary.util.logging_mixin import LoggingMixin
|
10
18
|
|
19
|
+
|
11
20
|
class PagePropertyManager(LoggingMixin):
|
12
21
|
"""Verwaltet den Zugriff auf und die Änderung von Seiteneigenschaften."""
|
13
|
-
|
14
|
-
def __init__(
|
15
|
-
|
16
|
-
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
page_id: str,
|
26
|
+
client: NotionClient,
|
27
|
+
metadata_editor: MetadataEditor,
|
28
|
+
db_relation: PageDatabaseRelation,
|
29
|
+
):
|
17
30
|
self._page_id = page_id
|
18
31
|
self._client = client
|
19
32
|
self._page_data = None
|
20
33
|
self._metadata_editor = metadata_editor
|
21
34
|
self._db_relation = db_relation
|
22
35
|
self._db_property_service = None
|
23
|
-
|
36
|
+
|
24
37
|
self._extractor = PropertyValueExtractor(self.logger)
|
25
38
|
self._title_resolver = NotionPageTitleResolver(client)
|
26
|
-
|
39
|
+
|
27
40
|
async def get_properties(self) -> Dict[str, Any]:
|
28
41
|
"""Retrieves all properties of the page."""
|
29
42
|
page_data = await self._get_page_data()
|
30
43
|
if page_data and "properties" in page_data:
|
31
44
|
return page_data["properties"]
|
32
45
|
return {}
|
33
|
-
|
46
|
+
|
34
47
|
async def get_property_value(self, property_name: str, relation_getter=None) -> Any:
|
35
48
|
"""
|
36
49
|
Get the value of a specific property.
|
37
|
-
|
50
|
+
|
38
51
|
Args:
|
39
52
|
property_name: Name of the property to get
|
40
53
|
relation_getter: Optional callback function to get relation values
|
@@ -42,105 +55,121 @@ class PagePropertyManager(LoggingMixin):
|
|
42
55
|
properties = await self.get_properties()
|
43
56
|
if property_name not in properties:
|
44
57
|
return None
|
45
|
-
|
58
|
+
|
46
59
|
prop_data = properties[property_name]
|
47
60
|
return await self._extractor.extract(property_name, prop_data, relation_getter)
|
48
|
-
|
49
61
|
|
50
|
-
async def set_property_by_name(
|
62
|
+
async def set_property_by_name(
|
63
|
+
self, property_name: str, value: Any
|
64
|
+
) -> PropertyOperationResult:
|
51
65
|
"""
|
52
66
|
Set a property value by name, automatically detecting the property type.
|
53
|
-
|
67
|
+
|
54
68
|
Args:
|
55
69
|
property_name: Name of the property
|
56
70
|
value: Value to set
|
57
|
-
|
71
|
+
|
58
72
|
Returns:
|
59
|
-
PropertyOperationResult: Result of the operation with status, error messages,
|
73
|
+
PropertyOperationResult: Result of the operation with status, error messages,
|
60
74
|
and available options if applicable
|
61
75
|
"""
|
62
76
|
property_type = await self.get_property_type(property_name)
|
63
|
-
|
77
|
+
|
64
78
|
if property_type == "relation":
|
65
|
-
result = PropertyOperationResult.from_relation_type_error(
|
79
|
+
result = PropertyOperationResult.from_relation_type_error(
|
80
|
+
property_name, value
|
81
|
+
)
|
66
82
|
self.logger.warning(result.error)
|
67
83
|
return result
|
68
|
-
|
84
|
+
|
69
85
|
if not await self._db_relation.is_database_page():
|
70
|
-
api_response = await self._metadata_editor.set_property_by_name(
|
86
|
+
api_response = await self._metadata_editor.set_property_by_name(
|
87
|
+
property_name, value
|
88
|
+
)
|
71
89
|
if api_response:
|
72
90
|
await self.invalidate_cache()
|
73
|
-
return PropertyOperationResult.from_success(
|
91
|
+
return PropertyOperationResult.from_success(
|
92
|
+
property_name, value, api_response
|
93
|
+
)
|
74
94
|
return PropertyOperationResult.from_no_api_response(property_name, value)
|
75
|
-
|
95
|
+
|
76
96
|
db_service = await self._init_db_property_service()
|
77
|
-
|
97
|
+
|
78
98
|
if not db_service:
|
79
|
-
api_response = await self._metadata_editor.set_property_by_name(
|
99
|
+
api_response = await self._metadata_editor.set_property_by_name(
|
100
|
+
property_name, value
|
101
|
+
)
|
80
102
|
if api_response:
|
81
103
|
await self.invalidate_cache()
|
82
|
-
return PropertyOperationResult.from_success(
|
104
|
+
return PropertyOperationResult.from_success(
|
105
|
+
property_name, value, api_response
|
106
|
+
)
|
83
107
|
return PropertyOperationResult.from_no_api_response(property_name, value)
|
84
|
-
|
85
|
-
is_valid, error_message, available_options =
|
86
|
-
property_name, value
|
108
|
+
|
109
|
+
is_valid, error_message, available_options = (
|
110
|
+
await db_service.validate_property_value(property_name, value)
|
87
111
|
)
|
88
|
-
|
112
|
+
|
89
113
|
if not is_valid:
|
90
114
|
if available_options:
|
91
115
|
options_str = "', '".join(available_options)
|
92
116
|
detailed_error = f"{error_message}\nAvailable options for '{property_name}': '{options_str}'"
|
93
117
|
self.logger.warning(detailed_error)
|
94
118
|
else:
|
95
|
-
self.logger.warning(
|
96
|
-
|
119
|
+
self.logger.warning(
|
120
|
+
"%s\nNo valid options available for '%s'",
|
121
|
+
error_message,
|
122
|
+
property_name,
|
123
|
+
)
|
124
|
+
|
97
125
|
return PropertyOperationResult.from_error(
|
98
|
-
property_name,
|
99
|
-
error_message,
|
100
|
-
value,
|
101
|
-
available_options
|
126
|
+
property_name, error_message, value, available_options
|
102
127
|
)
|
103
|
-
|
104
|
-
api_response = await self._metadata_editor.set_property_by_name(
|
128
|
+
|
129
|
+
api_response = await self._metadata_editor.set_property_by_name(
|
130
|
+
property_name, value
|
131
|
+
)
|
105
132
|
if api_response:
|
106
133
|
await self.invalidate_cache()
|
107
|
-
return PropertyOperationResult.from_success(
|
108
|
-
|
134
|
+
return PropertyOperationResult.from_success(
|
135
|
+
property_name, value, api_response
|
136
|
+
)
|
137
|
+
|
109
138
|
return PropertyOperationResult.from_no_api_response(property_name, value)
|
110
|
-
|
139
|
+
|
111
140
|
async def get_property_type(self, property_name: str) -> Optional[str]:
|
112
141
|
"""Gets the type of a specific property."""
|
113
142
|
db_service = await self._init_db_property_service()
|
114
143
|
if db_service:
|
115
144
|
return await db_service.get_property_type(property_name)
|
116
145
|
return None
|
117
|
-
|
146
|
+
|
118
147
|
async def get_available_options_for_property(self, property_name: str) -> List[str]:
|
119
148
|
"""Gets the available option names for a property."""
|
120
149
|
db_service = await self._init_db_property_service()
|
121
150
|
if db_service:
|
122
151
|
return await db_service.get_option_names(property_name)
|
123
152
|
return []
|
124
|
-
|
153
|
+
|
125
154
|
async def _get_page_data(self, force_refresh=False) -> Dict[str, Any]:
|
126
155
|
"""Gets the page data and caches it for future use."""
|
127
156
|
if self._page_data is None or force_refresh:
|
128
157
|
self._page_data = await self._client.get_page(self._page_id)
|
129
158
|
return self._page_data
|
130
|
-
|
159
|
+
|
131
160
|
async def invalidate_cache(self) -> None:
|
132
161
|
"""Forces a refresh of the cached page data on next access."""
|
133
162
|
self._page_data = None
|
134
|
-
|
163
|
+
|
135
164
|
async def _init_db_property_service(self) -> Optional[DatabasePropertyService]:
|
136
165
|
"""Lazily initializes the database property service if needed."""
|
137
166
|
if self._db_property_service is not None:
|
138
167
|
return self._db_property_service
|
139
|
-
|
168
|
+
|
140
169
|
database_id = await self._db_relation.get_parent_database_id()
|
141
170
|
if not database_id:
|
142
171
|
return None
|
143
|
-
|
172
|
+
|
144
173
|
self._db_property_service = DatabasePropertyService(database_id, self._client)
|
145
174
|
await self._db_property_service.load_schema()
|
146
|
-
return self._db_property_service
|
175
|
+
return self._db_property_service
|
@@ -1,11 +1,12 @@
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
2
2
|
from dataclasses import dataclass
|
3
3
|
|
4
|
+
|
4
5
|
@dataclass
|
5
6
|
class PropertyOperationResult:
|
6
7
|
"""
|
7
8
|
Result of a property operation in Notion.
|
8
|
-
|
9
|
+
|
9
10
|
Attributes:
|
10
11
|
success: Whether the operation was successful
|
11
12
|
property_name: Name of the affected property
|
@@ -14,90 +15,102 @@ class PropertyOperationResult:
|
|
14
15
|
available_options: Available options for select-like properties
|
15
16
|
api_response: The original API response
|
16
17
|
"""
|
18
|
+
|
17
19
|
success: bool
|
18
20
|
property_name: str
|
19
21
|
value: Optional[Any] = None
|
20
22
|
error: Optional[str] = None
|
21
23
|
available_options: Optional[List[str]] = None
|
22
24
|
api_response: Optional[Dict[str, Any]] = None
|
23
|
-
|
25
|
+
|
24
26
|
# Common error messages
|
25
27
|
NO_API_RESPONSE = "Failed to set property (no API response)"
|
26
28
|
RELATION_TYPE_ERROR = "Property '{}' is of type 'relation'. Relations must be set using the RelationManager."
|
27
|
-
|
29
|
+
|
28
30
|
@classmethod
|
29
|
-
def from_success(
|
31
|
+
def from_success(
|
32
|
+
cls, property_name: str, value: Any, api_response: Dict[str, Any]
|
33
|
+
) -> "PropertyOperationResult":
|
30
34
|
"""Creates a success result."""
|
31
35
|
return cls(
|
32
|
-
success=True,
|
33
|
-
property_name=property_name,
|
34
|
-
value=value,
|
35
|
-
api_response=api_response
|
36
|
+
success=True,
|
37
|
+
property_name=property_name,
|
38
|
+
value=value,
|
39
|
+
api_response=api_response,
|
36
40
|
)
|
37
|
-
|
41
|
+
|
38
42
|
@classmethod
|
39
|
-
def from_error(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
def from_error(
|
44
|
+
cls,
|
45
|
+
property_name: str,
|
46
|
+
error: str,
|
47
|
+
value: Optional[Any] = None,
|
48
|
+
available_options: Optional[List[str]] = None,
|
49
|
+
) -> "PropertyOperationResult":
|
43
50
|
"""Creates an error result."""
|
44
51
|
return cls(
|
45
52
|
success=False,
|
46
53
|
property_name=property_name,
|
47
54
|
value=value,
|
48
55
|
error=error,
|
49
|
-
available_options=available_options or []
|
56
|
+
available_options=available_options or [],
|
50
57
|
)
|
51
|
-
|
58
|
+
|
52
59
|
@classmethod
|
53
|
-
def from_api_error(
|
60
|
+
def from_api_error(
|
61
|
+
cls, property_name: str, api_response: Dict[str, Any]
|
62
|
+
) -> "PropertyOperationResult":
|
54
63
|
"""Creates a result from an API error response."""
|
55
64
|
return cls(
|
56
65
|
success=False,
|
57
66
|
property_name=property_name,
|
58
67
|
error=api_response.get("message", "Unknown API error"),
|
59
|
-
api_response=api_response
|
68
|
+
api_response=api_response,
|
60
69
|
)
|
61
|
-
|
70
|
+
|
62
71
|
@classmethod
|
63
|
-
def from_no_api_response(
|
72
|
+
def from_no_api_response(
|
73
|
+
cls, property_name: str, value: Optional[Any] = None
|
74
|
+
) -> "PropertyOperationResult":
|
64
75
|
"""Creates a standardized result for missing API responses."""
|
65
76
|
return cls.from_error(property_name, cls.NO_API_RESPONSE, value)
|
66
77
|
|
67
78
|
@classmethod
|
68
|
-
def from_relation_type_error(
|
79
|
+
def from_relation_type_error(
|
80
|
+
cls, property_name: str, value: Optional[Any] = None
|
81
|
+
) -> "PropertyOperationResult":
|
69
82
|
"""Creates a standardized error result for relation type properties."""
|
70
83
|
error_msg = cls.RELATION_TYPE_ERROR.format(property_name)
|
71
84
|
return cls.from_error(property_name, error_msg, value)
|
72
|
-
|
85
|
+
|
73
86
|
def to_dict(self) -> Dict[str, Any]:
|
74
87
|
"""Converts the result to a dictionary."""
|
75
88
|
result = {
|
76
89
|
"success": self.success,
|
77
90
|
"property": self.property_name,
|
78
91
|
}
|
79
|
-
|
92
|
+
|
80
93
|
if self.value is not None:
|
81
94
|
result["value"] = self.value
|
82
|
-
|
95
|
+
|
83
96
|
if not self.success:
|
84
97
|
result["error"] = self.error
|
85
|
-
|
98
|
+
|
86
99
|
if self.available_options:
|
87
100
|
result["available_options"] = self.available_options
|
88
|
-
|
101
|
+
|
89
102
|
if self.api_response:
|
90
103
|
result["api_response"] = self.api_response
|
91
|
-
|
104
|
+
|
92
105
|
return result
|
93
|
-
|
106
|
+
|
94
107
|
def __str__(self) -> str:
|
95
108
|
"""String representation of the result."""
|
96
109
|
if self.success:
|
97
110
|
return f"Success: Property '{self.property_name}' set to '{self.value}'"
|
98
|
-
|
111
|
+
|
99
112
|
if self.available_options:
|
100
113
|
options = "', '".join(self.available_options)
|
101
114
|
return f"Error: {self.error}\nAvailable options for '{self.property_name}': '{options}'"
|
102
|
-
|
103
|
-
return f"Error: {self.error}"
|
115
|
+
|
116
|
+
return f"Error: {self.error}"
|
@@ -10,19 +10,33 @@ class PropertyValueExtractor:
|
|
10
10
|
self,
|
11
11
|
property_name: str,
|
12
12
|
prop_data: dict,
|
13
|
-
relation_resolver: Callable[[str], Awaitable[Any]]
|
13
|
+
relation_resolver: Callable[[str], Awaitable[Any]],
|
14
14
|
) -> Any:
|
15
15
|
prop_type = prop_data.get("type")
|
16
16
|
if not prop_type:
|
17
17
|
return None
|
18
18
|
|
19
19
|
handlers: dict[str, Callable[[], Awaitable[Any] | Any]] = {
|
20
|
-
"title": lambda: "".join(
|
21
|
-
|
20
|
+
"title": lambda: "".join(
|
21
|
+
t.get("plain_text", "") for t in prop_data.get("title", [])
|
22
|
+
),
|
23
|
+
"rich_text": lambda: "".join(
|
24
|
+
t.get("plain_text", "") for t in prop_data.get("rich_text", [])
|
25
|
+
),
|
22
26
|
"number": lambda: prop_data.get("number"),
|
23
|
-
"select": lambda:
|
24
|
-
|
25
|
-
|
27
|
+
"select": lambda: (
|
28
|
+
prop_data.get("select", {}).get("name")
|
29
|
+
if prop_data.get("select")
|
30
|
+
else None
|
31
|
+
),
|
32
|
+
"multi_select": lambda: [
|
33
|
+
o.get("name") for o in prop_data.get("multi_select", [])
|
34
|
+
],
|
35
|
+
"status": lambda: (
|
36
|
+
prop_data.get("status", {}).get("name")
|
37
|
+
if prop_data.get("status")
|
38
|
+
else None
|
39
|
+
),
|
26
40
|
"date": lambda: prop_data.get("date"),
|
27
41
|
"checkbox": lambda: prop_data.get("checkbox"),
|
28
42
|
"url": lambda: prop_data.get("url"),
|
@@ -31,9 +45,13 @@ class PropertyValueExtractor:
|
|
31
45
|
"relation": lambda: relation_resolver(property_name),
|
32
46
|
"people": lambda: [p.get("id") for p in prop_data.get("people", [])],
|
33
47
|
"files": lambda: [
|
34
|
-
|
48
|
+
(
|
49
|
+
f.get("external", {}).get("url")
|
50
|
+
if f.get("type") == "external"
|
51
|
+
else f.get("name")
|
52
|
+
)
|
35
53
|
for f in prop_data.get("files", [])
|
36
|
-
]
|
54
|
+
],
|
37
55
|
}
|
38
56
|
|
39
57
|
handler = handlers.get(prop_type)
|