plexosdb 1.3.4__tar.gz → 1.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: plexosdb
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: SQLite API for plexos XMLs
5
5
  Keywords: PLEXOS,Database,SQLite
6
6
  Author: Pedro Andres Sanchez Perez, Kodi Obika, mcllerena
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plexosdb"
3
- version = "1.3.4"
3
+ version = "1.4.0"
4
4
  readme = "README.md"
5
5
  license = {file = "LICENSE.txt"}
6
6
  keywords = ["PLEXOS", "Database", "SQLite"]
@@ -0,0 +1,601 @@
1
+ """Checks used on plexosdb."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import TYPE_CHECKING
7
+
8
+ from loguru import logger
9
+
10
+ from .enums import ClassEnum, CollectionEnum, Schema
11
+ from .exceptions import NotFoundError
12
+ from .utils import normalize_names
13
+
14
+ if TYPE_CHECKING:
15
+ from .db import PlexosDB
16
+
17
+ MEMBERSHIP_FROM_RECORD_FIELDS = {
18
+ "parent_object_id",
19
+ "child_object_id",
20
+ "collection_id",
21
+ "child_class_id",
22
+ "parent_class_id",
23
+ }
24
+
25
+
26
+ def check_memberships_from_records(memberships: list[dict[str, int]]) -> bool:
27
+ """Validate membership records have the exact required fields.
28
+
29
+ Parameters
30
+ ----------
31
+ memberships : list[dict[str, int]]
32
+ Membership dictionaries expected to contain exactly these keys:
33
+ parent_object_id, child_object_id, collection_id, child_class_id,
34
+ parent_class_id.
35
+
36
+ Returns
37
+ -------
38
+ bool
39
+ True when all records contain exactly the expected keys, otherwise False.
40
+ """
41
+ return all(record.keys() == MEMBERSHIP_FROM_RECORD_FIELDS for record in memberships)
42
+
43
+
44
+ def check_attribute_exists(
45
+ db: PlexosDB, attribute_name: str, /, *, object_name: str, object_class: ClassEnum
46
+ ) -> bool:
47
+ """Check if an attribute exists for a specific object.
48
+
49
+ Parameters
50
+ ----------
51
+ db : PlexosDB
52
+ Database instance.
53
+ attribute_name : str
54
+ Name of the attribute to validate.
55
+ object_name : str
56
+ Name of the target object.
57
+ object_class : ClassEnum
58
+ Class for the target object.
59
+
60
+ Returns
61
+ -------
62
+ bool
63
+ True if the attribute exists for the object.
64
+
65
+ Notes
66
+ -----
67
+ This check is not yet implemented.
68
+ """
69
+ raise NotImplementedError
70
+
71
+
72
+ def check_class_exists(db: PlexosDB, class_enum: ClassEnum) -> bool:
73
+ """Check if a class exists in the database.
74
+
75
+ Determines whether a class with the given enumeration exists in the schema.
76
+
77
+ Parameters
78
+ ----------
79
+ db : PlexosDB
80
+ Database instance.
81
+ class_enum : ClassEnum
82
+ Class enumeration to check.
83
+
84
+ Returns
85
+ -------
86
+ bool
87
+ True if the class exists, False otherwise.
88
+
89
+ See Also
90
+ --------
91
+ PlexosDB.get_class_id : Get the ID for a class.
92
+ PlexosDB.list_classes : List all available classes.
93
+ """
94
+ query = f"SELECT 1 FROM {Schema.Class.name} WHERE name = ?"
95
+ return bool(db._db.query(query, (class_enum,)))
96
+
97
+
98
+ def check_category_exists(db: PlexosDB, class_enum: ClassEnum, name: str) -> bool:
99
+ """Check if a category exists for a specific class.
100
+
101
+ Determines whether a category with the given name exists for the specified
102
+ class.
103
+
104
+ Parameters
105
+ ----------
106
+ db : PlexosDB
107
+ Database instance.
108
+ class_enum : ClassEnum
109
+ Class enumeration to check the category for.
110
+ name : str
111
+ Name of the category to check.
112
+
113
+ Returns
114
+ -------
115
+ bool
116
+ True if the category exists, False otherwise.
117
+
118
+ Raises
119
+ ------
120
+ NotFoundError
121
+ If class_enum does not exist in the database. This indicates a
122
+ programming error because categories are class-scoped.
123
+ """
124
+ if not check_class_exists(db, class_enum):
125
+ msg = (
126
+ f"Class '{class_enum}' does not exist. "
127
+ "Cannot check category for non-existent class. "
128
+ "Use `list_classes()` to see available classes."
129
+ )
130
+ raise NotFoundError(msg)
131
+
132
+ query = f"SELECT 1 FROM {Schema.Categories.name} WHERE name = ? AND class_id = ?"
133
+ class_id = db.get_class_id(class_enum)
134
+ return bool(db._db.query(query, (name, class_id)))
135
+
136
+
137
+ def check_collection_exists(
138
+ db: PlexosDB,
139
+ collection_enum: CollectionEnum,
140
+ /,
141
+ *,
142
+ parent_class: ClassEnum | None = None,
143
+ child_class: ClassEnum | None = None,
144
+ ) -> bool:
145
+ """Check if a collection exists in the database.
146
+
147
+ Determines whether a collection with the given enumeration exists,
148
+ optionally filtered by parent and/or child class.
149
+
150
+ Parameters
151
+ ----------
152
+ db : PlexosDB
153
+ Database instance.
154
+ collection_enum : CollectionEnum
155
+ Collection enumeration to check.
156
+ parent_class : ClassEnum | None, optional
157
+ Parent class enumeration to filter by.
158
+ child_class : ClassEnum | None, optional
159
+ Child class enumeration to filter by.
160
+
161
+ Returns
162
+ -------
163
+ bool
164
+ True if the collection exists (matching all specified criteria),
165
+ False otherwise.
166
+
167
+ Raises
168
+ ------
169
+ NotFoundError
170
+ If parent_class or child_class is specified but does not exist.
171
+
172
+ Notes
173
+ -----
174
+ The method returns False only when the collection itself does not exist or
175
+ does not match the requested class filter.
176
+ """
177
+ conditions = ["name = ?"]
178
+ params: list[str | int] = [str(collection_enum)]
179
+
180
+ if parent_class and not check_class_exists(db, parent_class):
181
+ msg = (
182
+ f"Parent class '{parent_class}' does not exist. "
183
+ "Cannot search for collection with non-existent parent class. "
184
+ "Use `list_classes()` to see available classes."
185
+ )
186
+ raise NotFoundError(msg)
187
+
188
+ if parent_class:
189
+ parent_class_id = db.get_class_id(parent_class)
190
+ conditions.append("parent_class_id = ?")
191
+ params.append(parent_class_id)
192
+
193
+ if child_class is not None and not check_class_exists(db, child_class):
194
+ msg = (
195
+ f"Child class '{child_class}' does not exist. "
196
+ "Cannot search for collection with non-existent child class. "
197
+ "Use `list_classes()` to see available classes."
198
+ )
199
+ raise NotFoundError(msg)
200
+
201
+ if child_class:
202
+ child_class_id = db.get_class_id(child_class)
203
+ conditions.append("child_class_id = ?")
204
+ params.append(child_class_id)
205
+
206
+ where_clause = " AND ".join(conditions)
207
+ query = f"SELECT 1 FROM {Schema.Collection.name} WHERE {where_clause}"
208
+ return bool(db._db.query(query, tuple(params)))
209
+
210
+
211
+ def check_data_id_exist(db: PlexosDB, data_id: int) -> bool:
212
+ """Check that a data id is present on t_data table.
213
+
214
+ Parameters
215
+ ----------
216
+ db : PlexosDB
217
+ Database instance.
218
+ data_id : int
219
+ Data row identifier.
220
+
221
+ Returns
222
+ -------
223
+ bool
224
+ True when data_id exists in t_data, False otherwise.
225
+ """
226
+ query = "SELECT 1 FROM t_data where data_id = ?"
227
+ return bool(db.query(query, (data_id,)))
228
+
229
+
230
+ def check_tag_exists(db: PlexosDB, data_id: int, object_id: int) -> bool:
231
+ """Check if a tag exists linking a data record to an object.
232
+
233
+ Parameters
234
+ ----------
235
+ db : PlexosDB
236
+ Database instance.
237
+ data_id : int
238
+ Data ID to check.
239
+ object_id : int
240
+ Object ID to check.
241
+
242
+ Returns
243
+ -------
244
+ bool
245
+ True if a t_tag row exists for the pair (data_id, object_id),
246
+ otherwise False.
247
+ """
248
+ query = "SELECT 1 FROM t_tag WHERE data_id = ? AND object_id = ?"
249
+ return bool(db.query(query, (data_id, object_id)))
250
+
251
+
252
+ def check_membership_exists(
253
+ db: PlexosDB,
254
+ parent_object_name: str,
255
+ child_object_name: str,
256
+ /,
257
+ *,
258
+ parent_class: ClassEnum,
259
+ child_class: ClassEnum,
260
+ collection: CollectionEnum,
261
+ ) -> bool:
262
+ """Check if a membership exists between two objects.
263
+
264
+ Determines whether a membership relationship exists between the specified
265
+ parent and child objects within the given collection.
266
+
267
+ Parameters
268
+ ----------
269
+ db : PlexosDB
270
+ Database instance.
271
+ parent_object_name : str
272
+ Name of the parent object.
273
+ child_object_name : str
274
+ Name of the child object.
275
+ parent_class : ClassEnum
276
+ Class enumeration of the parent object.
277
+ child_class : ClassEnum
278
+ Class enumeration of the child object.
279
+ collection : CollectionEnum
280
+ Collection enumeration defining the relationship type.
281
+
282
+ Returns
283
+ -------
284
+ bool
285
+ True if the membership exists, False otherwise.
286
+
287
+ Raises
288
+ ------
289
+ NotFoundError
290
+ If parent class, child class, or collection filter is invalid.
291
+ """
292
+ if not check_class_exists(db, parent_class):
293
+ msg = (
294
+ f"Parent class '{parent_class}' does not exist. "
295
+ "Cannot check membership for non-existent parent class. "
296
+ "Use `list_classes()` to see available classes."
297
+ )
298
+ raise NotFoundError(msg)
299
+
300
+ if not check_class_exists(db, child_class):
301
+ msg = (
302
+ f"Child class '{child_class}' does not exist. "
303
+ "Cannot check membership for non-existent child class. "
304
+ "Use `list_classes()` to see available classes."
305
+ )
306
+ raise NotFoundError(msg)
307
+
308
+ if not check_collection_exists(db, collection, parent_class=parent_class, child_class=child_class):
309
+ msg = (
310
+ f"Collection '{collection}' does not exist for "
311
+ f"parent_class={parent_class} and child_class={child_class}. "
312
+ "Check available collections using `list_collections()`"
313
+ )
314
+ raise NotFoundError(msg)
315
+
316
+ parent_object_id = db.get_object_id(parent_class, parent_object_name)
317
+ child_object_id = db.get_object_id(child_class, child_object_name)
318
+ collection_id = db.get_collection_id(collection, parent_class, child_class)
319
+
320
+ query = """
321
+ SELECT 1 FROM t_membership
322
+ WHERE parent_object_id = ?
323
+ AND child_object_id = ?
324
+ AND collection_id = ?
325
+ """
326
+ result = bool(db._db.query(query, (parent_object_id, child_object_id, collection_id)))
327
+ return bool(result)
328
+
329
+
330
+ def check_object_exists(
331
+ db: PlexosDB, class_enum: ClassEnum, /, name: str, *, category: str | None = None
332
+ ) -> bool:
333
+ """Check if an object exists in the database.
334
+
335
+ Determines whether an object with the given name and class exists,
336
+ optionally filtered by category.
337
+
338
+ Parameters
339
+ ----------
340
+ db : PlexosDB
341
+ Database instance.
342
+ class_enum : ClassEnum
343
+ Class enumeration of the object.
344
+ name : str
345
+ Name of the object to check.
346
+ category : str | None, optional
347
+ Category name to filter by.
348
+
349
+ Returns
350
+ -------
351
+ bool
352
+ True if the object exists (and matches category if specified),
353
+ False otherwise.
354
+
355
+ Raises
356
+ ------
357
+ NotFoundError
358
+ If class_enum does not exist in the database.
359
+ """
360
+ if not check_class_exists(db, class_enum):
361
+ msg = (
362
+ f"Class '{class_enum}' does not exist. "
363
+ "Cannot check object for non-existent class. "
364
+ "Use `list_classes()` to see available classes."
365
+ )
366
+ raise NotFoundError(msg)
367
+
368
+ class_id = db.get_class_id(class_enum)
369
+ if category is None:
370
+ query = f"SELECT 1 FROM {Schema.Objects.name} WHERE name = ? AND class_id = ?"
371
+ params: tuple[str, int] | tuple[str, int, str] = (name, class_id)
372
+ else:
373
+ query = f"""
374
+ SELECT 1 FROM {Schema.Objects.name} obj
375
+ JOIN {Schema.Categories.name} cat ON obj.category_id = cat.category_id
376
+ WHERE obj.name = ? AND obj.class_id = ? AND cat.name = ?
377
+ """
378
+ params = (name, class_id, category)
379
+
380
+ return bool(db._db.query(query, params))
381
+
382
+
383
+ def check_property_exists(
384
+ db: PlexosDB,
385
+ collection_enum: CollectionEnum,
386
+ /,
387
+ object_class: ClassEnum,
388
+ property_names: str | Iterable[str],
389
+ *,
390
+ parent_class: ClassEnum | None = None,
391
+ ) -> bool:
392
+ """Check if properties exist for a specific collection and class.
393
+
394
+ Verifies that all specified property names are valid for the given
395
+ collection and class.
396
+
397
+ Parameters
398
+ ----------
399
+ db : PlexosDB
400
+ Database instance.
401
+ collection_enum : CollectionEnum
402
+ Collection enumeration the properties should belong to.
403
+ object_class : ClassEnum
404
+ Class enumeration of the object.
405
+ property_names : str | Iterable[str]
406
+ Property name or names to check.
407
+ parent_class : ClassEnum | None, optional
408
+ Class enumeration of the parent object.
409
+
410
+ Returns
411
+ -------
412
+ bool
413
+ True if all properties exist, False otherwise.
414
+
415
+ Raises
416
+ ------
417
+ NotFoundError
418
+ If parent class, child class, or collection filter is invalid.
419
+
420
+ Notes
421
+ -----
422
+ If any property in the list is invalid, the function returns False and logs
423
+ the invalid list.
424
+ """
425
+ if parent_class and not check_class_exists(db, parent_class):
426
+ msg = (
427
+ f"Parent class '{parent_class}' does not exist. "
428
+ "Cannot check properties for non-existent parent class. "
429
+ "Use `list_classes()` to see available classes."
430
+ )
431
+ raise NotFoundError(msg)
432
+
433
+ if not check_class_exists(db, object_class):
434
+ msg = (
435
+ f"Child class '{object_class}' does not exist. "
436
+ "Cannot check properties for non-existent child class. "
437
+ "Use `list_classes()` to see available classes."
438
+ )
439
+ raise NotFoundError(msg)
440
+
441
+ if not check_collection_exists(
442
+ db,
443
+ collection_enum,
444
+ parent_class=parent_class or ClassEnum.System,
445
+ child_class=object_class,
446
+ ):
447
+ msg = (
448
+ f"Collection '{collection_enum}' does not exist for "
449
+ f"parent_class={parent_class or ClassEnum.System} and child_class={object_class}. "
450
+ "Check available collections using `list_collections()`"
451
+ )
452
+ raise NotFoundError(msg)
453
+
454
+ property_names = normalize_names(property_names)
455
+ valid_props = db.list_valid_properties(
456
+ collection_enum,
457
+ parent_class_enum=parent_class or ClassEnum.System,
458
+ child_class_enum=object_class,
459
+ )
460
+ invalid = [prop for prop in property_names if prop not in valid_props]
461
+ if invalid:
462
+ logger.error("Invalid properties {} for collection {}", property_names, collection_enum)
463
+ return False
464
+ return True
465
+
466
+
467
+ def check_scenario_exists(db: PlexosDB, name: str) -> bool:
468
+ """Check if a scenario exists in the database.
469
+
470
+ Determines whether a scenario with the given name exists.
471
+
472
+ Parameters
473
+ ----------
474
+ db : PlexosDB
475
+ Database instance.
476
+ name : str
477
+ Name of the scenario to check.
478
+
479
+ Returns
480
+ -------
481
+ bool
482
+ True if the scenario exists, False otherwise.
483
+
484
+ See Also
485
+ --------
486
+ PlexosDB.get_class_id : Get the ID for a class.
487
+ ClassEnum.Scenario : Scenario class enumeration.
488
+ """
489
+ query = f"SELECT 1 FROM {Schema.Objects.name} WHERE name = ? AND class_id = ?"
490
+ class_id = db.get_class_id(ClassEnum.Scenario)
491
+ return bool(db._db.query(query, (name, class_id)))
492
+
493
+
494
+ def _check_attribute_exists_method(
495
+ self: PlexosDB, attribute_name: str, /, *, object_name: str, object_class: ClassEnum
496
+ ) -> bool:
497
+ """Check if an attribute exists for a specific object."""
498
+ return check_attribute_exists(self, attribute_name, object_name=object_name, object_class=object_class)
499
+
500
+
501
+ def _check_category_exists_method(self: PlexosDB, class_enum: ClassEnum, name: str) -> bool:
502
+ """Check if a category exists for a specific class."""
503
+ return check_category_exists(self, class_enum, name)
504
+
505
+
506
+ def _check_class_exists_method(self: PlexosDB, class_enum: ClassEnum) -> bool:
507
+ """Check if a class exists in the database."""
508
+ return check_class_exists(self, class_enum)
509
+
510
+
511
+ def _check_collection_exists_method(
512
+ self: PlexosDB,
513
+ collection_enum: CollectionEnum,
514
+ /,
515
+ *,
516
+ parent_class: ClassEnum | None = None,
517
+ child_class: ClassEnum | None = None,
518
+ ) -> bool:
519
+ """Check if a collection exists in the database."""
520
+ return check_collection_exists(
521
+ self,
522
+ collection_enum,
523
+ parent_class=parent_class,
524
+ child_class=child_class,
525
+ )
526
+
527
+
528
+ def _check_data_id_exist_method(self: PlexosDB, data_id: int) -> bool:
529
+ """Check that a data id is present on t_data table."""
530
+ return check_data_id_exist(self, data_id)
531
+
532
+
533
+ def _check_tag_exists_method(self: PlexosDB, data_id: int, object_id: int) -> bool:
534
+ """Check if a tag exists linking a data record to an object."""
535
+ return check_tag_exists(self, data_id, object_id)
536
+
537
+
538
+ def _check_membership_exists_method(
539
+ self: PlexosDB,
540
+ parent_object_name: str,
541
+ child_object_name: str,
542
+ /,
543
+ *,
544
+ parent_class: ClassEnum,
545
+ child_class: ClassEnum,
546
+ collection: CollectionEnum,
547
+ ) -> bool:
548
+ """Check if a membership exists between two objects."""
549
+ return check_membership_exists(
550
+ self,
551
+ parent_object_name,
552
+ child_object_name,
553
+ parent_class=parent_class,
554
+ child_class=child_class,
555
+ collection=collection,
556
+ )
557
+
558
+
559
+ def _check_object_exists_method(
560
+ self: PlexosDB, class_enum: ClassEnum, /, name: str, *, category: str | None = None
561
+ ) -> bool:
562
+ """Check if an object exists in the database."""
563
+ return check_object_exists(self, class_enum, name, category=category)
564
+
565
+
566
+ def _check_property_exists_method(
567
+ self: PlexosDB,
568
+ collection_enum: CollectionEnum,
569
+ /,
570
+ object_class: ClassEnum,
571
+ property_names: str | Iterable[str],
572
+ *,
573
+ parent_class: ClassEnum | None = None,
574
+ ) -> bool:
575
+ """Check if properties exist for a specific collection and class."""
576
+ return check_property_exists(
577
+ self,
578
+ collection_enum,
579
+ object_class,
580
+ property_names,
581
+ parent_class=parent_class,
582
+ )
583
+
584
+
585
+ def _check_scenario_exists_method(self: PlexosDB, name: str) -> bool:
586
+ """Check if a scenario exists in the database."""
587
+ return check_scenario_exists(self, name)
588
+
589
+
590
+ def register_plexosdb_check_methods(plexosdb_cls: type[PlexosDB]) -> None:
591
+ """Attach check_* instance methods to PlexosDB from this module."""
592
+ setattr(plexosdb_cls, "check_attribute_exists", _check_attribute_exists_method)
593
+ setattr(plexosdb_cls, "check_category_exists", _check_category_exists_method)
594
+ setattr(plexosdb_cls, "check_class_exists", _check_class_exists_method)
595
+ setattr(plexosdb_cls, "check_collection_exists", _check_collection_exists_method)
596
+ setattr(plexosdb_cls, "check_data_id_exist", _check_data_id_exist_method)
597
+ setattr(plexosdb_cls, "check_tag_exists", _check_tag_exists_method)
598
+ setattr(plexosdb_cls, "check_membership_exists", _check_membership_exists_method)
599
+ setattr(plexosdb_cls, "check_object_exists", _check_object_exists_method)
600
+ setattr(plexosdb_cls, "check_property_exists", _check_property_exists_method)
601
+ setattr(plexosdb_cls, "check_scenario_exists", _check_scenario_exists_method)