shepherd-core 2023.12.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 (116) hide show
  1. shepherd_core/__init__.py +5 -4
  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 +41 -16
  8. shepherd_core/data_models/base/content.py +20 -5
  9. shepherd_core/data_models/base/shepherd.py +23 -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 +32 -16
  14. shepherd_core/data_models/content/energy_environment.py +7 -5
  15. shepherd_core/data_models/content/energy_environment_fixture.yaml +3 -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 +25 -20
  19. shepherd_core/data_models/content/virtual_harvester_fixture.yaml +1 -0
  20. shepherd_core/data_models/content/virtual_source.py +40 -23
  21. shepherd_core/data_models/content/virtual_source_fixture.yaml +1 -0
  22. shepherd_core/data_models/experiment/__init__.py +5 -4
  23. shepherd_core/data_models/experiment/experiment.py +16 -15
  24. shepherd_core/data_models/experiment/observer_features.py +18 -12
  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 +4 -2
  32. shepherd_core/data_models/task/programming.py +3 -1
  33. shepherd_core/data_models/task/testbed_tasks.py +10 -4
  34. shepherd_core/data_models/testbed/__init__.py +5 -2
  35. shepherd_core/data_models/testbed/cape.py +8 -6
  36. shepherd_core/data_models/testbed/gpio.py +11 -9
  37. shepherd_core/data_models/testbed/mcu.py +10 -10
  38. shepherd_core/data_models/testbed/observer.py +10 -5
  39. shepherd_core/data_models/testbed/observer_fixture.yaml +23 -22
  40. shepherd_core/data_models/testbed/target.py +5 -3
  41. shepherd_core/data_models/testbed/target_fixture.yaml +11 -11
  42. shepherd_core/data_models/testbed/testbed.py +6 -3
  43. shepherd_core/decoder_waveform/__init__.py +2 -0
  44. shepherd_core/decoder_waveform/uart.py +44 -25
  45. shepherd_core/fw_tools/__init__.py +2 -0
  46. shepherd_core/fw_tools/converter.py +20 -9
  47. shepherd_core/fw_tools/converter_elf.py +3 -0
  48. shepherd_core/fw_tools/patcher.py +16 -4
  49. shepherd_core/fw_tools/validation.py +25 -5
  50. shepherd_core/inventory/__init__.py +66 -6
  51. shepherd_core/inventory/python.py +4 -0
  52. shepherd_core/inventory/system.py +13 -1
  53. shepherd_core/inventory/target.py +4 -0
  54. shepherd_core/logger.py +5 -0
  55. shepherd_core/reader.py +44 -26
  56. shepherd_core/testbed_client/__init__.py +2 -0
  57. shepherd_core/testbed_client/cache_path.py +17 -0
  58. shepherd_core/testbed_client/client.py +14 -8
  59. shepherd_core/testbed_client/fixtures.py +30 -11
  60. shepherd_core/testbed_client/user_model.py +13 -6
  61. shepherd_core/vsource/__init__.py +2 -0
  62. shepherd_core/vsource/virtual_converter_model.py +11 -4
  63. shepherd_core/vsource/virtual_harvester_model.py +8 -1
  64. shepherd_core/vsource/virtual_source_model.py +10 -5
  65. shepherd_core/writer.py +28 -20
  66. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/METADATA +50 -34
  67. shepherd_core-2024.4.2.dist-info/RECORD +75 -0
  68. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/WHEEL +1 -1
  69. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/top_level.txt +0 -1
  70. shepherd_core-2023.12.1.dist-info/RECORD +0 -117
  71. tests/__init__.py +0 -0
  72. tests/conftest.py +0 -64
  73. tests/data_models/__init__.py +0 -0
  74. tests/data_models/conftest.py +0 -14
  75. tests/data_models/example_cal_data.yaml +0 -31
  76. tests/data_models/example_cal_data_faulty.yaml +0 -29
  77. tests/data_models/example_cal_meas.yaml +0 -178
  78. tests/data_models/example_cal_meas_faulty1.yaml +0 -142
  79. tests/data_models/example_cal_meas_faulty2.yaml +0 -136
  80. tests/data_models/example_config_emulator.yaml +0 -41
  81. tests/data_models/example_config_experiment.yaml +0 -16
  82. tests/data_models/example_config_experiment_alternative.yaml +0 -14
  83. tests/data_models/example_config_harvester.yaml +0 -15
  84. tests/data_models/example_config_testbed.yaml +0 -26
  85. tests/data_models/example_config_virtsource.yaml +0 -78
  86. tests/data_models/test_base_models.py +0 -205
  87. tests/data_models/test_content_fixtures.py +0 -41
  88. tests/data_models/test_content_models.py +0 -282
  89. tests/data_models/test_examples.py +0 -48
  90. tests/data_models/test_experiment_models.py +0 -277
  91. tests/data_models/test_task_generation.py +0 -52
  92. tests/data_models/test_task_models.py +0 -131
  93. tests/data_models/test_testbed_fixtures.py +0 -47
  94. tests/data_models/test_testbed_models.py +0 -187
  95. tests/decoder_waveform/__init__.py +0 -0
  96. tests/decoder_waveform/test_decoder.py +0 -34
  97. tests/fw_tools/__init__.py +0 -0
  98. tests/fw_tools/conftest.py +0 -5
  99. tests/fw_tools/test_converter.py +0 -76
  100. tests/fw_tools/test_patcher.py +0 -66
  101. tests/fw_tools/test_validation.py +0 -56
  102. tests/inventory/__init__.py +0 -0
  103. tests/inventory/test_inventory.py +0 -20
  104. tests/test_cal_hw.py +0 -34
  105. tests/test_examples.py +0 -40
  106. tests/test_logger.py +0 -15
  107. tests/test_reader.py +0 -283
  108. tests/test_writer.py +0 -169
  109. tests/testbed_client/__init__.py +0 -0
  110. tests/vsource/__init__.py +0 -0
  111. tests/vsource/conftest.py +0 -49
  112. tests/vsource/test_converter.py +0 -161
  113. tests/vsource/test_harvester.py +0 -73
  114. tests/vsource/test_z.py +0 -5
  115. /shepherd_core/data_models/{doc_virtual_source.py → virtual_source_doc.txt} +0 -0
  116. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/zip-safe +0 -0
shepherd_core/__init__.py CHANGED
@@ -1,9 +1,10 @@
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
 
6
6
  """
7
+
7
8
  from .data_models.base.calibration import Calc_t
8
9
  from .data_models.base.calibration import CalibrationCape
9
10
  from .data_models.base.calibration import CalibrationEmulator
@@ -12,7 +13,7 @@ from .data_models.base.calibration import CalibrationPair
12
13
  from .data_models.base.calibration import CalibrationSeries
13
14
  from .data_models.base.timezone import local_now
14
15
  from .data_models.base.timezone import local_tz
15
- from .data_models.task import Compression
16
+ from .data_models.task.emulation import Compression
16
17
  from .inventory import Inventory
17
18
  from .logger import get_verbose_level
18
19
  from .logger import increase_verbose_level
@@ -22,7 +23,7 @@ from .testbed_client.client import TestbedClient
22
23
  from .testbed_client.client import tb_client
23
24
  from .writer import Writer
24
25
 
25
- __version__ = "2023.12.1"
26
+ __version__ = "2024.4.2"
26
27
 
27
28
  __all__ = [
28
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,
@@ -165,7 +184,7 @@ class CapeData(ShpModel):
165
184
  `See<https://github.com/beagleboard/beaglebone-black/wiki/System-Reference-Manual#824_EEPROM_Data_Format>`_
166
185
  """
167
186
 
168
- header: conbytes(max_length=4) = b"\xAA\x55\x33\xEE"
187
+ header: conbytes(max_length=4) = b"\xaa\x55\x33\xee"
169
188
  eeprom_revision: constr(max_length=2) = "A2"
170
189
  board_name: constr(max_length=32) = "BeagleBone SHEPHERD2 Cape"
171
190
  version: constr(max_length=4) = "24B0"
@@ -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:
@@ -207,6 +228,7 @@ class CalibrationCape(ShpModel):
207
228
  cape: data can be supplied
208
229
  Returns:
209
230
  CalibrationCape object with extracted calibration data.
231
+
210
232
  """
211
233
  dv = cls().model_dump(include={"harvester", "emulator"})
212
234
  lw = list(dict_generator(dv))
@@ -219,12 +241,13 @@ class CalibrationCape(ShpModel):
219
241
  return cls(**dv)
220
242
 
221
243
  def to_bytestr(self) -> bytes:
222
- """Serializes calibration data to byte string.
244
+ """Serialize calibration data to byte string.
245
+
223
246
  Used to prepare data for writing it to EEPROM.
224
247
 
225
- Returns
226
- -------
248
+ Returns:
227
249
  Byte string representation of calibration values.
250
+
228
251
  """
229
252
  lw = list(dict_generator(self.model_dump(include={"harvester", "emulator"})))
230
253
  values = [walk[-1] for walk in lw]
@@ -232,6 +255,8 @@ class CalibrationCape(ShpModel):
232
255
 
233
256
 
234
257
  class CalibrationSeries(ShpModel):
258
+ """Cal-Data for a typical recording of a testbed experiment."""
259
+
235
260
  voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9)
236
261
  # ⤷ default allows 0 - 12 V in 3 nV-Steps
237
262
  current: CalibrationPair = CalibrationPair(gain=250 * 1e-12)
@@ -1,12 +1,18 @@
1
+ """Base-Model for all content."""
2
+
1
3
  import hashlib
2
4
  from datetime import datetime
3
5
  from typing import Optional
6
+ from typing import Union
7
+ from uuid import uuid4
4
8
 
9
+ from pydantic import UUID4
5
10
  from pydantic import Field
6
11
  from pydantic import StringConstraints
7
12
  from pydantic import model_validator
8
13
  from typing_extensions import Annotated
9
14
  from typing_extensions import Self
15
+ from typing_extensions import deprecated
10
16
 
11
17
  from .shepherd import ShpModel
12
18
  from .timezone import local_now
@@ -14,30 +20,39 @@ from .timezone import local_now
14
20
  # constr -> to_lower=True, max_length=16, regex=r"^[\w]+$"
15
21
  # ⤷ Regex = AlphaNum
16
22
  IdInt = Annotated[int, Field(ge=0, lt=2**128)]
17
- NameStr = Annotated[str, StringConstraints(max_length=32, pattern=r'^[^<>:;,?"*|\/\\]+$')]
23
+ NameStr = Annotated[str, StringConstraints(max_length=32, pattern=r"^[^<>:;,?\"\*|\/\\]+$")]
18
24
  # ⤷ Regex = FileSystem-Compatible ASCII
19
25
  SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
20
26
  # ⤷ Regex = All Printable ASCII-Characters with Space
21
27
 
22
28
 
29
+ @deprecated("use UUID4 instead")
23
30
  def id_default() -> int:
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
+ """
24
35
  time_stamp = str(local_now()).encode("utf-8")
25
- time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-16:]
36
+ time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-32:]
26
37
  return int(time_hash, 16)
27
38
 
28
39
 
29
40
  class ContentModel(ShpModel):
30
- # General Properties
31
- id: IdInt = Field( # noqa: A003
41
+ """Base-Model for content with generalized properties."""
42
+
43
+ # id: UUID4 = Field( # TODO: db-migration - temp fix for documentation
44
+ id: Union[UUID4, int] = Field(
32
45
  description="Unique ID",
33
- default_factory=id_default,
46
+ default_factory=uuid4,
34
47
  )
35
48
  name: NameStr
36
49
  description: Annotated[Optional[SafeStr], Field(description="Required when public")] = None
37
50
  comment: Optional[SafeStr] = None
38
51
  created: datetime = Field(default_factory=datetime.now)
52
+ updated_last: datetime = Field(default_factory=datetime.now)
39
53
 
40
54
  # Ownership & Access
55
+ # TODO: remove owner & group, only needed for DB
41
56
  owner: NameStr
42
57
  group: Annotated[NameStr, Field(description="University or Subgroup")]
43
58
  visible2group: bool = False
@@ -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
@@ -7,6 +9,7 @@ from typing import Any
7
9
  from typing import Generator
8
10
  from typing import Optional
9
11
  from typing import Union
12
+ from uuid import UUID
10
13
 
11
14
  import yaml
12
15
  from pydantic import BaseModel
@@ -23,14 +26,17 @@ from .wrapper import Wrapper
23
26
  def path2str(
24
27
  dumper: Dumper, data: Union[pathlib.Path, pathlib.WindowsPath, pathlib.PosixPath]
25
28
  ) -> ScalarNode:
29
+ """Add a yaml-representation for a specific datatype."""
26
30
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
27
31
 
28
32
 
29
33
  def time2int(dumper: Dumper, data: timedelta) -> ScalarNode:
34
+ """Add a yaml-representation for a specific datatype."""
30
35
  return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
31
36
 
32
37
 
33
38
  def generic2str(dumper: Dumper, data: Any) -> ScalarNode:
39
+ """Add a yaml-representation for a specific datatype."""
34
40
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data))
35
41
 
36
42
 
@@ -39,10 +45,11 @@ yaml.add_representer(pathlib.WindowsPath, path2str, SafeDumper)
39
45
  yaml.add_representer(pathlib.Path, path2str, SafeDumper)
40
46
  yaml.add_representer(timedelta, time2int, SafeDumper)
41
47
  yaml.add_representer(IPv4Address, generic2str, SafeDumper)
48
+ yaml.add_representer(UUID, generic2str, SafeDumper)
42
49
 
43
50
 
44
51
  class ShpModel(BaseModel):
45
- """Pre-configured Pydantic Base-Model (specifically for shepherd)
52
+ """Pre-configured Pydantic Base-Model (specifically for shepherd).
46
53
 
47
54
  Inheritable Features:
48
55
  - constant / frozen, hashable .get_hash()
@@ -67,17 +74,17 @@ class ShpModel(BaseModel):
67
74
  str_strip_whitespace=True, # strip leading & trailing whitespaces
68
75
  use_enum_values=True, # cleaner export of enum-parameters
69
76
  allow_inf_nan=False, # float without +-inf or NaN
70
- # defer_build, possible speedup -> but it triggers a bug
77
+ # defer_build=True, possible speedup -> but it triggers a bug
71
78
  )
72
79
 
73
80
  def __repr__(self) -> str:
74
- """string-representation allows print(model)"""
81
+ """string-representation allows print(model)."""
75
82
  name = type(self).__name__
76
83
  content = self.model_dump(exclude_unset=True, exclude_defaults=True)
77
84
  return f"{name}({content})"
78
85
 
79
86
  def __str__(self) -> str:
80
- """string-representation allows str(model)"""
87
+ """string-representation allows str(model)."""
81
88
  content = yaml.safe_dump(
82
89
  self.model_dump(exclude_unset=True, exclude_defaults=True),
83
90
  default_flow_style=False,
@@ -86,26 +93,29 @@ class ShpModel(BaseModel):
86
93
  return str(content)
87
94
 
88
95
  def __getitem__(self, key: str) -> Any:
89
- """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
+ """
90
100
  return self.__getattribute__(key)
91
101
 
92
102
  def __contains__(self, item: str) -> bool:
93
- """Allows checks like 'x in YClass'"""
103
+ """Allow checks like 'x in YClass'."""
94
104
  return item in self.model_dump().keys() # noqa: SIM118
95
105
  # more correct, but probably slower than hasattr
96
106
 
97
107
  def keys(self): # noqa: ANN201
98
- """Fn of dict"""
108
+ """Fn of dict."""
99
109
  return self.model_dump().keys()
100
110
 
101
111
  def items(self) -> Generator[tuple, None, None]:
102
- """Fn of dict"""
112
+ """Fn of dict."""
103
113
  for key in self.keys():
104
114
  yield key, self[key]
105
115
 
106
116
  @classmethod
107
117
  def schema_to_file(cls, path: Union[str, Path]) -> None:
108
- """Store schema to yaml (for frontend-generators)"""
118
+ """Store schema to yaml (for frontend-generators)."""
109
119
  model_dict = cls.model_json_schema()
110
120
  model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
111
121
  with Path(path).resolve().with_suffix(".yaml").open("w") as f:
@@ -118,9 +128,10 @@ class ShpModel(BaseModel):
118
128
  *,
119
129
  minimal: bool = True,
120
130
  ) -> Path:
121
- """Store data to yaml in a wrapper
131
+ """Store data to yaml in a wrapper.
132
+
122
133
  minimal: stores minimal set (filters out unset & default parameters)
123
- comment: documentation
134
+ comment: documentation.
124
135
  """
125
136
  model_dict = self.model_dump(exclude_unset=minimal)
126
137
  model_wrap = Wrapper(
@@ -144,7 +155,7 @@ class ShpModel(BaseModel):
144
155
 
145
156
  @classmethod
146
157
  def from_file(cls, path: Union[str, Path]) -> Self:
147
- """Load from yaml"""
158
+ """Load from yaml."""
148
159
  with Path(path).open() as shp_file:
149
160
  shp_dict = yaml.safe_load(shp_file)
150
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