shepherd-core 2024.4.1__py3-none-any.whl → 2024.5.1__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.
Files changed (76) hide show
  1. shepherd_core/__init__.py +3 -3
  2. shepherd_core/calibration_hw_def.py +9 -1
  3. shepherd_core/commons.py +2 -0
  4. shepherd_core/data_models/__init__.py +11 -0
  5. shepherd_core/data_models/base/__init__.py +4 -1
  6. shepherd_core/data_models/base/cal_measurement.py +18 -6
  7. shepherd_core/data_models/base/calibration.py +39 -15
  8. shepherd_core/data_models/base/content.py +10 -2
  9. shepherd_core/data_models/base/shepherd.py +21 -12
  10. shepherd_core/data_models/base/timezone.py +5 -0
  11. shepherd_core/data_models/base/wrapper.py +3 -3
  12. shepherd_core/data_models/content/__init__.py +5 -4
  13. shepherd_core/data_models/content/_external_fixtures.yaml +410 -0
  14. shepherd_core/data_models/content/energy_environment.py +7 -5
  15. shepherd_core/data_models/content/energy_environment_fixture.yaml +53 -0
  16. shepherd_core/data_models/content/firmware.py +12 -5
  17. shepherd_core/data_models/content/firmware_datatype.py +7 -0
  18. shepherd_core/data_models/content/virtual_harvester.py +24 -19
  19. shepherd_core/data_models/content/virtual_harvester_fixture.yaml +160 -0
  20. shepherd_core/data_models/content/virtual_source.py +22 -10
  21. shepherd_core/data_models/content/virtual_source_fixture.yaml +230 -0
  22. shepherd_core/data_models/experiment/__init__.py +5 -4
  23. shepherd_core/data_models/experiment/experiment.py +7 -6
  24. shepherd_core/data_models/experiment/observer_features.py +14 -7
  25. shepherd_core/data_models/experiment/target_config.py +11 -7
  26. shepherd_core/data_models/readme.md +88 -0
  27. shepherd_core/data_models/task/__init__.py +10 -3
  28. shepherd_core/data_models/task/emulation.py +10 -7
  29. shepherd_core/data_models/task/firmware_mod.py +4 -2
  30. shepherd_core/data_models/task/harvest.py +5 -4
  31. shepherd_core/data_models/task/observer_tasks.py +3 -1
  32. shepherd_core/data_models/task/programming.py +3 -1
  33. shepherd_core/data_models/task/testbed_tasks.py +3 -1
  34. shepherd_core/data_models/testbed/__init__.py +5 -2
  35. shepherd_core/data_models/testbed/cape.py +7 -5
  36. shepherd_core/data_models/testbed/cape_fixture.yaml +94 -0
  37. shepherd_core/data_models/testbed/gpio.py +10 -8
  38. shepherd_core/data_models/testbed/gpio_fixture.yaml +166 -0
  39. shepherd_core/data_models/testbed/mcu.py +9 -9
  40. shepherd_core/data_models/testbed/mcu_fixture.yaml +19 -0
  41. shepherd_core/data_models/testbed/observer.py +9 -4
  42. shepherd_core/data_models/testbed/observer_fixture.yaml +221 -0
  43. shepherd_core/data_models/testbed/target.py +4 -2
  44. shepherd_core/data_models/testbed/target_fixture.yaml +137 -0
  45. shepherd_core/data_models/testbed/testbed.py +5 -2
  46. shepherd_core/data_models/testbed/testbed_fixture.yaml +25 -0
  47. shepherd_core/decoder_waveform/__init__.py +2 -0
  48. shepherd_core/decoder_waveform/uart.py +43 -25
  49. shepherd_core/fw_tools/__init__.py +2 -0
  50. shepherd_core/fw_tools/converter.py +20 -9
  51. shepherd_core/fw_tools/converter_elf.py +3 -0
  52. shepherd_core/fw_tools/patcher.py +17 -5
  53. shepherd_core/fw_tools/validation.py +27 -6
  54. shepherd_core/inventory/__init__.py +15 -5
  55. shepherd_core/inventory/python.py +4 -0
  56. shepherd_core/inventory/system.py +6 -2
  57. shepherd_core/inventory/target.py +4 -0
  58. shepherd_core/logger.py +5 -0
  59. shepherd_core/reader.py +38 -23
  60. shepherd_core/testbed_client/__init__.py +2 -0
  61. shepherd_core/testbed_client/cache_path.py +2 -0
  62. shepherd_core/testbed_client/client.py +15 -8
  63. shepherd_core/testbed_client/fixtures.py +27 -11
  64. shepherd_core/testbed_client/user_model.py +8 -3
  65. shepherd_core/vsource/__init__.py +2 -0
  66. shepherd_core/vsource/virtual_converter_model.py +10 -3
  67. shepherd_core/vsource/virtual_harvester_model.py +7 -1
  68. shepherd_core/vsource/virtual_source_model.py +9 -5
  69. shepherd_core/writer.py +26 -21
  70. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/METADATA +2 -1
  71. shepherd_core-2024.5.1.dist-info/RECORD +75 -0
  72. shepherd_core-2024.4.1.dist-info/RECORD +0 -64
  73. /shepherd_core/data_models/{doc_virtual_source.py → virtual_source_doc.txt} +0 -0
  74. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/WHEEL +0 -0
  75. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/top_level.txt +0 -0
  76. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/zip-safe +0 -0
shepherd_core/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- """shepherd.core
2
- ~~~~~
1
+ """Bundled core features used by several systems.
2
+
3
3
  Provides classes for storing and retrieving sampled IV data to/from
4
4
  HDF5 files.
5
5
 
@@ -23,7 +23,7 @@ from .testbed_client.client import TestbedClient
23
23
  from .testbed_client.client import tb_client
24
24
  from .writer import Writer
25
25
 
26
- __version__ = "2024.04.1"
26
+ __version__ = "2024.5.1"
27
27
 
28
28
  __all__ = [
29
29
  "Reader",
@@ -1,4 +1,6 @@
1
- """Contains some info about the hardware configuration on the shepherd
1
+ """parametrized math models for converting between raw- and si-values.
2
+
3
+ Contains some info about the hardware configuration on the shepherd
2
4
  cape. Based on these values, one can derive the expected adc readings given
3
5
  an input voltage/current or, for emulation, the expected voltage and current
4
6
  given the digital code in the DAC.
@@ -31,6 +33,7 @@ RAW_MAX_DAC = 2**M_DAC - 1
31
33
 
32
34
 
33
35
  def adc_current_to_raw(current: float) -> int:
36
+ """Convert back a current [A] to raw ADC value."""
34
37
  # voltage on input of adc
35
38
  val_adc = G_INST_AMP * R_SHT * current
36
39
  # digital value according to ADC gain
@@ -39,6 +42,7 @@ def adc_current_to_raw(current: float) -> int:
39
42
 
40
43
 
41
44
  def adc_raw_to_current(value: int) -> float:
45
+ """Convert a raw ADC value to a current [A]."""
42
46
  value = min(max(value, 0), 2**M_ADC - 1)
43
47
  # voltage on input of adc
44
48
  val_adc = float(value) * (G_ADC_I * V_REF_ADC) / (2**M_ADC)
@@ -47,22 +51,26 @@ def adc_raw_to_current(value: int) -> float:
47
51
 
48
52
 
49
53
  def adc_voltage_to_raw(voltage: float) -> int:
54
+ """Convert back a voltage [V] to raw ADC value."""
50
55
  # digital value according to ADC gain
51
56
  val_raw = int(voltage * (2**M_ADC) / (G_ADC_V * V_REF_ADC))
52
57
  return min(max(val_raw, 0), 2**M_ADC - 1)
53
58
 
54
59
 
55
60
  def adc_raw_to_voltage(value: int) -> float:
61
+ """Convert a raw ADC value to a voltage [V]."""
56
62
  value = min(max(value, 0), 2**M_ADC - 1)
57
63
  # voltage according to ADC value
58
64
  return float(value) * (G_ADC_V * V_REF_ADC) / (2**M_ADC)
59
65
 
60
66
 
61
67
  def dac_raw_to_voltage(value: int) -> float:
68
+ """Convert back a raw DAC value to a voltage [V]."""
62
69
  value = min(max(value, 0), 2**M_DAC - 1)
63
70
  return float(value) * (V_REF_DAC * G_DAC) / (2**M_DAC)
64
71
 
65
72
 
66
73
  def dac_voltage_to_raw(voltage: float) -> int:
74
+ """Convert a voltage [V] to raw DAC value."""
67
75
  val_raw = int(voltage * (2**M_DAC) / (V_REF_DAC * G_DAC))
68
76
  return min(max(val_raw, 0), 2**M_DAC - 1)
shepherd_core/commons.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Container for commonly shared constants."""
2
+
1
3
  samplerate_sps_default: int = 100_000
2
4
 
3
5
  uid_str_default: str = "SHEPHERD_NODE_ID"
@@ -1,3 +1,14 @@
1
+ """Container for shepherds data-models.
2
+
3
+ Public models are directly referenced here and are usable like:
4
+
5
+ '''python
6
+ from shepherd-core import data_models
7
+
8
+ cdata = data_models.CapeData(serial_number="A123")
9
+ '''
10
+ """
11
+
1
12
  from .base.calibration import CalibrationCape
2
13
  from .base.calibration import CalibrationEmulator
3
14
  from .base.calibration import CalibrationHarvester
@@ -1 +1,4 @@
1
- # these models import externally from: None
1
+ """Module for lowest hierarchy of data-models.
2
+
3
+ these models import externally from: None
4
+ """
@@ -1,3 +1,5 @@
1
+ """Models for the process of calibration a device by measurements."""
2
+
1
3
  from typing import List
2
4
  from typing import Optional
3
5
 
@@ -7,10 +9,10 @@ from pydantic import PositiveFloat
7
9
  from pydantic import validate_call
8
10
  from typing_extensions import Annotated
9
11
 
10
- from .. import CalibrationCape
11
- from .. import CalibrationEmulator
12
- from .. import CalibrationHarvester
13
- from .. import CalibrationPair
12
+ from .calibration import CalibrationCape
13
+ from .calibration import CalibrationEmulator
14
+ from .calibration import CalibrationHarvester
15
+ from .calibration import CalibrationPair
14
16
  from .calibration import CapeData
15
17
  from .shepherd import ShpModel
16
18
 
@@ -18,6 +20,8 @@ from .shepherd import ShpModel
18
20
 
19
21
 
20
22
  class CalMeasurementPair(ShpModel):
23
+ """Value-container for a calibration-measurement."""
24
+
21
25
  shepherd_raw: PositiveFloat
22
26
  reference_si: float = 0
23
27
 
@@ -27,7 +31,8 @@ CalMeasPairs = Annotated[List[CalMeasurementPair], Field(min_length=2)]
27
31
 
28
32
  @validate_call
29
33
  def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
30
- from scipy import stats # here due to massive delay
34
+ """Convert values from calibration-measurement to the calibration itself."""
35
+ from scipy import stats # placed here due to massive delay
31
36
 
32
37
  x = np.empty(len(data))
33
38
  y = np.empty(len(data))
@@ -40,14 +45,17 @@ def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
40
45
  rval = result.rvalue # test quality of regression
41
46
 
42
47
  if rval < 0.999:
43
- raise ValueError(
48
+ msg = (
44
49
  "Calibration faulty -> Correlation coefficient "
45
50
  f"(rvalue) = {rval}:.6f is too low for '{component}'"
46
51
  )
52
+ raise ValueError(msg)
47
53
  return CalibrationPair(offset=offset, gain=gain)
48
54
 
49
55
 
50
56
  class CalMeasurementHarvester(ShpModel):
57
+ """Container for the values of the calibration-measurement."""
58
+
51
59
  dac_V_Hrv: CalMeasPairs
52
60
  dac_V_Sim: CalMeasPairs
53
61
  adc_V_Sense: CalMeasPairs
@@ -62,6 +70,8 @@ class CalMeasurementHarvester(ShpModel):
62
70
 
63
71
 
64
72
  class CalMeasurementEmulator(ShpModel):
73
+ """Container for the values of the calibration-measurement."""
74
+
65
75
  dac_V_A: CalMeasPairs # TODO: why not V_dac_A or V_dac_a
66
76
  dac_V_B: CalMeasPairs
67
77
  adc_C_A: CalMeasPairs
@@ -76,6 +86,8 @@ class CalMeasurementEmulator(ShpModel):
76
86
 
77
87
 
78
88
  class CalMeasurementCape(ShpModel):
89
+ """Container for the values of the calibration-measurement."""
90
+
79
91
  cape: Optional[CapeData] = None
80
92
  host: Optional[str] = None
81
93
 
@@ -1,3 +1,5 @@
1
+ """Models for the calibration-data to convert between raw & SI-Values."""
2
+
1
3
  import struct
2
4
  from typing import Callable
3
5
  from typing import Generator
@@ -26,6 +28,10 @@ Calc_t = TypeVar("Calc_t", NDArray[np.float64], float)
26
28
  def dict_generator(
27
29
  in_dict: Union[dict, list], pre: Optional[list] = None
28
30
  ) -> Generator[list, None, None]:
31
+ """Recursive helper-function to generate a 1D-List(or not?).
32
+
33
+ TODO: isn't that a 1D-List generator?
34
+ """
29
35
  pre = pre[:] if pre else []
30
36
  if isinstance(in_dict, dict):
31
37
  for key, value in in_dict.items():
@@ -41,13 +47,13 @@ def dict_generator(
41
47
 
42
48
 
43
49
  class CalibrationPair(ShpModel):
44
- """SI-value [SI-Unit] = raw-value * gain + offset"""
50
+ """SI-value [SI-Unit] = raw-value * gain + offset."""
45
51
 
46
52
  gain: PositiveFloat
47
53
  offset: float = 0
48
54
 
49
55
  def raw_to_si(self, values_raw: Calc_t, *, allow_negative: bool = True) -> Calc_t:
50
- """Convert between physical units and raw unsigned integers"""
56
+ """Convert between physical units and raw unsigned integers."""
51
57
  values_si = values_raw * self.gain + self.offset
52
58
  if not allow_negative:
53
59
  if isinstance(values_si, np.ndarray):
@@ -60,17 +66,19 @@ class CalibrationPair(ShpModel):
60
66
  return values_si
61
67
 
62
68
  def si_to_raw(self, values_si: Calc_t) -> Calc_t:
63
- """Convert between physical units and raw unsigned integers"""
69
+ """Convert between physical units and raw unsigned integers."""
64
70
  values_raw = (values_si - self.offset) / self.gain
65
71
  if isinstance(values_raw, np.ndarray):
66
72
  values_raw[values_raw < 0.0] = 0.0
67
73
  values_raw = np.around(values_raw)
74
+ # TODO: overflow should also be prevented (add bit-width) -> fail or warn at both?
68
75
  else:
69
76
  values_raw = round(max(values_raw, 0.0))
70
77
  return values_raw
71
78
 
72
79
  @classmethod
73
80
  def from_fn(cls, fn: Callable) -> Self:
81
+ """Probe linear function to determine scaling values."""
74
82
  offset = fn(0)
75
83
  gain_inv = fn(1.0) - offset
76
84
  return cls(
@@ -88,13 +96,17 @@ cal_hrv_legacy = { # legacy translator
88
96
 
89
97
 
90
98
  class CalibrationHarvester(ShpModel):
99
+ """Container for all calibration-pairs for that device."""
100
+
91
101
  dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
92
102
  dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
93
103
  adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw)
94
104
  adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
95
105
 
96
106
  def export_for_sysfs(self) -> dict:
97
- """[scaling according to commons.h]
107
+ """Convert and write the essential data.
108
+
109
+ [scaling according to commons.h]
98
110
  # ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
99
111
  # ADC-V is handled in uV (micro-volt), gain is shifted by 8 bit
100
112
  # DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
@@ -111,7 +123,8 @@ class CalibrationHarvester(ShpModel):
111
123
  if (("gain" in key) and not (0 <= value < 2**32)) or (
112
124
  ("offset" in key) and not (-(2**31) <= value < 2**31)
113
125
  ):
114
- raise ValueError(f"Value ({key}={value}) exceeds uint32-container")
126
+ msg = f"Value ({key}={value}) exceeds uint32-container"
127
+ raise ValueError(msg)
115
128
  return cal_set
116
129
 
117
130
 
@@ -124,7 +137,10 @@ cal_emu_legacy = { # legacy translator
124
137
 
125
138
 
126
139
  class CalibrationEmulator(ShpModel):
127
- """Cal-Data for both Target-Ports A/B"""
140
+ """Container for all calibration-pairs for that device.
141
+
142
+ Differentiates between both target-ports A/B.
143
+ """
128
144
 
129
145
  dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
130
146
  dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
@@ -132,7 +148,9 @@ class CalibrationEmulator(ShpModel):
132
148
  adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
133
149
 
134
150
  def export_for_sysfs(self) -> dict:
135
- """[scaling according to commons.h]
151
+ """Convert and write the essential data.
152
+
153
+ [scaling according to commons.h]
136
154
  # ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
137
155
  # ADC-V -> unused by vsrc / emu
138
156
  # DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
@@ -150,13 +168,15 @@ class CalibrationEmulator(ShpModel):
150
168
  if (("gain" in key) and not (0 <= value < 2**32)) or (
151
169
  ("offset" in key) and not (-(2**31) <= value < 2**31)
152
170
  ):
153
- raise ValueError(f"Value ({key}={value}) exceeds uint32-container")
171
+ msg = f"Value ({key}={value}) exceeds uint32-container"
172
+ raise ValueError(msg)
154
173
  return cal_set
155
174
 
156
175
 
157
176
  class CapeData(ShpModel):
158
- """Representation of Beaglebone Cape information
159
- -> just provide serial-number on creation
177
+ """Representation of Beaglebone Cape information.
178
+
179
+ User must at least provide serial-number on creation.
160
180
 
161
181
  According to BeagleBone specifications, each cape should host an EEPROM
162
182
  that contains some standardized information about the type of cape,
@@ -178,12 +198,13 @@ class CapeData(ShpModel):
178
198
  # ⤷ produces something like '2023-01-01'
179
199
 
180
200
  def __repr__(self) -> str: # TODO: override useful?
181
- """string-representation allows print(model)"""
201
+ """string-representation allows print(model)."""
182
202
  return str(self.model_dump())
183
203
 
184
204
 
185
205
  class CalibrationCape(ShpModel):
186
206
  """Represents calibration data of shepherd cape.
207
+
187
208
  Defines the format of calibration data and provides convenient functions
188
209
  to read and write calibration data.
189
210
 
@@ -198,7 +219,8 @@ class CalibrationCape(ShpModel):
198
219
 
199
220
  @classmethod
200
221
  def from_bytestr(cls, data: bytes, cape: Optional[CapeData] = None) -> Self:
201
- """Instantiates calibration data based on byte string.
222
+ """Instantiate calibration data based on byte string.
223
+
202
224
  This is mainly used to deserialize data read from an EEPROM memory.
203
225
 
204
226
  Args:
@@ -220,11 +242,11 @@ class CalibrationCape(ShpModel):
220
242
  return cls(**dv)
221
243
 
222
244
  def to_bytestr(self) -> bytes:
223
- """Serializes calibration data to byte string.
245
+ """Serialize calibration data to byte string.
246
+
224
247
  Used to prepare data for writing it to EEPROM.
225
248
 
226
- Returns
227
- -------
249
+ Returns:
228
250
  Byte string representation of calibration values.
229
251
 
230
252
  """
@@ -234,6 +256,8 @@ class CalibrationCape(ShpModel):
234
256
 
235
257
 
236
258
  class CalibrationSeries(ShpModel):
259
+ """Cal-Data for a typical recording of a testbed experiment."""
260
+
237
261
  voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9)
238
262
  # ⤷ default allows 0 - 12 V in 3 nV-Steps
239
263
  current: CalibrationPair = CalibrationPair(gain=250 * 1e-12)
@@ -1,3 +1,5 @@
1
+ """Base-Model for all content."""
2
+
1
3
  import hashlib
2
4
  from datetime import datetime
3
5
  from typing import Optional
@@ -10,6 +12,7 @@ from pydantic import StringConstraints
10
12
  from pydantic import model_validator
11
13
  from typing_extensions import Annotated
12
14
  from typing_extensions import Self
15
+ from typing_extensions import deprecated
13
16
 
14
17
  from .shepherd import ShpModel
15
18
  from .timezone import local_now
@@ -23,15 +26,20 @@ SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
23
26
  # ⤷ Regex = All Printable ASCII-Characters with Space
24
27
 
25
28
 
29
+ @deprecated("use UUID4 instead")
26
30
  def id_default() -> int:
27
- # note: IdInt has space for 128 bit, so 128/4 = 32 hex-chars
31
+ """Generate a unique ID - usable as default value.
32
+
33
+ Note: IdInt has space for 128 bit, so 128/4 = 32 hex-chars
34
+ """
28
35
  time_stamp = str(local_now()).encode("utf-8")
29
36
  time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-32:]
30
37
  return int(time_hash, 16)
31
38
 
32
39
 
33
40
  class ContentModel(ShpModel):
34
- # General Properties
41
+ """Base-Model for content with generalized properties."""
42
+
35
43
  # id: UUID4 = Field( # TODO: db-migration - temp fix for documentation
36
44
  id: Union[UUID4, int] = Field(
37
45
  description="Unique ID",
@@ -1,3 +1,5 @@
1
+ """Shepherds base-model that brings a lot of default functionality."""
2
+
1
3
  import hashlib
2
4
  import pathlib
3
5
  from datetime import timedelta
@@ -24,14 +26,17 @@ from .wrapper import Wrapper
24
26
  def path2str(
25
27
  dumper: Dumper, data: Union[pathlib.Path, pathlib.WindowsPath, pathlib.PosixPath]
26
28
  ) -> ScalarNode:
29
+ """Add a yaml-representation for a specific datatype."""
27
30
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
28
31
 
29
32
 
30
33
  def time2int(dumper: Dumper, data: timedelta) -> ScalarNode:
34
+ """Add a yaml-representation for a specific datatype."""
31
35
  return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
32
36
 
33
37
 
34
38
  def generic2str(dumper: Dumper, data: Any) -> ScalarNode:
39
+ """Add a yaml-representation for a specific datatype."""
35
40
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data))
36
41
 
37
42
 
@@ -44,7 +49,7 @@ yaml.add_representer(UUID, generic2str, SafeDumper)
44
49
 
45
50
 
46
51
  class ShpModel(BaseModel):
47
- """Pre-configured Pydantic Base-Model (specifically for shepherd)
52
+ """Pre-configured Pydantic Base-Model (specifically for shepherd).
48
53
 
49
54
  Inheritable Features:
50
55
  - constant / frozen, hashable .get_hash()
@@ -69,17 +74,17 @@ class ShpModel(BaseModel):
69
74
  str_strip_whitespace=True, # strip leading & trailing whitespaces
70
75
  use_enum_values=True, # cleaner export of enum-parameters
71
76
  allow_inf_nan=False, # float without +-inf or NaN
72
- # defer_build, possible speedup -> but it triggers a bug
77
+ # defer_build=True, possible speedup -> but it triggers a bug
73
78
  )
74
79
 
75
80
  def __repr__(self) -> str:
76
- """string-representation allows print(model)"""
81
+ """string-representation allows print(model)."""
77
82
  name = type(self).__name__
78
83
  content = self.model_dump(exclude_unset=True, exclude_defaults=True)
79
84
  return f"{name}({content})"
80
85
 
81
86
  def __str__(self) -> str:
82
- """string-representation allows str(model)"""
87
+ """string-representation allows str(model)."""
83
88
  content = yaml.safe_dump(
84
89
  self.model_dump(exclude_unset=True, exclude_defaults=True),
85
90
  default_flow_style=False,
@@ -88,26 +93,29 @@ class ShpModel(BaseModel):
88
93
  return str(content)
89
94
 
90
95
  def __getitem__(self, key: str) -> Any:
91
- """Allows dict access -> model["key"], in addition to model.key"""
96
+ """Allow dict access like model["key"].
97
+
98
+ in addition to model.key.
99
+ """
92
100
  return self.__getattribute__(key)
93
101
 
94
102
  def __contains__(self, item: str) -> bool:
95
- """Allows checks like 'x in YClass'"""
103
+ """Allow checks like 'x in YClass'."""
96
104
  return item in self.model_dump().keys() # noqa: SIM118
97
105
  # more correct, but probably slower than hasattr
98
106
 
99
107
  def keys(self): # noqa: ANN201
100
- """Fn of dict"""
108
+ """Fn of dict."""
101
109
  return self.model_dump().keys()
102
110
 
103
111
  def items(self) -> Generator[tuple, None, None]:
104
- """Fn of dict"""
112
+ """Fn of dict."""
105
113
  for key in self.keys():
106
114
  yield key, self[key]
107
115
 
108
116
  @classmethod
109
117
  def schema_to_file(cls, path: Union[str, Path]) -> None:
110
- """Store schema to yaml (for frontend-generators)"""
118
+ """Store schema to yaml (for frontend-generators)."""
111
119
  model_dict = cls.model_json_schema()
112
120
  model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
113
121
  with Path(path).resolve().with_suffix(".yaml").open("w") as f:
@@ -120,9 +128,10 @@ class ShpModel(BaseModel):
120
128
  *,
121
129
  minimal: bool = True,
122
130
  ) -> Path:
123
- """Store data to yaml in a wrapper
131
+ """Store data to yaml in a wrapper.
132
+
124
133
  minimal: stores minimal set (filters out unset & default parameters)
125
- comment: documentation
134
+ comment: documentation.
126
135
  """
127
136
  model_dict = self.model_dump(exclude_unset=minimal)
128
137
  model_wrap = Wrapper(
@@ -146,7 +155,7 @@ class ShpModel(BaseModel):
146
155
 
147
156
  @classmethod
148
157
  def from_file(cls, path: Union[str, Path]) -> Self:
149
- """Load from yaml"""
158
+ """Load from yaml."""
150
159
  with Path(path).open() as shp_file:
151
160
  shp_dict = yaml.safe_load(shp_file)
152
161
  shp_wrap = Wrapper(**shp_dict)
@@ -1,3 +1,5 @@
1
+ """Helper-functions for better support of timezones."""
2
+
1
3
  import time
2
4
  from datetime import datetime
3
5
  from datetime import timedelta
@@ -5,14 +7,17 @@ from datetime import timezone
5
7
 
6
8
 
7
9
  def local_tz() -> timezone:
10
+ """Query the local timezone of the user."""
8
11
  if time.daylight:
9
12
  return timezone(timedelta(seconds=-time.altzone), time.tzname[1])
10
13
  return timezone(timedelta(seconds=-time.timezone), time.tzname[0])
11
14
 
12
15
 
13
16
  def local_now() -> datetime:
17
+ """Query the local date-time of the user."""
14
18
  return datetime.now(tz=local_tz())
15
19
 
16
20
 
17
21
  def local_iso_date() -> str:
22
+ """Query the local date-time of the user as a ISO 8601 date."""
18
23
  return local_now().date().isoformat()
@@ -1,3 +1,5 @@
1
+ """Wrapper-related ecosystem for transferring models."""
2
+
1
3
  from datetime import datetime
2
4
  from typing import Optional
3
5
 
@@ -10,9 +12,7 @@ SafeStrClone = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
10
12
 
11
13
 
12
14
  class Wrapper(BaseModel):
13
- """Prototype for enabling one web- & file-interface for
14
- all models with dynamic typecasting
15
- """
15
+ """Generalized web- & file-interface for all models with dynamic typecasting."""
16
16
 
17
17
  datatype: str
18
18
  # ⤷ model-name
@@ -1,3 +1,8 @@
1
+ """Module for content-type data-models.
2
+
3
+ These models import externally from: /base, /testbed.
4
+ """
5
+
1
6
  from .energy_environment import EnergyDType
2
7
  from .energy_environment import EnergyEnvironment
3
8
  from .firmware import Firmware
@@ -5,8 +10,6 @@ from .firmware_datatype import FirmwareDType
5
10
  from .virtual_harvester import VirtualHarvesterConfig
6
11
  from .virtual_source import VirtualSourceConfig
7
12
 
8
- # these models import externally from: /base, /testbed
9
-
10
13
  __all__ = [
11
14
  "EnergyEnvironment",
12
15
  "VirtualSourceConfig",
@@ -16,5 +19,3 @@ __all__ = [
16
19
  "EnergyDType",
17
20
  "FirmwareDType",
18
21
  ]
19
-
20
- # TODO: include models for end-users on root-level