steer-core 0.1.30__tar.gz → 0.1.32__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.
Files changed (32) hide show
  1. {steer_core-0.1.30 → steer_core-0.1.32}/PKG-INFO +1 -1
  2. {steer_core-0.1.30/steer_core → steer_core-0.1.32/steer_core/Data}/DataManager.py +76 -1
  3. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/Plotter.py +2 -0
  4. steer_core-0.1.32/steer_core/Mixins/Serializer.py +281 -0
  5. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/TypeChecker.py +2 -1
  6. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/__init__.py +1 -4
  7. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core.egg-info/PKG-INFO +1 -1
  8. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core.egg-info/SOURCES.txt +1 -2
  9. steer_core-0.1.30/steer_core/Data/database.db +0 -0
  10. steer_core-0.1.30/steer_core/Mixins/Serializer.py +0 -46
  11. {steer_core-0.1.30 → steer_core-0.1.32}/README.md +0 -0
  12. {steer_core-0.1.30 → steer_core-0.1.32}/pyproject.toml +0 -0
  13. {steer_core-0.1.30 → steer_core-0.1.32}/setup.cfg +0 -0
  14. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Constants/Units.py +0 -0
  15. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Constants/Universal.py +0 -0
  16. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Constants/__init__.py +0 -0
  17. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/ContextManagers/ContextManagers.py +0 -0
  18. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/ContextManagers/__init__.py +0 -0
  19. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Data/__init__.py +0 -0
  20. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Decorators/Coordinates.py +0 -0
  21. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Decorators/General.py +0 -0
  22. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Decorators/Objects.py +0 -0
  23. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Decorators/__init__.py +0 -0
  24. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/Colors.py +0 -0
  25. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/Coordinates.py +0 -0
  26. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/Data.py +0 -0
  27. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/Dunder.py +0 -0
  28. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core/Mixins/__init__.py +0 -0
  29. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core.egg-info/dependency_links.txt +0 -0
  30. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core.egg-info/requires.txt +0 -0
  31. {steer_core-0.1.30 → steer_core-0.1.32}/steer_core.egg-info/top_level.txt +0 -0
  32. {steer_core-0.1.30 → steer_core-0.1.32}/test/test_validation_mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steer-core
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Modelling energy storage from cell to site - STEER OpenCell Design
5
5
  Author-email: Nicholas Siemons <nsiemons@stanford.edu>
6
6
  Maintainer-email: Nicholas Siemons <nsiemons@stanford.edu>
@@ -1,15 +1,20 @@
1
1
  import sqlite3 as sql
2
2
  from pathlib import Path
3
+ from typing import TypeVar
3
4
  import pandas as pd
4
5
  import importlib.resources
5
6
 
6
7
  from steer_core.Constants.Units import *
8
+ from steer_core.Mixins.Serializer import SerializerMixin
9
+
10
+
11
+ T = TypeVar('T', bound='SerializerMixin')
7
12
 
8
13
 
9
14
  class DataManager:
10
15
 
11
16
  def __init__(self):
12
- with importlib.resources.path("steer_core.Data", "database.db") as db_path:
17
+ with importlib.resources.path("steer_opencell_data", "database.db") as db_path:
13
18
  self._db_path = db_path
14
19
  self._connection = sql.connect(self._db_path)
15
20
  self._cursor = self._connection.cursor()
@@ -356,5 +361,75 @@ class DataManager:
356
361
  self._cursor.execute(f"DELETE FROM {table_name} WHERE {condition}")
357
362
  self._connection.commit()
358
363
 
364
+
365
+ @classmethod
366
+ def from_database(cls: type[T], name: str, table_name: str = None) -> T:
367
+ """
368
+ Pull object from the database by name.
369
+
370
+ Subclasses must define a '_table_name' class variable (str or list of str)
371
+ unless table_name is explicitly provided.
372
+
373
+ Parameters
374
+ ----------
375
+ name : str
376
+ Name of the object to retrieve.
377
+ table_name : str, optional
378
+ Specific table to search. If provided, '_table_name' is not required.
379
+ If None, uses class's _table_name.
380
+
381
+ Returns
382
+ -------
383
+ T
384
+ Instance of the class.
385
+
386
+ Raises
387
+ ------
388
+ NotImplementedError
389
+ If the subclass doesn't define '_table_name' and table_name is not provided.
390
+ ValueError
391
+ If the object name is not found in any of the tables.
392
+ """
393
+ database = cls()
394
+
395
+ # Get list of tables to search
396
+ if table_name:
397
+ tables_to_search = [table_name]
398
+ else:
399
+ # Only check for _table_name if table_name wasn't provided
400
+ if not hasattr(cls, '_table_name'):
401
+ raise NotImplementedError(
402
+ f"{cls.__name__} must define a '_table_name' class variable "
403
+ "or provide 'table_name' argument"
404
+ )
405
+
406
+ if isinstance(cls._table_name, (list, tuple)):
407
+ tables_to_search = cls._table_name
408
+ else:
409
+ tables_to_search = [cls._table_name]
410
+
411
+ # Try each table until found
412
+ for table in tables_to_search:
413
+ available_materials = database.get_unique_values(table, "name")
414
+
415
+ if name in available_materials:
416
+ data = database.get_data(table, condition=f"name = '{name}'")
417
+ serialized_bytes = data["object"].iloc[0]
418
+ return cls.deserialize(serialized_bytes)
419
+
420
+ # Not found in any table
421
+ all_available = []
422
+ for table in tables_to_search:
423
+ all_available.extend(database.get_unique_values(table, "name"))
424
+
425
+ raise ValueError(
426
+ f"'{name}' not found in tables {tables_to_search}. "
427
+ f"Available: {all_available}"
428
+ )
429
+
430
+
431
+
359
432
  def __del__(self):
360
433
  self._connection.close()
434
+
435
+
@@ -0,0 +1,281 @@
1
+ import msgpack
2
+ import msgpack_numpy as m
3
+ import zlib
4
+ from typing import TypeVar, Any
5
+ from datetime import datetime
6
+ from enum import Enum
7
+
8
+ T = TypeVar('T', bound='SerializerMixin')
9
+
10
+
11
+ class SerializerMixin:
12
+
13
+ def serialize(self, compress: bool = True) -> bytes:
14
+ """
15
+ Serialize object using MessagePack with numpy support.
16
+
17
+ Parameters
18
+ ----------
19
+ compress : bool, optional
20
+ Whether to compress the serialized data (default: True)
21
+
22
+ Returns
23
+ -------
24
+ bytes
25
+ The serialized byte representation of the object.
26
+ """
27
+ m.patch() # Enable numpy support
28
+ data = msgpack.packb(self._to_dict(), use_bin_type=True)
29
+
30
+ if compress:
31
+ # Add marker byte to indicate compression
32
+ return b'\x01' + zlib.compress(data, level=6)
33
+ else:
34
+ return b'\x00' + data
35
+
36
+ def _serialize_value(self, value: Any) -> Any:
37
+ """
38
+ Recursively serialize a value, handling nested structures.
39
+
40
+ Parameters
41
+ ----------
42
+ value : Any
43
+ The value to serialize.
44
+
45
+ Returns
46
+ -------
47
+ Any
48
+ The serialized representation.
49
+ """
50
+ if hasattr(value, '_to_dict'):
51
+ # Add marker and class info for object reconstruction
52
+ return {
53
+ '__object__': True,
54
+ '_class': f"{value.__class__.__module__}.{value.__class__.__name__}",
55
+ **value._to_dict()
56
+ }
57
+ elif isinstance(value, datetime):
58
+ return {'__datetime__': value.isoformat()}
59
+ elif isinstance(value, Enum):
60
+ return {
61
+ '__enum__': True,
62
+ 'class': f"{value.__class__.__module__}.{value.__class__.__name__}",
63
+ 'value': value.value
64
+ }
65
+ elif isinstance(value, tuple):
66
+ # Recursively serialize tuple items
67
+ return {
68
+ '__tuple__': True,
69
+ 'items': [self._serialize_value(item) for item in value]
70
+ }
71
+ elif isinstance(value, list):
72
+ # Recursively serialize list items
73
+ return [self._serialize_value(item) for item in value]
74
+ elif isinstance(value, dict):
75
+ # Handle dictionaries with object keys or values
76
+ has_object_keys = value and any(hasattr(k, '_to_dict') for k in value.keys())
77
+ has_object_values = value and any(hasattr(v, '_to_dict') for v in value.values())
78
+
79
+ if has_object_keys or has_object_values:
80
+ return {
81
+ '__object_dict__': True,
82
+ 'items': [
83
+ {
84
+ 'key': self._serialize_value(k),
85
+ 'value': self._serialize_value(v)
86
+ }
87
+ for k, v in value.items()
88
+ ]
89
+ }
90
+ else:
91
+ # Recursively serialize regular dict values
92
+ return {k: self._serialize_value(v) for k, v in value.items()}
93
+ else:
94
+ return value
95
+
96
+ def _to_dict(self) -> dict:
97
+ """
98
+ Convert object to dictionary for serialization.
99
+ Override this in subclasses to customize serialization behavior.
100
+
101
+ Returns
102
+ -------
103
+ dict
104
+ Dictionary representation of the object.
105
+ """
106
+ result = {}
107
+ for key, value in self.__dict__.items():
108
+ result[key] = self._serialize_value(value)
109
+ return result
110
+
111
+ @classmethod
112
+ def deserialize(cls: type[T], data: bytes) -> T:
113
+ """
114
+ Deserialize byte data into an object.
115
+ Automatically detects and decompresses if needed.
116
+
117
+ Parameters
118
+ ----------
119
+ data : bytes
120
+ The byte data to deserialize.
121
+
122
+ Returns
123
+ -------
124
+ T
125
+ Instance of the class.
126
+ """
127
+ m.patch() # Enable numpy support
128
+
129
+ # Check compression marker
130
+ if data[0:1] == b'\x01':
131
+ data = zlib.decompress(data[1:])
132
+ else:
133
+ data = data[1:]
134
+
135
+ obj_dict = msgpack.unpackb(data, raw=False)
136
+ return cls._from_dict(obj_dict)
137
+
138
+ @classmethod
139
+ def _deserialize_value(cls, value: Any) -> Any:
140
+ """
141
+ Recursively deserialize a value, handling nested structures.
142
+
143
+ Parameters
144
+ ----------
145
+ value : Any
146
+ The value to deserialize.
147
+
148
+ Returns
149
+ -------
150
+ Any
151
+ The deserialized object.
152
+ """
153
+ if isinstance(value, dict):
154
+ if '__datetime__' in value:
155
+ return datetime.fromisoformat(value['__datetime__'])
156
+ elif '__enum__' in value:
157
+ # Reconstruct enum
158
+ module_name, class_name = value['class'].rsplit('.', 1)
159
+ import importlib
160
+ module = importlib.import_module(module_name)
161
+ enum_class = getattr(module, class_name)
162
+ return enum_class(value['value'])
163
+ elif '__tuple__' in value:
164
+ # Recursively reconstruct tuple items
165
+ return tuple(cls._deserialize_value(item) for item in value['items'])
166
+ elif '__object__' in value:
167
+ # Reconstruct regular object
168
+ import importlib
169
+ module_name, class_name = value['_class'].rsplit('.', 1)
170
+ module = importlib.import_module(module_name)
171
+ obj_class = getattr(module, class_name)
172
+ # Remove marker fields before passing to _from_dict
173
+ obj_data = {k: v for k, v in value.items() if k not in ('__object__', '_class')}
174
+ return obj_class._from_dict(obj_data)
175
+ elif '__object_dict__' in value:
176
+ # Reconstruct dictionary with object keys or values
177
+ reconstructed_dict = {}
178
+ for item in value['items']:
179
+ key_obj = cls._deserialize_value(item['key'])
180
+ value_obj = cls._deserialize_value(item['value'])
181
+ reconstructed_dict[key_obj] = value_obj
182
+ return reconstructed_dict
183
+ else:
184
+ # Recursively deserialize regular dict values
185
+ return {k: cls._deserialize_value(v) for k, v in value.items()}
186
+ elif isinstance(value, list):
187
+ # Recursively deserialize list items
188
+ return [cls._deserialize_value(item) for item in value]
189
+ else:
190
+ return value
191
+
192
+ @classmethod
193
+ def _from_dict(cls: type[T], data: dict) -> T:
194
+ """
195
+ Reconstruct object from dictionary.
196
+ Override in subclasses for custom deserialization.
197
+
198
+ Parameters
199
+ ----------
200
+ data : dict
201
+ Dictionary representation to reconstruct from.
202
+
203
+ Returns
204
+ -------
205
+ T
206
+ Reconstructed object instance.
207
+ """
208
+ obj = cls.__new__(cls)
209
+ reconstructed = {}
210
+ for key, value in data.items():
211
+ reconstructed[key] = cls._deserialize_value(value)
212
+ obj.__dict__.update(reconstructed)
213
+ return obj
214
+
215
+ @classmethod
216
+ def from_database(cls: type[T], name: str, table_name: str = None) -> T:
217
+ """
218
+ Pull object from the database by name.
219
+
220
+ Subclasses must define a '_table_name' class variable (str or list of str)
221
+ unless table_name is explicitly provided.
222
+
223
+ Parameters
224
+ ----------
225
+ name : str
226
+ Name of the object to retrieve.
227
+ table_name : str, optional
228
+ Specific table to search. If provided, '_table_name' is not required.
229
+ If None, uses class's _table_name.
230
+
231
+ Returns
232
+ -------
233
+ T
234
+ Instance of the class.
235
+
236
+ Raises
237
+ ------
238
+ NotImplementedError
239
+ If the subclass doesn't define '_table_name' and table_name is not provided.
240
+ ValueError
241
+ If the object name is not found in any of the tables.
242
+ """
243
+ from steer_core.Data.DataManager import DataManager
244
+
245
+ database = DataManager()
246
+
247
+ # Get list of tables to search
248
+ if table_name:
249
+ tables_to_search = [table_name]
250
+ else:
251
+ # Only check for _table_name if table_name wasn't provided
252
+ if not hasattr(cls, '_table_name'):
253
+ raise NotImplementedError(
254
+ f"{cls.__name__} must define a '_table_name' class variable "
255
+ "or provide 'table_name' argument"
256
+ )
257
+
258
+ if isinstance(cls._table_name, (list, tuple)):
259
+ tables_to_search = cls._table_name
260
+ else:
261
+ tables_to_search = [cls._table_name]
262
+
263
+ # Try each table until found
264
+ for table in tables_to_search:
265
+ available_materials = database.get_unique_values(table, "name")
266
+
267
+ if name in available_materials:
268
+ data = database.get_data(table, condition=f"name = '{name}'")
269
+ serialized_bytes = data["object"].iloc[0]
270
+ return cls.deserialize(serialized_bytes)
271
+
272
+ # Not found in any table
273
+ all_available = []
274
+ for table in tables_to_search:
275
+ all_available.extend(database.get_unique_values(table, "name"))
276
+
277
+ raise ValueError(
278
+ f"'{name}' not found in tables {tables_to_search}. "
279
+ f"Available: {all_available}"
280
+ )
281
+
@@ -208,8 +208,9 @@ class ValidationMixin:
208
208
  ValueError
209
209
  If the value is not a positive float.
210
210
  """
211
+ value = float(value) # Ensure value is float
211
212
  if not isinstance(value, (int, float)):
212
- raise ValueError(f"{name} must be a positive float. Provided: {value}.")
213
+ raise ValueError(f"{name} must be a positive float. Provided: {value} of type {type(value).__name__}.")
213
214
 
214
215
  @staticmethod
215
216
  def validate_positive_int(value: int, name: str) -> None:
@@ -1,7 +1,4 @@
1
- __version__ = "0.1.30"
2
-
3
- # datamanager import
4
- from .DataManager import DataManager
1
+ __version__ = "0.1.32"
5
2
 
6
3
  from .Mixins.Colors import ColorMixin
7
4
  from .Mixins.Coordinates import CoordinateMixin
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steer-core
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Modelling energy storage from cell to site - STEER OpenCell Design
5
5
  Author-email: Nicholas Siemons <nsiemons@stanford.edu>
6
6
  Maintainer-email: Nicholas Siemons <nsiemons@stanford.edu>
@@ -1,6 +1,5 @@
1
1
  README.md
2
2
  pyproject.toml
3
- steer_core/DataManager.py
4
3
  steer_core/__init__.py
5
4
  steer_core.egg-info/PKG-INFO
6
5
  steer_core.egg-info/SOURCES.txt
@@ -12,8 +11,8 @@ steer_core/Constants/Universal.py
12
11
  steer_core/Constants/__init__.py
13
12
  steer_core/ContextManagers/ContextManagers.py
14
13
  steer_core/ContextManagers/__init__.py
14
+ steer_core/Data/DataManager.py
15
15
  steer_core/Data/__init__.py
16
- steer_core/Data/database.db
17
16
  steer_core/Decorators/Coordinates.py
18
17
  steer_core/Decorators/General.py
19
18
  steer_core/Decorators/Objects.py
@@ -1,46 +0,0 @@
1
- import base64
2
- from pickle import loads, dumps
3
- from typing import Type
4
- from copy import deepcopy
5
-
6
-
7
- class SerializerMixin:
8
-
9
- def serialize(self) -> str:
10
- """
11
- Serialize an object to a string representation.
12
-
13
- Parameters
14
- ----------
15
- obj : Type
16
- The object to serialize.
17
-
18
- Returns
19
- -------
20
- str
21
- The serialized string representation of the object.
22
- """
23
- pickled = dumps(self)
24
- based = base64.b64encode(pickled).decode("utf-8")
25
- return based
26
-
27
- @staticmethod
28
- def deserialize(String: str) -> Type:
29
- """
30
- Deserialize a string representation into an object.
31
-
32
- Parameters
33
- ----------
34
- String : str
35
- The string representation to deserialize.
36
-
37
- Returns
38
- -------
39
- SerializerMixin
40
- The deserialized object.
41
- """
42
- decoded = base64.b64decode(String.encode("utf-8"))
43
- obj = deepcopy(loads(decoded))
44
- return obj
45
-
46
-
File without changes
File without changes
File without changes