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,17 +1,24 @@
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
2
|
-
from notionary.
|
3
|
-
from notionary.
|
4
|
-
|
2
|
+
from notionary.notion_client import NotionClient
|
3
|
+
from notionary.page.relations.notion_page_title_resolver import (
|
4
|
+
NotionPageTitleResolver,
|
5
|
+
)
|
6
|
+
from notionary.page.relations.relation_operation_result import (
|
7
|
+
RelationOperationResult,
|
8
|
+
)
|
5
9
|
from notionary.util.logging_mixin import LoggingMixin
|
6
10
|
from notionary.util.page_id_utils import is_valid_uuid
|
7
11
|
|
12
|
+
|
8
13
|
class NotionRelationManager(LoggingMixin):
|
9
14
|
"""
|
10
15
|
Manager for relation properties of a Notion page.
|
11
16
|
Manages links between pages and loads available relation options.
|
12
17
|
"""
|
13
18
|
|
14
|
-
def __init__(
|
19
|
+
def __init__(
|
20
|
+
self, page_id: str, client: NotionClient, database_id: Optional[str] = None
|
21
|
+
):
|
15
22
|
"""
|
16
23
|
Initializes the relation manager.
|
17
24
|
|
@@ -75,7 +82,8 @@ class NotionRelationManager(LoggingMixin):
|
|
75
82
|
properties = await self._get_page_properties()
|
76
83
|
|
77
84
|
return [
|
78
|
-
prop_name
|
85
|
+
prop_name
|
86
|
+
for prop_name, prop_data in properties.items()
|
79
87
|
if prop_data.get("type") == "relation"
|
80
88
|
]
|
81
89
|
|
@@ -101,7 +109,9 @@ class NotionRelationManager(LoggingMixin):
|
|
101
109
|
|
102
110
|
return [rel.get("id") for rel in prop_data["relation"]]
|
103
111
|
|
104
|
-
async def get_relation_details(
|
112
|
+
async def get_relation_details(
|
113
|
+
self, property_name: str
|
114
|
+
) -> Optional[Dict[str, Any]]:
|
105
115
|
"""
|
106
116
|
Returns details about the relation property, including the linked database.
|
107
117
|
|
@@ -153,7 +163,9 @@ class NotionRelationManager(LoggingMixin):
|
|
153
163
|
|
154
164
|
return relation_details.get("database_id")
|
155
165
|
|
156
|
-
async def get_relation_options(
|
166
|
+
async def get_relation_options(
|
167
|
+
self, property_name: str, limit: int = 100
|
168
|
+
) -> List[Dict[str, Any]]:
|
157
169
|
"""
|
158
170
|
Returns available options for a relation property.
|
159
171
|
|
@@ -174,7 +186,7 @@ class NotionRelationManager(LoggingMixin):
|
|
174
186
|
f"databases/{related_db_id}/query",
|
175
187
|
{
|
176
188
|
"page_size": limit,
|
177
|
-
}
|
189
|
+
},
|
178
190
|
)
|
179
191
|
|
180
192
|
if not query_result or "results" not in query_result:
|
@@ -186,10 +198,7 @@ class NotionRelationManager(LoggingMixin):
|
|
186
198
|
title = self._extract_title_from_page(page)
|
187
199
|
|
188
200
|
if page_id and title:
|
189
|
-
options.append({
|
190
|
-
"id": page_id,
|
191
|
-
"name": title
|
192
|
-
})
|
201
|
+
options.append({"id": page_id, "name": title})
|
193
202
|
|
194
203
|
return options
|
195
204
|
except Exception as e:
|
@@ -214,11 +223,15 @@ class NotionRelationManager(LoggingMixin):
|
|
214
223
|
for prop_data in properties.values():
|
215
224
|
if prop_data.get("type") == "title" and "title" in prop_data:
|
216
225
|
title_parts = prop_data["title"]
|
217
|
-
return "".join(
|
226
|
+
return "".join(
|
227
|
+
[text_obj.get("plain_text", "") for text_obj in title_parts]
|
228
|
+
)
|
218
229
|
|
219
230
|
return None
|
220
231
|
|
221
|
-
async def add_relation(
|
232
|
+
async def add_relation(
|
233
|
+
self, property_name: str, page_ids: List[str]
|
234
|
+
) -> Optional[Dict[str, Any]]:
|
222
235
|
"""
|
223
236
|
Adds one or more relations.
|
224
237
|
|
@@ -233,18 +246,12 @@ class NotionRelationManager(LoggingMixin):
|
|
233
246
|
|
234
247
|
all_relations = list(set(existing_relations + page_ids))
|
235
248
|
|
236
|
-
relation_payload = {
|
237
|
-
"relation": [{"id": page_id} for page_id in all_relations]
|
238
|
-
}
|
249
|
+
relation_payload = {"relation": [{"id": page_id} for page_id in all_relations]}
|
239
250
|
|
240
251
|
try:
|
241
252
|
result = await self._client.patch(
|
242
253
|
f"pages/{self._page_id}",
|
243
|
-
{
|
244
|
-
"properties": {
|
245
|
-
property_name: relation_payload
|
246
|
-
}
|
247
|
-
},
|
254
|
+
{"properties": {property_name: relation_payload}},
|
248
255
|
)
|
249
256
|
|
250
257
|
self._page_properties = None
|
@@ -254,7 +261,9 @@ class NotionRelationManager(LoggingMixin):
|
|
254
261
|
self.logger.error("Error adding relation: %s", str(e))
|
255
262
|
return None
|
256
263
|
|
257
|
-
async def add_relation_by_name(
|
264
|
+
async def add_relation_by_name(
|
265
|
+
self, property_name: str, page_titles: List[str]
|
266
|
+
) -> RelationOperationResult:
|
258
267
|
"""
|
259
268
|
Adds one or more relations based on page titles.
|
260
269
|
|
@@ -268,9 +277,13 @@ class NotionRelationManager(LoggingMixin):
|
|
268
277
|
found_pages = []
|
269
278
|
not_found_pages = []
|
270
279
|
page_ids = []
|
271
|
-
|
272
|
-
self.logger.info(
|
273
|
-
|
280
|
+
|
281
|
+
self.logger.info(
|
282
|
+
"Attempting to add %d relation(s) to property '%s'",
|
283
|
+
len(page_titles),
|
284
|
+
property_name,
|
285
|
+
)
|
286
|
+
|
274
287
|
for page in page_titles:
|
275
288
|
if is_valid_uuid(page):
|
276
289
|
page_ids.append(page)
|
@@ -287,38 +300,51 @@ class NotionRelationManager(LoggingMixin):
|
|
287
300
|
self.logger.warning("No page found with title '%s'", page)
|
288
301
|
|
289
302
|
if not page_ids:
|
290
|
-
self.logger.warning(
|
291
|
-
|
303
|
+
self.logger.warning(
|
304
|
+
"No valid page IDs found for any of the titles, no changes applied"
|
305
|
+
)
|
306
|
+
return RelationOperationResult.from_no_pages_found(
|
307
|
+
property_name, not_found_pages
|
308
|
+
)
|
292
309
|
|
293
310
|
api_response = await self.add_relation(property_name, page_ids)
|
294
|
-
|
311
|
+
|
295
312
|
if api_response:
|
296
313
|
result = RelationOperationResult.from_success(
|
297
314
|
property_name=property_name,
|
298
315
|
found_pages=found_pages,
|
299
316
|
not_found_pages=not_found_pages,
|
300
317
|
page_ids_added=page_ids,
|
301
|
-
api_response=api_response
|
318
|
+
api_response=api_response,
|
302
319
|
)
|
303
|
-
|
320
|
+
|
304
321
|
if not_found_pages:
|
305
322
|
not_found_str = "', '".join(not_found_pages)
|
306
|
-
self.logger.info(
|
307
|
-
|
323
|
+
self.logger.info(
|
324
|
+
"Added %d relation(s) to '%s', but couldn't find pages: '%s'",
|
325
|
+
len(page_ids),
|
326
|
+
property_name,
|
327
|
+
not_found_str,
|
328
|
+
)
|
308
329
|
else:
|
309
|
-
self.logger.info(
|
310
|
-
|
311
|
-
|
330
|
+
self.logger.info(
|
331
|
+
"Successfully added all %d relation(s) to '%s'",
|
332
|
+
len(page_ids),
|
333
|
+
property_name,
|
334
|
+
)
|
335
|
+
|
312
336
|
return result
|
313
|
-
|
337
|
+
|
314
338
|
self.logger.error("Failed to add relations to '%s' (API error)", property_name)
|
315
339
|
return RelationOperationResult.from_no_api_response(
|
316
340
|
property_name=property_name,
|
317
341
|
found_pages=found_pages,
|
318
|
-
page_ids_added=page_ids
|
342
|
+
page_ids_added=page_ids,
|
319
343
|
)
|
320
344
|
|
321
|
-
async def set_relations(
|
345
|
+
async def set_relations(
|
346
|
+
self, property_name: str, page_ids: List[str]
|
347
|
+
) -> Optional[Dict[str, Any]]:
|
322
348
|
"""
|
323
349
|
Sets the relations to the specified IDs (replaces existing ones).
|
324
350
|
|
@@ -329,18 +355,12 @@ class NotionRelationManager(LoggingMixin):
|
|
329
355
|
Returns:
|
330
356
|
Optional[Dict[str, Any]]: API response or None on error
|
331
357
|
"""
|
332
|
-
relation_payload = {
|
333
|
-
"relation": [{"id": page_id} for page_id in page_ids]
|
334
|
-
}
|
358
|
+
relation_payload = {"relation": [{"id": page_id} for page_id in page_ids]}
|
335
359
|
|
336
360
|
try:
|
337
361
|
result = await self._client.patch(
|
338
362
|
f"pages/{self._page_id}",
|
339
|
-
{
|
340
|
-
"properties": {
|
341
|
-
property_name: relation_payload
|
342
|
-
}
|
343
|
-
},
|
363
|
+
{"properties": {property_name: relation_payload}},
|
344
364
|
)
|
345
365
|
|
346
366
|
self._page_properties = None
|
@@ -349,16 +369,15 @@ class NotionRelationManager(LoggingMixin):
|
|
349
369
|
except Exception as e:
|
350
370
|
self.logger.error("Error setting relations: %s", str(e))
|
351
371
|
return None
|
352
|
-
|
372
|
+
|
353
373
|
async def get_all_relations(self) -> Dict[str, List[str]]:
|
354
|
-
"""
|
355
|
-
"""
|
374
|
+
"""Returns all relation properties and their values."""
|
356
375
|
relation_properties = await self.get_relation_property_ids()
|
357
376
|
if not relation_properties:
|
358
377
|
return {}
|
359
|
-
|
378
|
+
|
360
379
|
result = {}
|
361
380
|
for prop_name in relation_properties:
|
362
381
|
result[prop_name] = await self.get_relation_values(prop_name)
|
363
|
-
|
364
|
-
return result
|
382
|
+
|
383
|
+
return result
|
@@ -1,12 +1,12 @@
|
|
1
1
|
from typing import Optional
|
2
|
-
from notionary.
|
2
|
+
from notionary.notion_client import NotionClient
|
3
3
|
from notionary.util.logging_mixin import LoggingMixin
|
4
4
|
|
5
5
|
|
6
6
|
class NotionPageTitleResolver(LoggingMixin):
|
7
7
|
def __init__(self, client: NotionClient):
|
8
8
|
self._client = client
|
9
|
-
|
9
|
+
|
10
10
|
async def get_page_id_by_title(self, title: str) -> Optional[str]:
|
11
11
|
"""
|
12
12
|
Searches for a Notion page by its title and returns the corresponding page ID if found.
|
@@ -14,13 +14,7 @@ class NotionPageTitleResolver(LoggingMixin):
|
|
14
14
|
try:
|
15
15
|
search_results = await self._client.post(
|
16
16
|
"search",
|
17
|
-
{
|
18
|
-
"query": title,
|
19
|
-
"filter": {
|
20
|
-
"value": "page",
|
21
|
-
"property": "object"
|
22
|
-
}
|
23
|
-
}
|
17
|
+
{"query": title, "filter": {"value": "page", "property": "object"}},
|
24
18
|
)
|
25
19
|
|
26
20
|
for result in search_results.get("results", []):
|
@@ -30,14 +24,20 @@ class NotionPageTitleResolver(LoggingMixin):
|
|
30
24
|
if prop_value.get("type") == "title":
|
31
25
|
title_texts = prop_value.get("title", [])
|
32
26
|
|
33
|
-
page_title = " ".join(
|
27
|
+
page_title = " ".join(
|
28
|
+
[t.get("plain_text", "") for t in title_texts]
|
29
|
+
)
|
34
30
|
|
35
31
|
if page_title == title or title in page_title:
|
36
|
-
self.logger.debug(
|
32
|
+
self.logger.debug(
|
33
|
+
"Found page: '%s' with ID: %s",
|
34
|
+
page_title,
|
35
|
+
result.get("id"),
|
36
|
+
)
|
37
37
|
return result.get("id")
|
38
38
|
|
39
39
|
self.logger.debug("No page found with title '%s'", title)
|
40
40
|
return None
|
41
41
|
except Exception as e:
|
42
42
|
self.logger.error("Error while searching for page '%s': %s", title, e)
|
43
|
-
return None
|
43
|
+
return None
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from typing import Dict, Optional, Any
|
2
|
-
from notionary.
|
2
|
+
from notionary.notion_client import NotionClient
|
3
3
|
from notionary.util.logging_mixin import LoggingMixin
|
4
4
|
|
5
5
|
|
@@ -8,11 +8,11 @@ class PageDatabaseRelation(LoggingMixin):
|
|
8
8
|
Manages the relationship between a Notion page and its parent database.
|
9
9
|
Provides methods to access database schema and property options.
|
10
10
|
"""
|
11
|
-
|
11
|
+
|
12
12
|
def __init__(self, page_id: str, client: NotionClient):
|
13
13
|
"""
|
14
14
|
Initialize the page-database relationship handler.
|
15
|
-
|
15
|
+
|
16
16
|
Args:
|
17
17
|
page_id: ID of the Notion page
|
18
18
|
client: Instance of NotionClient
|
@@ -22,49 +22,49 @@ class PageDatabaseRelation(LoggingMixin):
|
|
22
22
|
self._parent_database_id = None
|
23
23
|
self._database_schema = None
|
24
24
|
self._page_data = None
|
25
|
-
|
25
|
+
|
26
26
|
async def _get_page_data(self, force_refresh=False) -> Dict[str, Any]:
|
27
27
|
"""
|
28
28
|
Gets the page data and caches it for future use.
|
29
|
-
|
29
|
+
|
30
30
|
Args:
|
31
31
|
force_refresh: Whether to force a refresh of the page data
|
32
|
-
|
32
|
+
|
33
33
|
Returns:
|
34
34
|
Dict[str, Any]: The page data
|
35
35
|
"""
|
36
36
|
if self._page_data is None or force_refresh:
|
37
37
|
self._page_data = await self._client.get_page(self._page_id)
|
38
38
|
return self._page_data
|
39
|
-
|
39
|
+
|
40
40
|
async def get_parent_database_id(self) -> Optional[str]:
|
41
41
|
"""
|
42
42
|
Gets the ID of the database this page belongs to, if any.
|
43
|
-
|
43
|
+
|
44
44
|
Returns:
|
45
45
|
Optional[str]: The database ID or None if the page doesn't belong to a database
|
46
46
|
"""
|
47
47
|
if self._parent_database_id is not None:
|
48
48
|
return self._parent_database_id
|
49
|
-
|
49
|
+
|
50
50
|
page_data = await self._get_page_data()
|
51
|
-
|
51
|
+
|
52
52
|
if not page_data or "parent" not in page_data:
|
53
53
|
return None
|
54
|
-
|
54
|
+
|
55
55
|
parent = page_data.get("parent", {})
|
56
56
|
if parent.get("type") == "database_id":
|
57
57
|
self._parent_database_id = parent.get("database_id")
|
58
58
|
return self._parent_database_id
|
59
|
-
|
59
|
+
|
60
60
|
return None
|
61
|
-
|
61
|
+
|
62
62
|
async def is_database_page(self) -> bool:
|
63
63
|
"""
|
64
64
|
Checks if this page belongs to a database.
|
65
|
-
|
65
|
+
|
66
66
|
Returns:
|
67
67
|
bool: True if the page belongs to a database, False otherwise
|
68
68
|
"""
|
69
69
|
database_id = await self.get_parent_database_id()
|
70
|
-
return database_id is not None
|
70
|
+
return database_id is not None
|
@@ -1,11 +1,12 @@
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
2
2
|
from dataclasses import dataclass, field
|
3
3
|
|
4
|
+
|
4
5
|
@dataclass
|
5
6
|
class RelationOperationResult:
|
6
7
|
"""
|
7
8
|
Result of a relation operation in Notion.
|
8
|
-
|
9
|
+
|
9
10
|
Attributes:
|
10
11
|
success: Whether the operation was successful overall
|
11
12
|
property_name: Name of the affected relation property
|
@@ -15,6 +16,7 @@ class RelationOperationResult:
|
|
15
16
|
error: Error message, if any
|
16
17
|
api_response: The original API response
|
17
18
|
"""
|
19
|
+
|
18
20
|
success: bool
|
19
21
|
property_name: str
|
20
22
|
found_pages: List[str] = field(default_factory=list)
|
@@ -22,32 +24,38 @@ class RelationOperationResult:
|
|
22
24
|
page_ids_added: List[str] = field(default_factory=list)
|
23
25
|
error: Optional[str] = None
|
24
26
|
api_response: Optional[Dict[str, Any]] = None
|
25
|
-
|
27
|
+
|
26
28
|
NO_API_RESPONSE = "Failed to update relation (no API response)"
|
27
29
|
NO_PAGES_FOUND = "No valid pages found for relation"
|
28
|
-
|
30
|
+
|
29
31
|
@classmethod
|
30
|
-
def from_success(
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
def from_success(
|
33
|
+
cls,
|
34
|
+
property_name: str,
|
35
|
+
found_pages: List[str],
|
36
|
+
page_ids_added: List[str],
|
37
|
+
api_response: Dict[str, Any],
|
38
|
+
not_found_pages: Optional[List[str]] = None,
|
39
|
+
) -> "RelationOperationResult":
|
35
40
|
"""Creates a success result."""
|
36
41
|
return cls(
|
37
|
-
success=True,
|
42
|
+
success=True,
|
38
43
|
property_name=property_name,
|
39
44
|
found_pages=found_pages,
|
40
45
|
not_found_pages=not_found_pages or [],
|
41
46
|
page_ids_added=page_ids_added,
|
42
|
-
api_response=api_response
|
47
|
+
api_response=api_response,
|
43
48
|
)
|
44
|
-
|
49
|
+
|
45
50
|
@classmethod
|
46
|
-
def from_error(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
def from_error(
|
52
|
+
cls,
|
53
|
+
property_name: str,
|
54
|
+
error: str,
|
55
|
+
found_pages: Optional[List[str]] = None,
|
56
|
+
not_found_pages: Optional[List[str]] = None,
|
57
|
+
page_ids_added: Optional[List[str]] = None,
|
58
|
+
) -> "RelationOperationResult":
|
51
59
|
"""Creates an error result."""
|
52
60
|
return cls(
|
53
61
|
success=False,
|
@@ -55,81 +63,82 @@ class RelationOperationResult:
|
|
55
63
|
found_pages=found_pages or [],
|
56
64
|
not_found_pages=not_found_pages or [],
|
57
65
|
page_ids_added=page_ids_added or [],
|
58
|
-
error=error
|
66
|
+
error=error,
|
59
67
|
)
|
60
|
-
|
68
|
+
|
61
69
|
@classmethod
|
62
|
-
def from_no_pages_found(
|
63
|
-
|
70
|
+
def from_no_pages_found(
|
71
|
+
cls, property_name: str, not_found_pages: List[str]
|
72
|
+
) -> "RelationOperationResult":
|
64
73
|
"""Creates a standardized result for when no pages were found."""
|
65
74
|
return cls.from_error(
|
66
75
|
property_name=property_name,
|
67
76
|
error=cls.NO_PAGES_FOUND,
|
68
|
-
not_found_pages=not_found_pages
|
77
|
+
not_found_pages=not_found_pages,
|
69
78
|
)
|
70
79
|
|
71
80
|
@classmethod
|
72
|
-
def from_no_api_response(
|
73
|
-
|
74
|
-
|
81
|
+
def from_no_api_response(
|
82
|
+
cls, property_name: str, found_pages: List[str], page_ids_added: List[str]
|
83
|
+
) -> "RelationOperationResult":
|
75
84
|
"""Creates a standardized result for a missing API response."""
|
76
85
|
return cls.from_error(
|
77
86
|
property_name=property_name,
|
78
87
|
error=cls.NO_API_RESPONSE,
|
79
88
|
found_pages=found_pages,
|
80
|
-
page_ids_added=page_ids_added
|
89
|
+
page_ids_added=page_ids_added,
|
81
90
|
)
|
82
|
-
|
91
|
+
|
83
92
|
@property
|
84
93
|
def has_not_found_pages(self) -> bool:
|
85
94
|
"""Returns True if there were any pages that couldn't be found."""
|
86
95
|
return len(self.not_found_pages) > 0
|
87
|
-
|
96
|
+
|
88
97
|
@property
|
89
98
|
def has_found_pages(self) -> bool:
|
90
99
|
"""Returns True if any pages were found."""
|
91
100
|
return len(self.found_pages) > 0
|
92
|
-
|
101
|
+
|
93
102
|
def to_dict(self) -> Dict[str, Any]:
|
94
103
|
"""Converts the result to a dictionary."""
|
95
104
|
result = {
|
96
105
|
"success": self.success,
|
97
106
|
"property": self.property_name,
|
98
107
|
}
|
99
|
-
|
108
|
+
|
100
109
|
if self.found_pages:
|
101
110
|
result["found_pages"] = self.found_pages
|
102
|
-
|
111
|
+
|
103
112
|
if self.not_found_pages:
|
104
113
|
result["not_found_pages"] = self.not_found_pages
|
105
|
-
|
114
|
+
|
106
115
|
if self.page_ids_added:
|
107
116
|
result["page_ids_added"] = self.page_ids_added
|
108
|
-
|
117
|
+
|
109
118
|
if not self.success:
|
110
119
|
result["error"] = self.error
|
111
|
-
|
120
|
+
|
112
121
|
if self.api_response:
|
113
122
|
result["api_response"] = self.api_response
|
114
|
-
|
123
|
+
|
115
124
|
return result
|
116
|
-
|
125
|
+
|
117
126
|
def __str__(self) -> str:
|
118
127
|
"""String representation of the result."""
|
119
128
|
if self.success:
|
120
129
|
base = f"Success: Added {len(self.page_ids_added)} relation(s) to property '{self.property_name}'"
|
121
|
-
|
130
|
+
|
122
131
|
if self.not_found_pages:
|
123
132
|
pages_str = "', '".join(self.not_found_pages)
|
124
133
|
base += f"\nWarning: Could not find pages: '{pages_str}'"
|
125
|
-
|
134
|
+
|
126
135
|
return base
|
127
|
-
|
136
|
+
|
128
137
|
if not self.found_pages and self.not_found_pages:
|
129
138
|
pages_str = "', '".join(self.not_found_pages)
|
130
139
|
return f"Error: {self.error}\nNone of the requested pages were found: '{pages_str}'"
|
131
|
-
|
140
|
+
|
132
141
|
if self.found_pages and not self.page_ids_added:
|
133
142
|
return f"Error: {self.error}\nPages were found but could not be added to the relation."
|
134
|
-
|
135
|
-
return f"Error: {self.error}"
|
143
|
+
|
144
|
+
return f"Error: {self.error}"
|
notionary/util/page_id_utils.py
CHANGED
@@ -4,39 +4,45 @@ from typing import Optional
|
|
4
4
|
UUID_PATTERN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
5
5
|
UUID_RAW_PATTERN = r"([a-f0-9]{32})"
|
6
6
|
|
7
|
+
|
7
8
|
def extract_uuid(source: str) -> Optional[str]:
|
8
9
|
if is_valid_uuid(source):
|
9
10
|
return source
|
10
|
-
|
11
|
+
|
11
12
|
match = re.search(UUID_RAW_PATTERN, source.lower())
|
12
13
|
if not match:
|
13
14
|
return None
|
14
|
-
|
15
|
+
|
15
16
|
uuid_raw = match.group(1)
|
16
17
|
return f"{uuid_raw[0:8]}-{uuid_raw[8:12]}-{uuid_raw[12:16]}-{uuid_raw[16:20]}-{uuid_raw[20:32]}"
|
17
18
|
|
19
|
+
|
18
20
|
def is_valid_uuid(uuid: str) -> bool:
|
19
21
|
return bool(re.match(UUID_PATTERN, uuid.lower()))
|
20
22
|
|
23
|
+
|
21
24
|
def format_uuid(value: str) -> Optional[str]:
|
22
25
|
if is_valid_uuid(value):
|
23
26
|
return value
|
24
27
|
return extract_uuid(value)
|
25
28
|
|
26
|
-
|
29
|
+
|
30
|
+
def extract_and_validate_page_id(
|
31
|
+
page_id: Optional[str] = None, url: Optional[str] = None
|
32
|
+
) -> str:
|
27
33
|
if not page_id and not url:
|
28
34
|
raise ValueError("Either page_id or url must be provided")
|
29
|
-
|
35
|
+
|
30
36
|
candidate = page_id or url
|
31
|
-
|
37
|
+
|
32
38
|
if is_valid_uuid(candidate):
|
33
39
|
return candidate
|
34
|
-
|
40
|
+
|
35
41
|
extracted_id = extract_uuid(candidate)
|
36
42
|
if not extracted_id:
|
37
43
|
raise ValueError(f"Could not extract a valid UUID from: {candidate}")
|
38
|
-
|
44
|
+
|
39
45
|
formatted = format_uuid(extracted_id)
|
40
46
|
if not formatted or not is_valid_uuid(formatted):
|
41
47
|
raise ValueError(f"Invalid UUID format: {formatted}")
|
42
|
-
return formatted
|
48
|
+
return formatted
|