plexosdb 1.4.1__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.1 → plexosdb-1.5.0}/PKG-INFO +1 -1
- {plexosdb-1.4.1 → plexosdb-1.5.0}/pyproject.toml +1 -1
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/checks.py +14 -6
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/db.py +284 -18
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/utils.py +33 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/LICENSE.txt +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/README.md +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/__init__.py +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/__main__.py +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/db_manager.py +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/enums.py +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/exceptions.py +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/py.typed +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/schema.sql +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/sql/object_properties.sql +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/sql/object_query.sql +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/sql/property.sql +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/sql/property_query.sql +0 -0
- {plexosdb-1.4.1 → plexosdb-1.5.0}/src/plexosdb/sql/simple_object_query.sql +0 -0
- {plexosdb-1.4.1 → 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)
|
|
@@ -1550,6 +1693,16 @@ class PlexosDB:
|
|
|
1550
1693
|
self._db.execute("DROP TABLE IF EXISTS temp_data_mapping")
|
|
1551
1694
|
return True
|
|
1552
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
|
+
|
|
1553
1706
|
def create_object_scenario(
|
|
1554
1707
|
self,
|
|
1555
1708
|
object_name: str,
|
|
@@ -1717,8 +1870,62 @@ class PlexosDB:
|
|
|
1717
1870
|
object_name: str,
|
|
1718
1871
|
object_class: ClassEnum,
|
|
1719
1872
|
) -> None:
|
|
1720
|
-
"""Delete an attribute from an object.
|
|
1721
|
-
|
|
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))
|
|
1722
1929
|
|
|
1723
1930
|
def delete_category(self, category: str, /, *, class_name: ClassEnum) -> None:
|
|
1724
1931
|
"""Delete a category from the database."""
|
|
@@ -1924,7 +2131,7 @@ class PlexosDB:
|
|
|
1924
2131
|
object_name: str,
|
|
1925
2132
|
attribute_name: str,
|
|
1926
2133
|
) -> Any:
|
|
1927
|
-
"""Get attribute
|
|
2134
|
+
"""Get an assigned attribute value for a specific object."""
|
|
1928
2135
|
query = """
|
|
1929
2136
|
SELECT
|
|
1930
2137
|
t_attribute_data.value
|
|
@@ -1956,12 +2163,12 @@ class PlexosDB:
|
|
|
1956
2163
|
Returns
|
|
1957
2164
|
-------
|
|
1958
2165
|
int
|
|
1959
|
-
ID of the
|
|
2166
|
+
ID of the attribute
|
|
1960
2167
|
|
|
1961
2168
|
Raises
|
|
1962
2169
|
------
|
|
1963
|
-
|
|
1964
|
-
If the
|
|
2170
|
+
NotFoundError
|
|
2171
|
+
If the attribute does not exist for the specified class.
|
|
1965
2172
|
"""
|
|
1966
2173
|
query = """
|
|
1967
2174
|
SELECT
|
|
@@ -1975,7 +2182,9 @@ class PlexosDB:
|
|
|
1975
2182
|
AND t_class.name = ?
|
|
1976
2183
|
"""
|
|
1977
2184
|
result = self._db.fetchone(query, (name, class_enum))
|
|
1978
|
-
|
|
2185
|
+
if not result:
|
|
2186
|
+
msg = f"Attribute '{name}' not found for class '{class_enum}'."
|
|
2187
|
+
raise NotFoundError(msg)
|
|
1979
2188
|
return cast(int, result[0])
|
|
1980
2189
|
|
|
1981
2190
|
def get_attributes(
|
|
@@ -1984,10 +2193,67 @@ class PlexosDB:
|
|
|
1984
2193
|
/,
|
|
1985
2194
|
*,
|
|
1986
2195
|
object_class: ClassEnum,
|
|
1987
|
-
attribute_names:
|
|
2196
|
+
attribute_names: str | Iterable[str] | None = None,
|
|
1988
2197
|
) -> list[dict[str, Any]]:
|
|
1989
|
-
"""
|
|
1990
|
-
|
|
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))
|
|
1991
2257
|
|
|
1992
2258
|
def get_category_id(self, class_enum: ClassEnum, /, name: str) -> int:
|
|
1993
2259
|
"""Return the ID for a given category.
|
|
@@ -3044,7 +3310,7 @@ class PlexosDB:
|
|
|
3044
3310
|
Parameters
|
|
3045
3311
|
----------
|
|
3046
3312
|
class_enum : ClassEnum
|
|
3047
|
-
Class enumeration to list
|
|
3313
|
+
Class enumeration to list attributes
|
|
3048
3314
|
|
|
3049
3315
|
Returns
|
|
3050
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
|