notionary 0.2.12__py3-none-any.whl → 0.2.14__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 +3 -20
- notionary/{notion_client.py → base_notion_client.py} +92 -98
- notionary/blocks/__init__.py +61 -0
- notionary/{elements → blocks}/audio_element.py +6 -4
- notionary/{elements → blocks}/bookmark_element.py +3 -6
- notionary/{elements → blocks}/bulleted_list_element.py +5 -7
- notionary/{elements → blocks}/callout_element.py +5 -8
- notionary/{elements → blocks}/code_block_element.py +4 -6
- notionary/{elements → blocks}/column_element.py +3 -6
- notionary/{elements → blocks}/divider_element.py +3 -6
- notionary/{elements → blocks}/embed_element.py +4 -6
- notionary/{elements → blocks}/heading_element.py +5 -9
- notionary/{elements → blocks}/image_element.py +4 -6
- notionary/{elements → blocks}/mention_element.py +3 -7
- notionary/blocks/notion_block_client.py +26 -0
- notionary/blocks/notion_block_element.py +34 -0
- notionary/{elements → blocks}/numbered_list_element.py +4 -7
- notionary/{elements → blocks}/paragraph_element.py +4 -7
- notionary/{prompting/element_prompt_content.py → blocks/prompts/element_prompt_builder.py} +1 -40
- notionary/blocks/prompts/element_prompt_content.py +41 -0
- notionary/{elements → blocks}/qoute_element.py +4 -6
- notionary/{elements → blocks}/registry/block_registry.py +4 -26
- notionary/{elements → blocks}/registry/block_registry_builder.py +26 -25
- notionary/{elements → blocks}/table_element.py +6 -8
- notionary/{elements → blocks}/text_inline_formatter.py +1 -4
- notionary/{elements → blocks}/todo_element.py +6 -8
- notionary/{elements → blocks}/toggle_element.py +3 -6
- notionary/{elements → blocks}/toggleable_heading_element.py +5 -8
- notionary/{elements → blocks}/video_element.py +4 -6
- notionary/cli/main.py +245 -53
- notionary/cli/onboarding.py +117 -0
- notionary/database/__init__.py +0 -0
- notionary/database/client.py +132 -0
- notionary/database/database_exceptions.py +13 -0
- notionary/database/factory.py +0 -0
- notionary/database/filter_builder.py +175 -0
- notionary/database/notion_database.py +339 -128
- notionary/database/notion_database_provider.py +230 -0
- notionary/elements/__init__.py +0 -0
- notionary/models/notion_database_response.py +294 -13
- notionary/models/notion_page_response.py +9 -31
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/client.py +110 -0
- notionary/page/content/page_content_retriever.py +5 -20
- notionary/page/content/page_content_writer.py +3 -4
- notionary/page/formatting/markdown_to_notion_converter.py +1 -3
- notionary/{prompting → page}/markdown_syntax_prompt_generator.py +1 -2
- notionary/page/notion_page.py +354 -317
- notionary/page/notion_to_markdown_converter.py +1 -4
- notionary/page/properites/property_value_extractor.py +0 -64
- notionary/page/{properites/property_formatter.py → property_formatter.py} +7 -4
- notionary/page/search_filter_builder.py +131 -0
- notionary/page/utils.py +60 -0
- notionary/util/__init__.py +12 -3
- notionary/util/factory_decorator.py +33 -0
- notionary/util/fuzzy_matcher.py +82 -0
- notionary/util/page_id_utils.py +0 -21
- notionary/util/singleton_metaclass.py +22 -0
- notionary/workspace.py +69 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/METADATA +4 -1
- notionary-0.2.14.dist-info/RECORD +72 -0
- notionary/database/database_discovery.py +0 -142
- notionary/database/notion_database_factory.py +0 -193
- notionary/elements/notion_block_element.py +0 -70
- notionary/exceptions/database_exceptions.py +0 -76
- notionary/exceptions/page_creation_exception.py +0 -9
- notionary/page/metadata/metadata_editor.py +0 -150
- notionary/page/metadata/notion_icon_manager.py +0 -77
- notionary/page/metadata/notion_page_cover_manager.py +0 -56
- notionary/page/notion_page_factory.py +0 -332
- notionary/page/properites/database_property_service.py +0 -302
- notionary/page/properites/page_property_manager.py +0 -152
- notionary/page/relations/notion_page_relation_manager.py +0 -350
- notionary/page/relations/notion_page_title_resolver.py +0 -104
- notionary/page/relations/page_database_relation.py +0 -68
- notionary/telemetry/__init__.py +0 -7
- notionary/telemetry/telemetry.py +0 -226
- notionary/telemetry/track_usage_decorator.py +0 -76
- notionary/util/warn_direct_constructor_usage.py +0 -54
- notionary-0.2.12.dist-info/RECORD +0 -70
- /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/top_level.txt +0 -0
@@ -1,302 +0,0 @@
|
|
1
|
-
from typing import Dict, List, Optional, Any, Tuple
|
2
|
-
from notionary.notion_client import NotionClient
|
3
|
-
from notionary.util import LoggingMixin
|
4
|
-
|
5
|
-
|
6
|
-
class DatabasePropertyService(LoggingMixin):
|
7
|
-
"""
|
8
|
-
Service for working with Notion database properties and options.
|
9
|
-
Provides specialized methods for retrieving property information and validating values.
|
10
|
-
"""
|
11
|
-
|
12
|
-
def __init__(self, database_id: str, client: NotionClient):
|
13
|
-
"""
|
14
|
-
Initialize the database property service.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
database_id: ID of the Notion database
|
18
|
-
client: Instance of NotionClient
|
19
|
-
"""
|
20
|
-
self._database_id = database_id
|
21
|
-
self._client = client
|
22
|
-
self._schema = None
|
23
|
-
|
24
|
-
async def load_schema(self, force_refresh: bool = False) -> bool:
|
25
|
-
"""
|
26
|
-
Loads the database schema.
|
27
|
-
|
28
|
-
Args:
|
29
|
-
force_refresh: Whether to force a refresh of the schema
|
30
|
-
|
31
|
-
Returns:
|
32
|
-
True if schema loaded successfully, False otherwise.
|
33
|
-
"""
|
34
|
-
if self._schema is not None and not force_refresh:
|
35
|
-
return True
|
36
|
-
|
37
|
-
try:
|
38
|
-
database = await self._client.get_database(self._database_id)
|
39
|
-
|
40
|
-
self._schema = database.properties
|
41
|
-
self.logger.debug("Loaded schema for database %s", self._database_id)
|
42
|
-
return True
|
43
|
-
|
44
|
-
except Exception as e:
|
45
|
-
self.logger.error(
|
46
|
-
"Error loading database schema for %s: %s", self._database_id, str(e)
|
47
|
-
)
|
48
|
-
return False
|
49
|
-
|
50
|
-
async def _ensure_schema_loaded(self) -> None:
|
51
|
-
"""
|
52
|
-
Ensures the schema is loaded before accessing it.
|
53
|
-
"""
|
54
|
-
if self._schema is None:
|
55
|
-
await self.load_schema()
|
56
|
-
|
57
|
-
async def get_schema(self) -> Dict[str, Any]:
|
58
|
-
"""
|
59
|
-
Gets the database schema.
|
60
|
-
|
61
|
-
Returns:
|
62
|
-
Dict[str, Any]: The database schema
|
63
|
-
"""
|
64
|
-
await self._ensure_schema_loaded()
|
65
|
-
return self._schema or {}
|
66
|
-
|
67
|
-
async def get_property_types(self) -> Dict[str, str]:
|
68
|
-
"""
|
69
|
-
Gets all property types for the database.
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
Dict[str, str]: Dictionary mapping property names to their types
|
73
|
-
"""
|
74
|
-
await self._ensure_schema_loaded()
|
75
|
-
|
76
|
-
if not self._schema:
|
77
|
-
return {}
|
78
|
-
|
79
|
-
return {
|
80
|
-
prop_name: prop_data.get("type", "unknown")
|
81
|
-
for prop_name, prop_data in self._schema.items()
|
82
|
-
}
|
83
|
-
|
84
|
-
async def get_property_schema(self, property_name: str) -> Optional[Dict[str, Any]]:
|
85
|
-
"""
|
86
|
-
Gets the schema for a specific property.
|
87
|
-
|
88
|
-
Args:
|
89
|
-
property_name: The name of the property
|
90
|
-
|
91
|
-
Returns:
|
92
|
-
Optional[Dict[str, Any]]: The property schema or None if not found
|
93
|
-
"""
|
94
|
-
await self._ensure_schema_loaded()
|
95
|
-
|
96
|
-
if not self._schema or property_name not in self._schema:
|
97
|
-
return None
|
98
|
-
|
99
|
-
return self._schema[property_name]
|
100
|
-
|
101
|
-
async def get_property_type(self, property_name: str) -> Optional[str]:
|
102
|
-
"""
|
103
|
-
Gets the type of a specific property.
|
104
|
-
|
105
|
-
Args:
|
106
|
-
property_name: The name of the property
|
107
|
-
|
108
|
-
Returns:
|
109
|
-
Optional[str]: The property type or None if not found
|
110
|
-
"""
|
111
|
-
property_schema = await self.get_property_schema(property_name)
|
112
|
-
|
113
|
-
if not property_schema:
|
114
|
-
return None
|
115
|
-
|
116
|
-
return property_schema.get("type")
|
117
|
-
|
118
|
-
async def property_exists(self, property_name: str) -> bool:
|
119
|
-
"""
|
120
|
-
Checks if a property exists in the database.
|
121
|
-
|
122
|
-
Args:
|
123
|
-
property_name: The name of the property
|
124
|
-
|
125
|
-
Returns:
|
126
|
-
bool: True if the property exists, False otherwise
|
127
|
-
"""
|
128
|
-
property_schema = await self.get_property_schema(property_name)
|
129
|
-
return property_schema is not None
|
130
|
-
|
131
|
-
async def get_property_options(self, property_name: str) -> List[Dict[str, Any]]:
|
132
|
-
"""
|
133
|
-
Gets the available options for a property (select, multi_select, status).
|
134
|
-
|
135
|
-
Args:
|
136
|
-
property_name: The name of the property
|
137
|
-
|
138
|
-
Returns:
|
139
|
-
List[Dict[str, Any]]: List of available options with their metadata
|
140
|
-
"""
|
141
|
-
property_schema = await self.get_property_schema(property_name)
|
142
|
-
|
143
|
-
if not property_schema:
|
144
|
-
return []
|
145
|
-
|
146
|
-
property_type = property_schema.get("type")
|
147
|
-
|
148
|
-
if property_type in ["select", "multi_select", "status"]:
|
149
|
-
return property_schema.get(property_type, {}).get("options", [])
|
150
|
-
|
151
|
-
return []
|
152
|
-
|
153
|
-
async def get_option_names(self, property_name: str) -> List[str]:
|
154
|
-
"""
|
155
|
-
Gets the available option names for a property (select, multi_select, status).
|
156
|
-
|
157
|
-
Args:
|
158
|
-
property_name: The name of the property
|
159
|
-
|
160
|
-
Returns:
|
161
|
-
List[str]: List of available option names
|
162
|
-
"""
|
163
|
-
options = await self.get_property_options(property_name)
|
164
|
-
return [option.get("name", "") for option in options]
|
165
|
-
|
166
|
-
async def get_relation_details(
|
167
|
-
self, property_name: str
|
168
|
-
) -> Optional[Dict[str, Any]]:
|
169
|
-
"""
|
170
|
-
Gets details about a relation property, including the related database.
|
171
|
-
|
172
|
-
Args:
|
173
|
-
property_name: The name of the property
|
174
|
-
|
175
|
-
Returns:
|
176
|
-
Optional[Dict[str, Any]]: The relation details or None if not a relation
|
177
|
-
"""
|
178
|
-
property_schema = await self.get_property_schema(property_name)
|
179
|
-
|
180
|
-
if not property_schema or property_schema.get("type") != "relation":
|
181
|
-
return None
|
182
|
-
|
183
|
-
return property_schema.get("relation", {})
|
184
|
-
|
185
|
-
async def get_relation_options(
|
186
|
-
self, property_name: str, limit: int = 100
|
187
|
-
) -> List[Dict[str, Any]]:
|
188
|
-
"""
|
189
|
-
Gets available options for a relation property by querying the related database.
|
190
|
-
|
191
|
-
Args:
|
192
|
-
property_name: The name of the relation property
|
193
|
-
limit: Maximum number of options to retrieve
|
194
|
-
|
195
|
-
Returns:
|
196
|
-
List[Dict[str, Any]]: List of pages from the related database
|
197
|
-
"""
|
198
|
-
relation_details = await self.get_relation_details(property_name)
|
199
|
-
|
200
|
-
if not relation_details or "database_id" not in relation_details:
|
201
|
-
return []
|
202
|
-
|
203
|
-
related_db_id = relation_details["database_id"]
|
204
|
-
|
205
|
-
try:
|
206
|
-
# Query the related database to get options
|
207
|
-
query_result = await self._client.post(
|
208
|
-
f"databases/{related_db_id}/query",
|
209
|
-
{
|
210
|
-
"page_size": limit,
|
211
|
-
},
|
212
|
-
)
|
213
|
-
|
214
|
-
if not query_result or "results" not in query_result:
|
215
|
-
return []
|
216
|
-
|
217
|
-
# Extract relevant information from each page
|
218
|
-
options = []
|
219
|
-
for page in query_result["results"]:
|
220
|
-
page_id = page.get("id")
|
221
|
-
title = self._extract_title_from_page(page)
|
222
|
-
|
223
|
-
if page_id and title:
|
224
|
-
options.append({"id": page_id, "name": title})
|
225
|
-
|
226
|
-
return options
|
227
|
-
except Exception as e:
|
228
|
-
self.logger.error(f"Error getting relation options: {str(e)}")
|
229
|
-
return []
|
230
|
-
|
231
|
-
def _extract_title_from_page(self, page: Dict[str, Any]) -> Optional[str]:
|
232
|
-
"""
|
233
|
-
Extracts the title from a page object.
|
234
|
-
|
235
|
-
Args:
|
236
|
-
page: The page object from Notion API
|
237
|
-
|
238
|
-
Returns:
|
239
|
-
Optional[str]: The page title or None if not found
|
240
|
-
"""
|
241
|
-
if "properties" not in page:
|
242
|
-
return None
|
243
|
-
|
244
|
-
properties = page["properties"]
|
245
|
-
|
246
|
-
# Look for a title property
|
247
|
-
for prop_data in properties.values():
|
248
|
-
if prop_data.get("type") == "title" and "title" in prop_data:
|
249
|
-
title_parts = prop_data["title"]
|
250
|
-
return "".join(
|
251
|
-
[text_obj.get("plain_text", "") for text_obj in title_parts]
|
252
|
-
)
|
253
|
-
|
254
|
-
return None
|
255
|
-
|
256
|
-
async def validate_property_value(
|
257
|
-
self, property_name: str, value: Any
|
258
|
-
) -> Tuple[bool, Optional[str], Optional[List[str]]]:
|
259
|
-
"""
|
260
|
-
Validates a value for a property.
|
261
|
-
|
262
|
-
Args:
|
263
|
-
property_name: The name of the property
|
264
|
-
value: The value to validate
|
265
|
-
|
266
|
-
Returns:
|
267
|
-
Tuple[bool, Optional[str], Optional[List[str]]]:
|
268
|
-
- Boolean indicating if valid
|
269
|
-
- Error message if invalid
|
270
|
-
- Available options if applicable
|
271
|
-
"""
|
272
|
-
property_schema = await self.get_property_schema(property_name)
|
273
|
-
|
274
|
-
if not property_schema:
|
275
|
-
return False, f"Property '{property_name}' does not exist", None
|
276
|
-
|
277
|
-
property_type = property_schema.get("type")
|
278
|
-
|
279
|
-
# Validate select, multi_select, status properties
|
280
|
-
if property_type in ["select", "status"]:
|
281
|
-
options = await self.get_option_names(property_name)
|
282
|
-
|
283
|
-
if isinstance(value, str) and value not in options:
|
284
|
-
return (
|
285
|
-
False,
|
286
|
-
f"Invalid {property_type} option. Value '{value}' is not in the available options.",
|
287
|
-
options,
|
288
|
-
)
|
289
|
-
|
290
|
-
elif property_type == "multi_select":
|
291
|
-
options = await self.get_option_names(property_name)
|
292
|
-
|
293
|
-
if isinstance(value, list):
|
294
|
-
invalid_values = [val for val in value if val not in options]
|
295
|
-
if invalid_values:
|
296
|
-
return (
|
297
|
-
False,
|
298
|
-
f"Invalid multi_select options: {', '.join(invalid_values)}",
|
299
|
-
options,
|
300
|
-
)
|
301
|
-
|
302
|
-
return True, None, None
|
@@ -1,152 +0,0 @@
|
|
1
|
-
from typing import Dict, Any, List, Optional
|
2
|
-
from notionary.models.notion_page_response import NotionPageResponse
|
3
|
-
from notionary.notion_client import NotionClient
|
4
|
-
from notionary.page.metadata.metadata_editor import MetadataEditor
|
5
|
-
from notionary.page.properites.database_property_service import (
|
6
|
-
DatabasePropertyService,
|
7
|
-
)
|
8
|
-
from notionary.page.relations.page_database_relation import PageDatabaseRelation
|
9
|
-
from notionary.page.properites.property_value_extractor import (
|
10
|
-
PropertyValueExtractor,
|
11
|
-
)
|
12
|
-
from notionary.util import LoggingMixin
|
13
|
-
|
14
|
-
|
15
|
-
class PagePropertyManager(LoggingMixin):
|
16
|
-
"""Verwaltet den Zugriff auf und die Änderung von Seiteneigenschaften."""
|
17
|
-
|
18
|
-
def __init__(
|
19
|
-
self,
|
20
|
-
page_id: str,
|
21
|
-
client: NotionClient,
|
22
|
-
metadata_editor: MetadataEditor,
|
23
|
-
db_relation: PageDatabaseRelation,
|
24
|
-
):
|
25
|
-
self._page_id = page_id
|
26
|
-
self._client = client
|
27
|
-
self._page_data = None
|
28
|
-
self._metadata_editor = metadata_editor
|
29
|
-
self._db_relation = db_relation
|
30
|
-
self._db_property_service = None
|
31
|
-
|
32
|
-
self._extractor = PropertyValueExtractor()
|
33
|
-
|
34
|
-
async def get_property_value(self, property_name: str, relation_getter=None) -> Any:
|
35
|
-
"""
|
36
|
-
Get the value of a specific property.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
property_name: Name of the property to get
|
40
|
-
relation_getter: Optional callback function to get relation values
|
41
|
-
"""
|
42
|
-
properties = await self._get_properties()
|
43
|
-
if property_name not in properties:
|
44
|
-
return None
|
45
|
-
|
46
|
-
prop_data = properties[property_name]
|
47
|
-
return await self._extractor.extract(property_name, prop_data, relation_getter)
|
48
|
-
|
49
|
-
async def set_property_by_name(
|
50
|
-
self, property_name: str, value: Any
|
51
|
-
) -> Optional[Any]:
|
52
|
-
"""
|
53
|
-
Set a property value by name, automatically detecting the property type.
|
54
|
-
|
55
|
-
Args:
|
56
|
-
property_name: Name of the property
|
57
|
-
value: Value to set
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
Optional[Any]: The new value if successful, None if failed
|
61
|
-
"""
|
62
|
-
property_type = await self.get_property_type(property_name)
|
63
|
-
|
64
|
-
if property_type == "relation":
|
65
|
-
self.logger.warning(
|
66
|
-
"Property '%s' is of type 'relation'. Relations must be set using the RelationManager.",
|
67
|
-
property_name,
|
68
|
-
)
|
69
|
-
return None
|
70
|
-
|
71
|
-
is_db_page = await self._db_relation.is_database_page()
|
72
|
-
db_service = None
|
73
|
-
|
74
|
-
if is_db_page:
|
75
|
-
db_service = await self._init_db_property_service()
|
76
|
-
|
77
|
-
if db_service:
|
78
|
-
is_valid, error_message, available_options = (
|
79
|
-
await db_service.validate_property_value(property_name, value)
|
80
|
-
)
|
81
|
-
|
82
|
-
if not is_valid:
|
83
|
-
if available_options:
|
84
|
-
options_str = "', '".join(available_options)
|
85
|
-
self.logger.warning(
|
86
|
-
"%s\nAvailable options for '%s': '%s'",
|
87
|
-
error_message,
|
88
|
-
property_name,
|
89
|
-
options_str,
|
90
|
-
)
|
91
|
-
else:
|
92
|
-
self.logger.warning(
|
93
|
-
"%s\nNo valid options available for '%s'",
|
94
|
-
error_message,
|
95
|
-
property_name,
|
96
|
-
)
|
97
|
-
return None
|
98
|
-
|
99
|
-
api_response = await self._metadata_editor.set_property_by_name(
|
100
|
-
property_name, value
|
101
|
-
)
|
102
|
-
|
103
|
-
if api_response:
|
104
|
-
await self.invalidate_cache()
|
105
|
-
return value
|
106
|
-
|
107
|
-
self.logger.warning(
|
108
|
-
"Failed to set property '%s' (no API response)", property_name
|
109
|
-
)
|
110
|
-
return None
|
111
|
-
|
112
|
-
async def get_property_type(self, property_name: str) -> Optional[str]:
|
113
|
-
"""Gets the type of a specific property."""
|
114
|
-
db_service = await self._init_db_property_service()
|
115
|
-
if db_service:
|
116
|
-
return await db_service.get_property_type(property_name)
|
117
|
-
return None
|
118
|
-
|
119
|
-
async def get_available_options_for_property(self, property_name: str) -> List[str]:
|
120
|
-
"""Gets the available option names for a property."""
|
121
|
-
db_service = await self._init_db_property_service()
|
122
|
-
if db_service:
|
123
|
-
return await db_service.get_option_names(property_name)
|
124
|
-
return []
|
125
|
-
|
126
|
-
async def _get_page_data(self, force_refresh=False) -> NotionPageResponse:
|
127
|
-
"""Gets the page data and caches it for future use."""
|
128
|
-
if self._page_data is None or force_refresh:
|
129
|
-
self._page_data = await self._client.get_page(self._page_id)
|
130
|
-
return self._page_data
|
131
|
-
|
132
|
-
async def invalidate_cache(self) -> None:
|
133
|
-
"""Forces a refresh of the cached page data on next access."""
|
134
|
-
self._page_data = None
|
135
|
-
|
136
|
-
async def _init_db_property_service(self) -> Optional[DatabasePropertyService]:
|
137
|
-
"""Lazily initializes the database property service if needed."""
|
138
|
-
if self._db_property_service is not None:
|
139
|
-
return self._db_property_service
|
140
|
-
|
141
|
-
database_id = await self._db_relation.get_parent_database_id()
|
142
|
-
if not database_id:
|
143
|
-
return None
|
144
|
-
|
145
|
-
self._db_property_service = DatabasePropertyService(database_id, self._client)
|
146
|
-
await self._db_property_service.load_schema()
|
147
|
-
return self._db_property_service
|
148
|
-
|
149
|
-
async def _get_properties(self) -> Dict[str, Any]:
|
150
|
-
"""Retrieves all properties of the page."""
|
151
|
-
page_data = await self._get_page_data()
|
152
|
-
return page_data.properties if page_data.properties else {}
|