plexosdb 1.4.0__tar.gz → 1.5.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.
- {plexosdb-1.4.0 → plexosdb-1.5.0}/PKG-INFO +1 -1
- {plexosdb-1.4.0 → plexosdb-1.5.0}/pyproject.toml +1 -1
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/checks.py +14 -6
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/db.py +300 -18
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/utils.py +33 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/LICENSE.txt +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/README.md +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/__init__.py +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/__main__.py +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/db_manager.py +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/enums.py +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/exceptions.py +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/py.typed +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/schema.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/sql/object_properties.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/sql/object_query.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/sql/property.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/sql/property_query.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/sql/simple_object_query.sql +0 -0
- {plexosdb-1.4.0 → plexosdb-1.5.0}/src/plexosdb/xml_handler.py +0 -0
|
@@ -60,13 +60,21 @@ def check_attribute_exists(
|
|
|
60
60
|
Returns
|
|
61
61
|
-------
|
|
62
62
|
bool
|
|
63
|
-
True if the
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
True if the object has an assigned value for the attribute.
|
|
64
|
+
"""
|
|
65
|
+
query = """
|
|
66
|
+
SELECT 1
|
|
67
|
+
FROM t_attribute_data AS data
|
|
68
|
+
JOIN t_object AS obj ON obj.object_id = data.object_id
|
|
69
|
+
JOIN t_attribute AS attr ON attr.attribute_id = data.attribute_id
|
|
70
|
+
JOIN t_class AS class ON class.class_id = obj.class_id
|
|
71
|
+
WHERE obj.name = ?
|
|
72
|
+
AND class.name = ?
|
|
73
|
+
AND attr.name = ?
|
|
74
|
+
AND attr.class_id = obj.class_id
|
|
75
|
+
LIMIT 1
|
|
68
76
|
"""
|
|
69
|
-
|
|
77
|
+
return bool(db._db.query(query, (object_name, object_class, attribute_name)))
|
|
70
78
|
|
|
71
79
|
|
|
72
80
|
def check_class_exists(db: PlexosDB, class_enum: ClassEnum) -> bool:
|
|
@@ -40,6 +40,7 @@ from .utils import (
|
|
|
40
40
|
normalize_names,
|
|
41
41
|
plan_property_inserts,
|
|
42
42
|
resolve_membership_id,
|
|
43
|
+
_normalize_attribute_records,
|
|
43
44
|
)
|
|
44
45
|
from .xml_handler import XMLHandler
|
|
45
46
|
|
|
@@ -281,10 +282,6 @@ class PlexosDB:
|
|
|
281
282
|
-------
|
|
282
283
|
int
|
|
283
284
|
attribute_id
|
|
284
|
-
|
|
285
|
-
Notes
|
|
286
|
-
-----
|
|
287
|
-
By default, we add all objects to the system membership.
|
|
288
285
|
"""
|
|
289
286
|
object_id = self.get_object_id(object_class, name=object_name)
|
|
290
287
|
attribute_id = self.get_attribute_id(object_class, name=attribute_name)
|
|
@@ -293,7 +290,8 @@ class PlexosDB:
|
|
|
293
290
|
query = (
|
|
294
291
|
f"INSERT INTO {Schema.AttributeData.name}(object_id, attribute_id, value) VALUES({placeholders})"
|
|
295
292
|
)
|
|
296
|
-
|
|
293
|
+
result = self._db.execute(query, params)
|
|
294
|
+
assert result, f"Failed to add attribute '{attribute_name}' to object '{object_name}'."
|
|
297
295
|
return attribute_id
|
|
298
296
|
|
|
299
297
|
def add_band(
|
|
@@ -961,6 +959,140 @@ class PlexosDB:
|
|
|
961
959
|
logger.debug(f"Successfully processed {len(records)} property and text records in batches")
|
|
962
960
|
return
|
|
963
961
|
|
|
962
|
+
def add_attributes_from_records(
|
|
963
|
+
self,
|
|
964
|
+
records: list[dict[str, Any]],
|
|
965
|
+
/,
|
|
966
|
+
*,
|
|
967
|
+
object_class: ClassEnum,
|
|
968
|
+
chunksize: int = 10_000,
|
|
969
|
+
) -> None:
|
|
970
|
+
"""Bulk insert attribute values for objects.
|
|
971
|
+
|
|
972
|
+
Efficiently adds multiple object-level attribute values in batches.
|
|
973
|
+
This method is much more efficient than calling `add_attribute` repeatedly.
|
|
974
|
+
|
|
975
|
+
Each record should contain:
|
|
976
|
+
- 'name': object name
|
|
977
|
+
- 'attribute': attribute name
|
|
978
|
+
- 'value': attribute value
|
|
979
|
+
|
|
980
|
+
Alternatively, records may be provided in the following format:
|
|
981
|
+
- {'name': ..., 'Attr1': val1, 'Attr2': val2}
|
|
982
|
+
|
|
983
|
+
Optional:
|
|
984
|
+
- 'state'
|
|
985
|
+
|
|
986
|
+
Parameters
|
|
987
|
+
----------
|
|
988
|
+
records : list[dict[str, Any]]
|
|
989
|
+
List of attribute records in explicit or wide format.
|
|
990
|
+
object_class : ClassEnum
|
|
991
|
+
Class of the objects.
|
|
992
|
+
chunksize : int, optional
|
|
993
|
+
Batch size for inserts, by default 10_000.
|
|
994
|
+
|
|
995
|
+
Returns
|
|
996
|
+
-------
|
|
997
|
+
None
|
|
998
|
+
|
|
999
|
+
See Also
|
|
1000
|
+
--------
|
|
1001
|
+
add_attribute
|
|
1002
|
+
add_properties_from_records
|
|
1003
|
+
add_memberships_from_records
|
|
1004
|
+
|
|
1005
|
+
Examples
|
|
1006
|
+
--------
|
|
1007
|
+
>>> records = [
|
|
1008
|
+
... {"name": "2020", "Step Type": 4.0, "Chrono Step Count": 366.0},
|
|
1009
|
+
... ]
|
|
1010
|
+
|
|
1011
|
+
>>> db.add_attributes_from_records(records, object_class=ClassEnum.Horizon)
|
|
1012
|
+
"""
|
|
1013
|
+
if not records:
|
|
1014
|
+
logger.warning("No records provided for bulk attribute insertion")
|
|
1015
|
+
return
|
|
1016
|
+
|
|
1017
|
+
records = _normalize_attribute_records(records)
|
|
1018
|
+
|
|
1019
|
+
if chunksize < 1:
|
|
1020
|
+
msg = f"chunksize must be >= 1, received {chunksize}"
|
|
1021
|
+
raise ValueError(msg)
|
|
1022
|
+
|
|
1023
|
+
class_id = self.get_class_id(object_class)
|
|
1024
|
+
|
|
1025
|
+
object_names = tuple({record["name"] for record in records})
|
|
1026
|
+
name_to_object_id: dict[str, int] = {}
|
|
1027
|
+
|
|
1028
|
+
CHUNK = 900 # noqa: N806
|
|
1029
|
+
for i in range(0, len(object_names), CHUNK):
|
|
1030
|
+
chunk = object_names[i : i + CHUNK]
|
|
1031
|
+
object_placeholders = ", ".join("?" for _ in chunk)
|
|
1032
|
+
|
|
1033
|
+
object_rows = self._db.query(
|
|
1034
|
+
f"""
|
|
1035
|
+
SELECT object_id, name
|
|
1036
|
+
FROM t_object
|
|
1037
|
+
WHERE class_id = ?
|
|
1038
|
+
AND name IN ({object_placeholders})
|
|
1039
|
+
""",
|
|
1040
|
+
(class_id, *chunk),
|
|
1041
|
+
)
|
|
1042
|
+
name_to_object_id.update({name: object_id for object_id, name in object_rows})
|
|
1043
|
+
|
|
1044
|
+
attribute_rows = self._db.query(
|
|
1045
|
+
"""
|
|
1046
|
+
SELECT attribute_id, name
|
|
1047
|
+
FROM t_attribute
|
|
1048
|
+
WHERE class_id = ?
|
|
1049
|
+
""",
|
|
1050
|
+
(class_id,),
|
|
1051
|
+
)
|
|
1052
|
+
name_to_attribute_id = {name: attribute_id for attribute_id, name in attribute_rows}
|
|
1053
|
+
|
|
1054
|
+
params: list[tuple[int, int, Any, Any]] = []
|
|
1055
|
+
seen: set[tuple[int, int]] = set()
|
|
1056
|
+
|
|
1057
|
+
for record in records:
|
|
1058
|
+
try:
|
|
1059
|
+
object_id = name_to_object_id[record["name"]]
|
|
1060
|
+
attribute_id = name_to_attribute_id[record["attribute"]]
|
|
1061
|
+
except KeyError as exc:
|
|
1062
|
+
raise KeyError(f"Invalid attribute record: {record}") from exc
|
|
1063
|
+
|
|
1064
|
+
key = (object_id, attribute_id)
|
|
1065
|
+
if key in seen:
|
|
1066
|
+
raise ValueError(
|
|
1067
|
+
f"Duplicate attribute record for object={record['name']!r}, "
|
|
1068
|
+
f"attribute={record['attribute']!r}"
|
|
1069
|
+
)
|
|
1070
|
+
seen.add(key)
|
|
1071
|
+
|
|
1072
|
+
params.append(
|
|
1073
|
+
(
|
|
1074
|
+
object_id,
|
|
1075
|
+
attribute_id,
|
|
1076
|
+
record["value"],
|
|
1077
|
+
record.get("state"),
|
|
1078
|
+
)
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
query = f"""
|
|
1082
|
+
INSERT INTO {Schema.AttributeData.name}
|
|
1083
|
+
(object_id, attribute_id, value, state)
|
|
1084
|
+
VALUES (?, ?, ?, ?)
|
|
1085
|
+
"""
|
|
1086
|
+
|
|
1087
|
+
with self._db.transaction():
|
|
1088
|
+
for batch in batched(params, chunksize):
|
|
1089
|
+
result = self._db.executemany(query, list(batch))
|
|
1090
|
+
if not result:
|
|
1091
|
+
msg = f"Failed to add attribute values for {object_class}."
|
|
1092
|
+
raise RuntimeError(msg)
|
|
1093
|
+
|
|
1094
|
+
logger.debug("Added {} attribute values.", len(params))
|
|
1095
|
+
|
|
964
1096
|
def _handle_dates(
|
|
965
1097
|
self,
|
|
966
1098
|
data_id: int,
|
|
@@ -1365,14 +1497,25 @@ class PlexosDB:
|
|
|
1365
1497
|
original_object_name: str,
|
|
1366
1498
|
new_object_name: str,
|
|
1367
1499
|
copy_properties: bool = True,
|
|
1500
|
+
copy_attributes: bool = True,
|
|
1368
1501
|
) -> int:
|
|
1369
|
-
"""Copy an object and its properties, tags
|
|
1502
|
+
"""Copy an object and its memberships, attributes, properties, tags and texts."""
|
|
1370
1503
|
object_id = self.get_object_id(object_class, name=original_object_name)
|
|
1371
1504
|
category_id = self.query("SELECT category_id from t_object WHERE object_id = ?", (object_id,))
|
|
1372
1505
|
category = self.query("SELECT name from t_category WHERE category_id = ?", (category_id[0][0],))
|
|
1506
|
+
|
|
1373
1507
|
new_object_id = self.add_object(object_class, new_object_name, category=category[0][0])
|
|
1508
|
+
|
|
1509
|
+
if copy_attributes:
|
|
1510
|
+
self._copy_object_attributes(
|
|
1511
|
+
old_object_id=object_id,
|
|
1512
|
+
new_object_id=new_object_id,
|
|
1513
|
+
)
|
|
1514
|
+
|
|
1374
1515
|
membership_mapping = self.copy_object_memberships(
|
|
1375
|
-
object_class=object_class,
|
|
1516
|
+
object_class=object_class,
|
|
1517
|
+
original_name=original_object_name,
|
|
1518
|
+
new_name=new_object_name,
|
|
1376
1519
|
)
|
|
1377
1520
|
|
|
1378
1521
|
system_collection = get_default_collection(object_class)
|
|
@@ -1530,10 +1673,36 @@ class PlexosDB:
|
|
|
1530
1673
|
JOIN temp_data_mapping tdm ON b.data_id = tdm.old_id
|
|
1531
1674
|
""")
|
|
1532
1675
|
|
|
1676
|
+
# Copy date_from
|
|
1677
|
+
self._db.execute("""
|
|
1678
|
+
INSERT INTO t_date_from (data_id, date, state)
|
|
1679
|
+
SELECT tdm.new_id, df.date, df.state
|
|
1680
|
+
FROM t_date_from df
|
|
1681
|
+
JOIN temp_data_mapping tdm ON df.data_id = tdm.old_id
|
|
1682
|
+
""")
|
|
1683
|
+
|
|
1684
|
+
# Copy date_to
|
|
1685
|
+
self._db.execute("""
|
|
1686
|
+
INSERT INTO t_date_to (data_id, date, state)
|
|
1687
|
+
SELECT tdm.new_id, dt.date, dt.state
|
|
1688
|
+
FROM t_date_to dt
|
|
1689
|
+
JOIN temp_data_mapping tdm ON dt.data_id = tdm.old_id
|
|
1690
|
+
""")
|
|
1691
|
+
|
|
1533
1692
|
self._db.execute("DROP TABLE IF EXISTS temp_mapping")
|
|
1534
1693
|
self._db.execute("DROP TABLE IF EXISTS temp_data_mapping")
|
|
1535
1694
|
return True
|
|
1536
1695
|
|
|
1696
|
+
def _copy_object_attributes(self, old_object_id: int, new_object_id: int) -> bool:
|
|
1697
|
+
"""Copy attribute values from original object to new object."""
|
|
1698
|
+
query = """
|
|
1699
|
+
INSERT INTO t_attribute_data (object_id, attribute_id, value, state)
|
|
1700
|
+
SELECT ?, attribute_id, value, state
|
|
1701
|
+
FROM t_attribute_data
|
|
1702
|
+
WHERE object_id = ?
|
|
1703
|
+
"""
|
|
1704
|
+
return self._db.execute(query, (new_object_id, old_object_id))
|
|
1705
|
+
|
|
1537
1706
|
def create_object_scenario(
|
|
1538
1707
|
self,
|
|
1539
1708
|
object_name: str,
|
|
@@ -1701,8 +1870,62 @@ class PlexosDB:
|
|
|
1701
1870
|
object_name: str,
|
|
1702
1871
|
object_class: ClassEnum,
|
|
1703
1872
|
) -> None:
|
|
1704
|
-
"""Delete an attribute from an object.
|
|
1705
|
-
|
|
1873
|
+
"""Delete an attribute value from an object.
|
|
1874
|
+
|
|
1875
|
+
Removes an object-level attribute value from the database.
|
|
1876
|
+
|
|
1877
|
+
Parameters
|
|
1878
|
+
----------
|
|
1879
|
+
attribute_name : str
|
|
1880
|
+
Name of the attribute to delete.
|
|
1881
|
+
object_name : str
|
|
1882
|
+
Name of the object the attribute belongs to.
|
|
1883
|
+
object_class : ClassEnum
|
|
1884
|
+
Class enumeration of the object.
|
|
1885
|
+
|
|
1886
|
+
Returns
|
|
1887
|
+
-------
|
|
1888
|
+
None
|
|
1889
|
+
|
|
1890
|
+
Raises
|
|
1891
|
+
------
|
|
1892
|
+
NotFoundError
|
|
1893
|
+
If the object does not exist, the attribute is not defined for the
|
|
1894
|
+
object's class or the attribute value is not assigned to the object.
|
|
1895
|
+
|
|
1896
|
+
Examples
|
|
1897
|
+
--------
|
|
1898
|
+
>>> db.delete_attribute(
|
|
1899
|
+
... "Enabled",
|
|
1900
|
+
... object_name="Base_Model",
|
|
1901
|
+
... object_class=ClassEnum.Model,
|
|
1902
|
+
... )
|
|
1903
|
+
"""
|
|
1904
|
+
if not checks_module.check_object_exists(self, object_class, object_name):
|
|
1905
|
+
msg = f"Object = `{object_name}` does not exist for class `{object_class}`."
|
|
1906
|
+
raise NotFoundError(msg)
|
|
1907
|
+
|
|
1908
|
+
object_id = self.get_object_id(object_class, name=object_name)
|
|
1909
|
+
attribute_id = self.get_attribute_id(object_class, name=attribute_name)
|
|
1910
|
+
|
|
1911
|
+
find_query = """
|
|
1912
|
+
SELECT 1
|
|
1913
|
+
FROM t_attribute_data
|
|
1914
|
+
WHERE object_id = ? AND attribute_id = ?
|
|
1915
|
+
"""
|
|
1916
|
+
result = self._db.fetchone(find_query, (object_id, attribute_id))
|
|
1917
|
+
|
|
1918
|
+
if not result:
|
|
1919
|
+
msg = f"Attribute '{attribute_name}' not found for object '{object_name}'."
|
|
1920
|
+
raise NotFoundError(msg)
|
|
1921
|
+
|
|
1922
|
+
delete_query = """
|
|
1923
|
+
DELETE FROM t_attribute_data
|
|
1924
|
+
WHERE object_id = ? AND attribute_id = ?
|
|
1925
|
+
"""
|
|
1926
|
+
|
|
1927
|
+
with self._db.transaction():
|
|
1928
|
+
self._db.execute(delete_query, (object_id, attribute_id))
|
|
1706
1929
|
|
|
1707
1930
|
def delete_category(self, category: str, /, *, class_name: ClassEnum) -> None:
|
|
1708
1931
|
"""Delete a category from the database."""
|
|
@@ -1908,7 +2131,7 @@ class PlexosDB:
|
|
|
1908
2131
|
object_name: str,
|
|
1909
2132
|
attribute_name: str,
|
|
1910
2133
|
) -> Any:
|
|
1911
|
-
"""Get attribute
|
|
2134
|
+
"""Get an assigned attribute value for a specific object."""
|
|
1912
2135
|
query = """
|
|
1913
2136
|
SELECT
|
|
1914
2137
|
t_attribute_data.value
|
|
@@ -1940,12 +2163,12 @@ class PlexosDB:
|
|
|
1940
2163
|
Returns
|
|
1941
2164
|
-------
|
|
1942
2165
|
int
|
|
1943
|
-
ID of the
|
|
2166
|
+
ID of the attribute
|
|
1944
2167
|
|
|
1945
2168
|
Raises
|
|
1946
2169
|
------
|
|
1947
|
-
|
|
1948
|
-
If the
|
|
2170
|
+
NotFoundError
|
|
2171
|
+
If the attribute does not exist for the specified class.
|
|
1949
2172
|
"""
|
|
1950
2173
|
query = """
|
|
1951
2174
|
SELECT
|
|
@@ -1959,7 +2182,9 @@ class PlexosDB:
|
|
|
1959
2182
|
AND t_class.name = ?
|
|
1960
2183
|
"""
|
|
1961
2184
|
result = self._db.fetchone(query, (name, class_enum))
|
|
1962
|
-
|
|
2185
|
+
if not result:
|
|
2186
|
+
msg = f"Attribute '{name}' not found for class '{class_enum}'."
|
|
2187
|
+
raise NotFoundError(msg)
|
|
1963
2188
|
return cast(int, result[0])
|
|
1964
2189
|
|
|
1965
2190
|
def get_attributes(
|
|
@@ -1968,10 +2193,67 @@ class PlexosDB:
|
|
|
1968
2193
|
/,
|
|
1969
2194
|
*,
|
|
1970
2195
|
object_class: ClassEnum,
|
|
1971
|
-
attribute_names:
|
|
2196
|
+
attribute_names: str | Iterable[str] | None = None,
|
|
1972
2197
|
) -> list[dict[str, Any]]:
|
|
1973
|
-
"""
|
|
1974
|
-
|
|
2198
|
+
"""Retrieve assigned attribute values for a specific object.
|
|
2199
|
+
|
|
2200
|
+
Parameters
|
|
2201
|
+
----------
|
|
2202
|
+
object_name : str
|
|
2203
|
+
Name of the object.
|
|
2204
|
+
object_class : ClassEnum
|
|
2205
|
+
Class of the object.
|
|
2206
|
+
attribute_names : str | Iterable[str] | None, optional
|
|
2207
|
+
Attribute name or names to retrieve. If omitted, retrieves all
|
|
2208
|
+
assigned attribute values.
|
|
2209
|
+
|
|
2210
|
+
Returns
|
|
2211
|
+
-------
|
|
2212
|
+
list[dict[str, Any]]
|
|
2213
|
+
Attribute-value records with ``name``, ``attribute``, ``value``,
|
|
2214
|
+
and ``state`` keys. Returns an empty list when the object has no
|
|
2215
|
+
assigned attribute values.
|
|
2216
|
+
|
|
2217
|
+
Raises
|
|
2218
|
+
------
|
|
2219
|
+
NotFoundError
|
|
2220
|
+
If the object does not exist for the requested class.
|
|
2221
|
+
"""
|
|
2222
|
+
if not checks_module.check_object_exists(self, object_class, object_name):
|
|
2223
|
+
msg = f"Object = `{object_name}` does not exist in class = {object_class}. "
|
|
2224
|
+
msg += "See available objects with list_objects_by_class"
|
|
2225
|
+
raise NotFoundError(msg)
|
|
2226
|
+
|
|
2227
|
+
object_id = self.get_object_id(object_class, object_name)
|
|
2228
|
+
class_id = self.get_class_id(object_class)
|
|
2229
|
+
|
|
2230
|
+
conditions = [
|
|
2231
|
+
"data.object_id = ?",
|
|
2232
|
+
"attr.class_id = ?",
|
|
2233
|
+
]
|
|
2234
|
+
params: list[Any] = [object_id, class_id]
|
|
2235
|
+
|
|
2236
|
+
if attribute_names is not None:
|
|
2237
|
+
names = normalize_names(attribute_names)
|
|
2238
|
+
if names:
|
|
2239
|
+
placeholders = ", ".join("?" for _ in names)
|
|
2240
|
+
conditions.append(f"attr.name IN ({placeholders})")
|
|
2241
|
+
params.extend(names)
|
|
2242
|
+
|
|
2243
|
+
where_clause = " AND ".join(conditions)
|
|
2244
|
+
query = f"""
|
|
2245
|
+
SELECT
|
|
2246
|
+
obj.name AS name,
|
|
2247
|
+
attr.name AS attribute,
|
|
2248
|
+
data.value,
|
|
2249
|
+
data.state
|
|
2250
|
+
FROM t_attribute_data AS data
|
|
2251
|
+
JOIN t_object AS obj ON obj.object_id = data.object_id
|
|
2252
|
+
JOIN t_attribute AS attr ON attr.attribute_id = data.attribute_id
|
|
2253
|
+
WHERE {where_clause}
|
|
2254
|
+
ORDER BY attr.name
|
|
2255
|
+
"""
|
|
2256
|
+
return self._db.fetchall_dict(query, tuple(params))
|
|
1975
2257
|
|
|
1976
2258
|
def get_category_id(self, class_enum: ClassEnum, /, name: str) -> int:
|
|
1977
2259
|
"""Return the ID for a given category.
|
|
@@ -3028,7 +3310,7 @@ class PlexosDB:
|
|
|
3028
3310
|
Parameters
|
|
3029
3311
|
----------
|
|
3030
3312
|
class_enum : ClassEnum
|
|
3031
|
-
Class enumeration to list
|
|
3313
|
+
Class enumeration to list attributes
|
|
3032
3314
|
|
|
3033
3315
|
Returns
|
|
3034
3316
|
-------
|
|
@@ -273,6 +273,39 @@ def _flatten_property_records(records: list[dict[str, Any]]) -> tuple[list[dict[
|
|
|
273
273
|
return normalized_records, deprecated_format_used
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
def _normalize_attribute_records(
|
|
277
|
+
records: list[dict[str, Any]],
|
|
278
|
+
) -> list[dict[str, Any]]:
|
|
279
|
+
"""Normalize wide or explicit attribute records into explicit records."""
|
|
280
|
+
reserved_fields = {"name", "attribute", "value", "state"}
|
|
281
|
+
normalized: list[dict[str, Any]] = []
|
|
282
|
+
|
|
283
|
+
for record in records:
|
|
284
|
+
if "attribute" in record and "value" in record:
|
|
285
|
+
if "name" not in record:
|
|
286
|
+
raise KeyError(f"Attribute record is missing required 'name': {record}")
|
|
287
|
+
normalized.append(record)
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
if "name" not in record:
|
|
291
|
+
raise KeyError(f"Attribute record is missing required 'name': {record}")
|
|
292
|
+
|
|
293
|
+
for key, value in record.items():
|
|
294
|
+
if key in reserved_fields:
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
normalized.append(
|
|
298
|
+
{
|
|
299
|
+
"name": record["name"],
|
|
300
|
+
"attribute": key,
|
|
301
|
+
"value": value,
|
|
302
|
+
"state": record.get("state"),
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return normalized
|
|
307
|
+
|
|
308
|
+
|
|
276
309
|
def plan_property_inserts(
|
|
277
310
|
db: PlexosDB,
|
|
278
311
|
records: list[dict[str, Any]],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|