shepherd-core 2024.4.1__py3-none-any.whl → 2024.4.2__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 +38 -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 +9 -6
  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 +16 -4
  53. shepherd_core/fw_tools/validation.py +26 -5
  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 +24 -19
  70. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.4.2.dist-info}/METADATA +2 -1
  71. shepherd_core-2024.4.2.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.4.2.dist-info}/WHEEL +0 -0
  75. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.4.2.dist-info}/top_level.txt +0 -0
  76. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.4.2.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.4.2"
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,7 +66,7 @@ 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
@@ -71,6 +77,7 @@ class CalibrationPair(ShpModel):
71
77
 
72
78
  @classmethod
73
79
  def from_fn(cls, fn: Callable) -> Self:
80
+ """Probe linear function to determine scaling values."""
74
81
  offset = fn(0)
75
82
  gain_inv = fn(1.0) - offset
76
83
  return cls(
@@ -88,13 +95,17 @@ cal_hrv_legacy = { # legacy translator
88
95
 
89
96
 
90
97
  class CalibrationHarvester(ShpModel):
98
+ """Container for all calibration-pairs for that device."""
99
+
91
100
  dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
92
101
  dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
93
102
  adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw)
94
103
  adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
95
104
 
96
105
  def export_for_sysfs(self) -> dict:
97
- """[scaling according to commons.h]
106
+ """Convert and write the essential data.
107
+
108
+ [scaling according to commons.h]
98
109
  # ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
99
110
  # ADC-V is handled in uV (micro-volt), gain is shifted by 8 bit
100
111
  # DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
@@ -111,7 +122,8 @@ class CalibrationHarvester(ShpModel):
111
122
  if (("gain" in key) and not (0 <= value < 2**32)) or (
112
123
  ("offset" in key) and not (-(2**31) <= value < 2**31)
113
124
  ):
114
- raise ValueError(f"Value ({key}={value}) exceeds uint32-container")
125
+ msg = f"Value ({key}={value}) exceeds uint32-container"
126
+ raise ValueError(msg)
115
127
  return cal_set
116
128
 
117
129
 
@@ -124,7 +136,10 @@ cal_emu_legacy = { # legacy translator
124
136
 
125
137
 
126
138
  class CalibrationEmulator(ShpModel):
127
- """Cal-Data for both Target-Ports A/B"""
139
+ """Container for all calibration-pairs for that device.
140
+
141
+ Differentiates between both target-ports A/B.
142
+ """
128
143
 
129
144
  dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
130
145
  dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
@@ -132,7 +147,9 @@ class CalibrationEmulator(ShpModel):
132
147
  adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
133
148
 
134
149
  def export_for_sysfs(self) -> dict:
135
- """[scaling according to commons.h]
150
+ """Convert and write the essential data.
151
+
152
+ [scaling according to commons.h]
136
153
  # ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
137
154
  # ADC-V -> unused by vsrc / emu
138
155
  # DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
@@ -150,13 +167,15 @@ class CalibrationEmulator(ShpModel):
150
167
  if (("gain" in key) and not (0 <= value < 2**32)) or (
151
168
  ("offset" in key) and not (-(2**31) <= value < 2**31)
152
169
  ):
153
- raise ValueError(f"Value ({key}={value}) exceeds uint32-container")
170
+ msg = f"Value ({key}={value}) exceeds uint32-container"
171
+ raise ValueError(msg)
154
172
  return cal_set
155
173
 
156
174
 
157
175
  class CapeData(ShpModel):
158
- """Representation of Beaglebone Cape information
159
- -> just provide serial-number on creation
176
+ """Representation of Beaglebone Cape information.
177
+
178
+ User must at least provide serial-number on creation.
160
179
 
161
180
  According to BeagleBone specifications, each cape should host an EEPROM
162
181
  that contains some standardized information about the type of cape,
@@ -178,12 +197,13 @@ class CapeData(ShpModel):
178
197
  # ⤷ produces something like '2023-01-01'
179
198
 
180
199
  def __repr__(self) -> str: # TODO: override useful?
181
- """string-representation allows print(model)"""
200
+ """string-representation allows print(model)."""
182
201
  return str(self.model_dump())
183
202
 
184
203
 
185
204
  class CalibrationCape(ShpModel):
186
205
  """Represents calibration data of shepherd cape.
206
+
187
207
  Defines the format of calibration data and provides convenient functions
188
208
  to read and write calibration data.
189
209
 
@@ -198,7 +218,8 @@ class CalibrationCape(ShpModel):
198
218
 
199
219
  @classmethod
200
220
  def from_bytestr(cls, data: bytes, cape: Optional[CapeData] = None) -> Self:
201
- """Instantiates calibration data based on byte string.
221
+ """Instantiate calibration data based on byte string.
222
+
202
223
  This is mainly used to deserialize data read from an EEPROM memory.
203
224
 
204
225
  Args:
@@ -220,11 +241,11 @@ class CalibrationCape(ShpModel):
220
241
  return cls(**dv)
221
242
 
222
243
  def to_bytestr(self) -> bytes:
223
- """Serializes calibration data to byte string.
244
+ """Serialize calibration data to byte string.
245
+
224
246
  Used to prepare data for writing it to EEPROM.
225
247
 
226
- Returns
227
- -------
248
+ Returns:
228
249
  Byte string representation of calibration values.
229
250
 
230
251
  """
@@ -234,6 +255,8 @@ class CalibrationCape(ShpModel):
234
255
 
235
256
 
236
257
  class CalibrationSeries(ShpModel):
258
+ """Cal-Data for a typical recording of a testbed experiment."""
259
+
237
260
  voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9)
238
261
  # ⤷ default allows 0 - 12 V in 3 nV-Steps
239
262
  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