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