shepherd-core 2025.10.1__tar.gz → 2026.2.3__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 (171) hide show
  1. shepherd_core-2026.2.3/LICENSE +21 -0
  2. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/PKG-INFO +5 -3
  3. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/eenv_generator.py +2 -2
  4. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/experiment_generic_var1.py +6 -6
  5. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/experiment_generic_var2.py +4 -4
  6. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/simulations/vharvester.py +7 -1
  7. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/simulations/vstorage.py +17 -15
  8. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/vsource_debug_sim.py +5 -0
  9. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/pyproject.toml +3 -12
  10. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/config.py +1 -1
  11. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/__init__.py +4 -2
  12. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/cal_measurement.py +7 -2
  13. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/calibration.py +23 -12
  14. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/content.py +10 -2
  15. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/shepherd.py +13 -4
  16. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/wrapper.py +2 -0
  17. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/__init__.py +4 -2
  18. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/_external_fixtures.yaml +104 -96
  19. shepherd_core-2026.2.3/shepherd_core/data_models/content/_metadata_eenvs_bonito.yaml +436 -0
  20. shepherd_core-2026.2.3/shepherd_core/data_models/content/_metadata_eenvs_synthetic_multivariate_random_walk.yaml +164 -0
  21. shepherd_core-2026.2.3/shepherd_core/data_models/content/_metadata_eenvs_synthetic_on_off_markov.yaml +3280 -0
  22. shepherd_core-2026.2.3/shepherd_core/data_models/content/_metadata_eenvs_synthetic_on_off_windows.yaml +3260 -0
  23. shepherd_core-2026.2.3/shepherd_core/data_models/content/_metadata_eenvs_synthetic_static.yaml +450 -0
  24. shepherd_core-2026.2.3/shepherd_core/data_models/content/energy_environment.py +370 -0
  25. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/energy_environment_fixture.yaml +24 -18
  26. shepherd_core-2026.2.3/shepherd_core/data_models/content/enum_datatypes.py +109 -0
  27. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/firmware.py +50 -18
  28. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_harvester_config.py +10 -93
  29. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_source_config.py +21 -2
  30. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_config.py +7 -4
  31. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_creator.py +1 -1
  32. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_param_experiments.py +4 -4
  33. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/experiment/experiment.py +38 -13
  34. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/experiment/observer_features.py +17 -4
  35. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/experiment/target_config.py +55 -7
  36. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/__init__.py +13 -2
  37. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/emulation.py +9 -5
  38. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/firmware_mod.py +3 -1
  39. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/harvest.py +2 -0
  40. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/helper_paths.py +2 -2
  41. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/observer_tasks.py +8 -6
  42. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/programming.py +4 -2
  43. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/task/testbed_tasks.py +8 -2
  44. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/cape.py +2 -0
  45. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/gpio.py +2 -0
  46. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/mcu.py +2 -0
  47. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/observer.py +2 -0
  48. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/target.py +7 -5
  49. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/target_fixture.old1 +1 -1
  50. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/target_fixture.yaml +1 -1
  51. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/testbed.py +17 -15
  52. shepherd_core-2026.2.3/shepherd_core/exit_handler.py +22 -0
  53. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/fw_tools/converter.py +2 -2
  54. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/fw_tools/validation.py +1 -1
  55. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/inventory/__init__.py +23 -21
  56. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/inventory/system.py +2 -2
  57. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/logger.py +0 -1
  58. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/reader.py +29 -25
  59. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/cache_path.py +3 -3
  60. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/client_abc_fix.py +14 -3
  61. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/client_web.py +7 -5
  62. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/fixtures.py +7 -7
  63. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/version.py +1 -1
  64. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_converter_model.py +2 -2
  65. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_harvester_model.py +2 -2
  66. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_harvester_simulation.py +5 -5
  67. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_source_model.py +1 -1
  68. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_source_simulation.py +9 -9
  69. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_storage_models_kibam.py +3 -3
  70. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/writer.py +16 -9
  71. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/PKG-INFO +5 -3
  72. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/SOURCES.txt +8 -1
  73. shepherd_core-2026.2.3/tests/data_models/conftest.py +40 -0
  74. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_base_models.py +3 -1
  75. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_content_fixtures.py +2 -2
  76. shepherd_core-2026.2.3/tests/data_models/test_content_models.py +649 -0
  77. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_experiment_models.py +86 -6
  78. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_task_generation.py +4 -4
  79. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_task_models.py +2 -2
  80. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/vsource/test_converter.py +8 -8
  81. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/vsource/test_harvester.py +4 -4
  82. shepherd_core-2025.10.1/shepherd_core/data_models/content/energy_environment.py +0 -52
  83. shepherd_core-2025.10.1/shepherd_core/data_models/content/firmware_datatype.py +0 -15
  84. shepherd_core-2025.10.1/tests/data_models/conftest.py +0 -14
  85. shepherd_core-2025.10.1/tests/data_models/test_content_models.py +0 -283
  86. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/README.md +0 -0
  87. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/experiment_from_yaml.yaml +0 -0
  88. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/experiment_models.py +0 -0
  89. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/firmware_model.py +0 -0
  90. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/firmware_modification.py +0 -0
  91. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/inventory.py +0 -0
  92. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/simulations/vsource.py +0 -0
  93. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/uart_decode_waveform.py +0 -0
  94. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/examples/uart_raw2.csv +0 -0
  95. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/setup.cfg +0 -0
  96. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/__init__.py +0 -0
  97. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/calibration_hw_def.py +0 -0
  98. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/commons.py +0 -0
  99. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/__init__.py +0 -0
  100. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/base/timezone.py +0 -0
  101. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_harvester_fixture.yaml +0 -0
  102. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_source_fixture.yaml +0 -0
  103. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_ideal.yaml +0 -0
  104. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_lead.yaml +0 -0
  105. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_lipo.yaml +0 -0
  106. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_mlcc.yaml +0 -0
  107. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_super.yaml +0 -0
  108. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/content/virtual_storage_fixture_tantal.yaml +0 -0
  109. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/experiment/__init__.py +0 -0
  110. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/readme.md +0 -0
  111. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/__init__.py +0 -0
  112. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/cape_fixture.yaml +0 -0
  113. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/gpio_fixture.yaml +0 -0
  114. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/mcu_fixture.yaml +0 -0
  115. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/observer_fixture.yaml +0 -0
  116. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/data_models/testbed/testbed_fixture.yaml +0 -0
  117. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/decoder_waveform/__init__.py +0 -0
  118. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/decoder_waveform/uart.py +0 -0
  119. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/fw_tools/__init__.py +0 -0
  120. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/fw_tools/converter_elf.py +0 -0
  121. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/fw_tools/patcher.py +0 -0
  122. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/inventory/python.py +0 -0
  123. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/inventory/target.py +0 -0
  124. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/__init__.py +0 -0
  125. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/testbed_client/user_model.py +0 -0
  126. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/__init__.py +0 -0
  127. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/target_model.py +0 -0
  128. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_storage_model.py +0 -0
  129. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_storage_model_fixed_point_math.py +0 -0
  130. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core/vsource/virtual_storage_simulator.py +0 -0
  131. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/dependency_links.txt +0 -0
  132. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/requires.txt +0 -0
  133. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/top_level.txt +0 -0
  134. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/shepherd_core.egg-info/zip-safe +0 -0
  135. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/__init__.py +0 -0
  136. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/conftest.py +0 -0
  137. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/__init__.py +0 -0
  138. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_cal_data.yaml +0 -0
  139. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_cal_data_faulty.yaml +0 -0
  140. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_cal_meas.yaml +0 -0
  141. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_cal_meas_faulty1.yaml +0 -0
  142. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_cal_meas_faulty2.yaml +0 -0
  143. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_emulator.yaml +0 -0
  144. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_experiment.yaml +0 -0
  145. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_experiment_alternative.yaml +0 -0
  146. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_harvester.yaml +0 -0
  147. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_testbed.yaml +0 -0
  148. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/example_config_virtsource.yaml +0 -0
  149. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_examples.py +0 -0
  150. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_testbed_fixtures.py +0 -0
  151. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/data_models/test_testbed_models.py +0 -0
  152. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/decoder_waveform/__init__.py +0 -0
  153. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/decoder_waveform/test_decoder.py +0 -0
  154. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/__init__.py +0 -0
  155. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/build_msp.elf +0 -0
  156. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/build_nrf.elf +0 -0
  157. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/conftest.py +0 -0
  158. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/test_converter.py +0 -0
  159. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/test_patcher.py +0 -0
  160. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/fw_tools/test_validation.py +0 -0
  161. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/inventory/__init__.py +0 -0
  162. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/inventory/test_inventory.py +0 -0
  163. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/test_cal_hw.py +0 -0
  164. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/test_examples.py +0 -0
  165. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/test_logger.py +0 -0
  166. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/test_reader.py +0 -0
  167. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/test_writer.py +0 -0
  168. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/testbed_client/__init__.py +0 -0
  169. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/vsource/__init__.py +0 -0
  170. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/vsource/conftest.py +0 -0
  171. {shepherd_core-2025.10.1 → shepherd_core-2026.2.3}/tests/vsource/test_z.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2025, Networked Embedded Systems Lab, TU Dresden, Ingmar Splitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
- Name: shepherd_core
3
- Version: 2025.10.1
2
+ Name: shepherd-core
3
+ Version: 2026.2.3
4
4
  Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
5
5
  Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
6
6
  Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
7
+ License-Expression: MIT
7
8
  Project-URL: Documentation, https://github.com/nes-lab/shepherd-tools/blob/main/README.md
8
9
  Project-URL: Issues, https://github.com/nes-lab/shepherd-tools/issues
9
10
  Project-URL: Source, https://pypi.org/project/shepherd-core/
@@ -23,11 +24,11 @@ Classifier: Programming Language :: Python :: 3.11
23
24
  Classifier: Programming Language :: Python :: 3.12
24
25
  Classifier: Programming Language :: Python :: 3.13
25
26
  Classifier: Programming Language :: Python :: 3.14
26
- Classifier: License :: OSI Approved :: MIT License
27
27
  Classifier: Operating System :: OS Independent
28
28
  Classifier: Natural Language :: English
29
29
  Requires-Python: >=3.10
30
30
  Description-Content-Type: text/markdown
31
+ License-File: LICENSE
31
32
  Requires-Dist: h5py
32
33
  Requires-Dist: numpy
33
34
  Requires-Dist: pyYAML
@@ -50,6 +51,7 @@ Requires-Dist: pytest; extra == "test"
50
51
  Requires-Dist: coverage; extra == "test"
51
52
  Provides-Extra: all
52
53
  Requires-Dist: shepherd-core[dev,elf,inventory,test]; extra == "all"
54
+ Dynamic: license-file
53
55
 
54
56
  # Core Library
55
57
 
@@ -1,4 +1,4 @@
1
- """Create a set of static artificial energy environments."""
1
+ """Create a set of static synthetic energy environments."""
2
2
 
3
3
  from itertools import product
4
4
  from pathlib import Path
@@ -29,7 +29,7 @@ for _v, _c in product(voltages_V, currents_A):
29
29
  log.info("File exists, will skip: %s", file_path.name)
30
30
  else:
31
31
  with ShpWriter(file_path) as file:
32
- file.store_hostname("artificial")
32
+ file.store_hostname("synthetic")
33
33
  # values in SI units
34
34
  timestamp_vector = np.arange(0.0, duration_s, file.sample_interval_ns / 1e9)
35
35
  voltage_vector = np.linspace(_v, _v, int(file.samplerate_sps * duration_s))
@@ -30,20 +30,20 @@ do_connect = False
30
30
  if do_connect:
31
31
  WebClient()
32
32
 
33
- xp = sm.Experiment(
33
+ exp = sm.Experiment(
34
34
  name="meaningful_TestName",
35
35
  # time_start could be "2033-03-13 14:15:16" or "datetime.now() + timedelta(minutes=30)"
36
- duration=30,
36
+ duration=30, # ty: ignore[invalid-argument-type]
37
37
  target_configs=[
38
38
  sm.TargetConfig(
39
39
  target_IDs=range(7, 12),
40
40
  custom_IDs=range(1, 100), # note: longer list is OK
41
- energy_env=sm.EnergyEnvironment(name="eenv_static_3000mV_50mA_3600s"),
41
+ energy_env=sm.EnergyEnvironment(name="synthetic_static_3000mV_50mA"),
42
42
  firmware1=sm.Firmware(
43
43
  name="FW_TestXYZ",
44
44
  data=Path("/var/shepherd/content/fw/nes_lab/nrf52_demo_rf/build.elf"),
45
45
  data_type=sm.FirmwareDType.path_elf,
46
- data_local=False,
46
+ data_2_copy=False,
47
47
  mcu=MCU(name="nRF52"),
48
48
  ),
49
49
  power_tracing=None,
@@ -52,10 +52,10 @@ xp = sm.Experiment(
52
52
  ),
53
53
  ],
54
54
  )
55
- xp.to_file("experiment_generic_var1.yaml")
55
+ exp.to_file("experiment_generic_var1.yaml")
56
56
 
57
57
  # Create a tasks-list for the testbed
58
- tb_tasks = TestbedTasks.from_xp(xp)
58
+ tb_tasks = TestbedTasks.from_xp(exp)
59
59
  tb_tasks.to_file("experiment_generic_var1_tbt.yaml")
60
60
 
61
61
  # next steps:
@@ -28,7 +28,7 @@ do_connect = False
28
28
  if do_connect:
29
29
  WebClient()
30
30
 
31
- xp = sm.Experiment(
31
+ exp = sm.Experiment(
32
32
  name="meaningful_TestName",
33
33
  # time_start could be "2033-03-13 14:15:16" or "datetime.now() + timedelta(minutes=30)"
34
34
  duration=30,
@@ -36,7 +36,7 @@ xp = sm.Experiment(
36
36
  sm.TargetConfig(
37
37
  target_IDs=range(7, 12),
38
38
  custom_IDs=range(1, 100), # note: longer list is OK
39
- energy_env=sm.EnergyEnvironment(name="eenv_static_3000mV_50mA_3600s"),
39
+ energy_env=sm.EnergyEnvironment(name="synthetic_static_3000mV_50mA"),
40
40
  firmware1=sm.Firmware.from_firmware(
41
41
  file=Path("./firmware_nrf.elf").absolute(),
42
42
  ),
@@ -45,10 +45,10 @@ xp = sm.Experiment(
45
45
  ),
46
46
  ],
47
47
  )
48
- xp.to_file("experiment_generic_var2.yaml")
48
+ exp.to_file("experiment_generic_var2.yaml")
49
49
 
50
50
  # Create a tasks-list for the testbed
51
- tb_tasks = TestbedTasks.from_xp(xp)
51
+ tb_tasks = TestbedTasks.from_xp(exp)
52
52
  tb_tasks.to_file("experiment_generic_var2_tbt.yaml")
53
53
 
54
54
  # next steps:
@@ -15,6 +15,8 @@ E_out = 17.811 mWs -> mppt_opt
15
15
 
16
16
  """
17
17
 
18
+ import os
19
+ import sys
18
20
  from pathlib import Path
19
21
 
20
22
  from shepherd_core.data_models import VirtualHarvesterConfig
@@ -23,8 +25,12 @@ from shepherd_core.vsource import simulate_harvester
23
25
  from shepherd_core import Reader
24
26
  from shepherd_data import ivonne
25
27
 
28
+ DURATION_MAX = 1 if "PYTEST_CURRENT_TEST" in os.environ else sys.float_info.max
29
+ # ⤷ limits runtime for pytest
30
+
26
31
  # config simulation
27
- sim_duration = 32
32
+ sim_duration = min(32, DURATION_MAX)
33
+ # ⤷ limits runtime for pytest
28
34
  file_ivonne = Path(__file__).parents[3] / "shepherd_data/examples/jogging_10m.iv"
29
35
  file_ivcurve = Path(__file__).parent / "jogging_ivcurve.h5"
30
36
 
@@ -30,7 +30,7 @@ from shepherd_core import log
30
30
 
31
31
  path_here = Path(__file__).parent
32
32
 
33
- duration_max = 20 if "PYTEST_CURRENT_TEST" in os.environ else sys.float_info.max
33
+ DURATION_MAX = 1 if "PYTEST_CURRENT_TEST" in os.environ else sys.float_info.max
34
34
  # ⤷ limits runtime for pytest
35
35
 
36
36
 
@@ -98,7 +98,7 @@ def experiment_current_ramp_pos(config: VirtualStorageConfig) -> None:
98
98
  """Charge virtual storage with a positive current ramp (increasing power)."""
99
99
  dt_s = 0.1
100
100
  SoC_start = 0.5
101
- duration_s = min(200, duration_max)
101
+ duration_s = min(200, DURATION_MAX)
102
102
  sim = StorageSimulator(
103
103
  models=get_models(SoC_start, config, dt_s),
104
104
  dt_s=dt_s,
@@ -108,14 +108,14 @@ def experiment_current_ramp_pos(config: VirtualStorageConfig) -> None:
108
108
  return 0.1 + 0.15 * t_s / duration_s # pru-model can handle +- 268 mA
109
109
 
110
110
  sim.run(fn=current_trace, duration_s=duration_s)
111
- sim.plot(path_here, f"XP {config.name}, current charge ramp (positive)")
111
+ sim.plot(path_here, f"Experiment {config.name}, current charge ramp (positive)")
112
112
 
113
113
 
114
114
  def experiment_current_ramp_neg(config: VirtualStorageConfig) -> None:
115
115
  """Discharge virtual storage with a negative current ramp (increasing power)."""
116
116
  dt_s = 0.1
117
117
  SoC_start = 0.5
118
- duration_s = min(200, duration_max)
118
+ duration_s = min(200, DURATION_MAX)
119
119
  sim = StorageSimulator(
120
120
  models=get_models(SoC_start, config, dt_s),
121
121
  dt_s=dt_s,
@@ -125,7 +125,7 @@ def experiment_current_ramp_neg(config: VirtualStorageConfig) -> None:
125
125
  return -(0.1 + 0.14 * t_s / duration_s) # pru-model can handle +- 268 mA
126
126
 
127
127
  sim.run(fn=current_trace, duration_s=duration_s)
128
- sim.plot(path_here, f"XP {config.name}, current discharge ramp (negative)")
128
+ sim.plot(path_here, f"Experiment {config.name}, current discharge ramp (negative)")
129
129
 
130
130
 
131
131
  def experiment_pulsed_discharge(config: VirtualStorageConfig) -> None:
@@ -140,8 +140,8 @@ def experiment_pulsed_discharge(config: VirtualStorageConfig) -> None:
140
140
  models=get_models(SoC_start, config, dt_s),
141
141
  dt_s=dt_s,
142
142
  )
143
- sim.run(fn=i_pulse.step, duration_s=min(1_000, duration_max))
144
- sim.plot(path_here, f"XP {config.name}, pulsed discharge .1A, 1000 s (figure_9a)")
143
+ sim.run(fn=i_pulse.step, duration_s=min(1_000, DURATION_MAX))
144
+ sim.plot(path_here, f"Experiment {config.name}, pulsed discharge .1A, 1000 s (figure_9a)")
145
145
 
146
146
 
147
147
  def experiment_pulsed_charge(config: VirtualStorageConfig) -> None:
@@ -156,8 +156,8 @@ def experiment_pulsed_charge(config: VirtualStorageConfig) -> None:
156
156
  models=get_models(SoC_start, config, dt_s),
157
157
  dt_s=dt_s,
158
158
  )
159
- sim.run(fn=i_pulse.step, duration_s=min(1_000, duration_max))
160
- sim.plot(path_here, f"XP {config.name}, pulsed charge .1A, 1000 s (figure_9b)")
159
+ sim.run(fn=i_pulse.step, duration_s=min(1_000, DURATION_MAX))
160
+ sim.plot(path_here, f"Experiment {config.name}, pulsed charge .1A, 1000 s (figure_9b)")
161
161
 
162
162
 
163
163
  def experiment_pulsed_resistive_charge(config: VirtualStorageConfig) -> None:
@@ -169,8 +169,10 @@ def experiment_pulsed_resistive_charge(config: VirtualStorageConfig) -> None:
169
169
  models=get_models(SoC_start, config, dt_s),
170
170
  dt_s=dt_s,
171
171
  )
172
- sim.run(fn=i_pulse.step, duration_s=min(3_000, duration_max))
173
- sim.plot(path_here, f"XP {config.name}, pulsed resistive charge 20 Ohm to 4.2 V, 3000 s")
172
+ sim.run(fn=i_pulse.step, duration_s=min(3_000, DURATION_MAX))
173
+ sim.plot(
174
+ path_here, f"Experiment {config.name}, pulsed resistive charge 20 Ohm to 4.2 V, 3000 s"
175
+ )
174
176
 
175
177
 
176
178
  def experiment_resistive_load(config: VirtualStorageConfig) -> None:
@@ -185,8 +187,8 @@ def experiment_resistive_load(config: VirtualStorageConfig) -> None:
185
187
  models=get_models(SoC_start, config, dt_s),
186
188
  dt_s=dt_s,
187
189
  )
188
- sim.run(fn=i_charge, duration_s=min(1_000, duration_max))
189
- sim.plot(path_here, f"XP {config.name}, resistive load 20 Ohm from 4.2 V, 1000 s")
190
+ sim.run(fn=i_charge, duration_s=min(1_000, DURATION_MAX))
191
+ sim.plot(path_here, f"Experiment {config.name}, resistive load 20 Ohm from 4.2 V, 1000 s")
190
192
 
191
193
 
192
194
  def experiment_self_discharge() -> None:
@@ -207,10 +209,10 @@ def experiment_self_discharge() -> None:
207
209
  def step(_t: float, _s: float, _v: float) -> float:
208
210
  return 0
209
211
 
210
- sim.run(fn=step, duration_s=min(duration.total_seconds(), duration_max))
212
+ sim.run(fn=step, duration_s=min(duration.total_seconds(), DURATION_MAX))
211
213
  sim.plot(
212
214
  path_here,
213
- f"XP {config.name}, self-discharge, "
215
+ f"Experiment {config.name}, self-discharge, "
214
216
  f"SoC {SoC_start} to {SoC_target} in {duration.total_seconds()} s",
215
217
  )
216
218
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  """
12
12
 
13
+ import os
13
14
  from itertools import product
14
15
 
15
16
  import matplotlib.pyplot as plt
@@ -38,6 +39,10 @@ src_list = [
38
39
  I_mcu_sleep_A = 200e-9
39
40
  I_mcu_active_A = 1e-3
40
41
 
42
+ # limit runtime for pytest
43
+ if "PYTEST_CURRENT_TEST" in os.environ:
44
+ sample_dur_list = [10_000]
45
+
41
46
  # For online-queries the lib can be connected to the testbed-server.
42
47
  # NOTE: there are 3 states:
43
48
  # - unconnected -> demo-fixtures are queried (locally)
@@ -1,12 +1,13 @@
1
1
  [project]
2
- name = "shepherd_core"
2
+ name = "shepherd-core"
3
3
  description = "Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed"
4
4
  keywords = ["testbed", "beaglebone", "pru", "batteryless", "energyharvesting", "solar"]
5
5
 
6
6
  authors = [{name = "Ingmar Splitt", email = "ingmar.splitt@tu-dresden.de"},]
7
7
  maintainers = [{name = "Ingmar Splitt", email = "ingmar.splitt@tu-dresden.de"},]
8
8
 
9
- license = {file = "LICENSE"}
9
+ license = "MIT"
10
+ license-files = ["LICENSE"] # can't be in parent dir
10
11
  dynamic = ["version"]
11
12
 
12
13
  classifiers = [
@@ -19,7 +20,6 @@ classifiers = [
19
20
  "Programming Language :: Python :: 3.12",
20
21
  "Programming Language :: Python :: 3.13",
21
22
  "Programming Language :: Python :: 3.14",
22
- "License :: OSI Approved :: MIT License",
23
23
  "Operating System :: OS Independent",
24
24
  "Natural Language :: English",
25
25
  ]
@@ -104,12 +104,3 @@ addopts = "-vvv --stepwise" # opts: verbose result for each tests
104
104
  [tool.coverage.run]
105
105
  source = ["shepherd_core"]
106
106
  omit = ["*/shepherd_data/*"]
107
-
108
- [tool.mypy]
109
- python_version = 3.10
110
- ignore_missing_imports = true
111
- disable_error_code = ["call-arg", ]
112
- exclude = [
113
- "build/",
114
- ".egg-info/",
115
- ]
@@ -27,7 +27,7 @@ class ConfigDefault(BaseModel):
27
27
  UID_SIZE: int = 2
28
28
  """Variable size in Byte"""
29
29
 
30
- TESTBED_SERVER: HttpUrl = "https://shepherd.cfaed.tu-dresden.de:8000/"
30
+ TESTBED_SERVER: HttpUrl = HttpUrl("https://shepherd.cfaed.tu-dresden.de:8000/")
31
31
  """Server that holds up to date testbed fixtures"""
32
32
 
33
33
 
@@ -18,10 +18,11 @@ from .base.calibration import CapeData
18
18
  from .base.content import ContentModel
19
19
  from .base.shepherd import ShpModel
20
20
  from .base.wrapper import Wrapper
21
- from .content.energy_environment import EnergyDType
22
21
  from .content.energy_environment import EnergyEnvironment
22
+ from .content.energy_environment import EnergyProfile
23
+ from .content.enum_datatypes import EnergyDType
24
+ from .content.enum_datatypes import FirmwareDType
23
25
  from .content.firmware import Firmware
24
- from .content.firmware import FirmwareDType
25
26
  from .content.virtual_harvester_config import VirtualHarvesterConfig
26
27
  from .content.virtual_source_config import VirtualSourceConfig
27
28
  from .content.virtual_storage_config import VirtualStorageConfig
@@ -45,6 +46,7 @@ __all__ = [
45
46
  "ContentModel",
46
47
  "EnergyDType",
47
48
  "EnergyEnvironment",
49
+ "EnergyProfile",
48
50
  "Experiment",
49
51
  "Firmware",
50
52
  "FirmwareDType",
@@ -1,6 +1,7 @@
1
1
  """Models for the process of calibration a device by measurements."""
2
2
 
3
3
  from typing import Annotated
4
+ from typing import final
4
5
 
5
6
  import numpy as np
6
7
  from pydantic import Field
@@ -15,6 +16,7 @@ from .calibration import CapeData
15
16
  from .shepherd import ShpModel
16
17
 
17
18
 
19
+ @final
18
20
  class CalMeasurementPair(ShpModel):
19
21
  """Value-container for a calibration-measurement."""
20
22
 
@@ -39,8 +41,8 @@ def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
39
41
  gain: float = float(model[0])
40
42
 
41
43
  # r-squared, Pearson correlation coefficient
42
- _p = np.poly1d(model)
43
- yhat = _p(x)
44
+ pcoef = np.poly1d(model)
45
+ yhat = pcoef(x)
44
46
  ybar: float = np.sum(y) / len(y)
45
47
  ssreg: float = np.sum((yhat - ybar) ** 2)
46
48
  sstot: float = np.sum((y - ybar) ** 2)
@@ -55,6 +57,7 @@ def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
55
57
  return CalibrationPair(offset=offset, gain=gain)
56
58
 
57
59
 
60
+ @final
58
61
  class CalMeasurementHarvester(ShpModel):
59
62
  """Container for the values of the calibration-measurement."""
60
63
 
@@ -71,6 +74,7 @@ class CalMeasurementHarvester(ShpModel):
71
74
  return CalibrationHarvester(**dcal)
72
75
 
73
76
 
77
+ @final
74
78
  class CalMeasurementEmulator(ShpModel):
75
79
  """Container for the values of the calibration-measurement."""
76
80
 
@@ -87,6 +91,7 @@ class CalMeasurementEmulator(ShpModel):
87
91
  return CalibrationEmulator(**dcal)
88
92
 
89
93
 
94
+ @final
90
95
  class CalMeasurementCape(ShpModel):
91
96
  """Container for the values of the calibration-measurement."""
92
97
 
@@ -5,14 +5,16 @@ from collections.abc import Callable
5
5
  from collections.abc import Generator
6
6
  from collections.abc import Mapping
7
7
  from collections.abc import Sequence
8
+ from typing import Annotated
8
9
  from typing import TypeVar
10
+ from typing import final
9
11
 
10
12
  import numpy as np
11
13
  from numpy.typing import NDArray
12
14
  from pydantic import Field
13
15
  from pydantic import PositiveFloat
16
+ from pydantic import StringConstraints
14
17
  from pydantic import conbytes
15
- from pydantic import constr
16
18
  from pydantic import validate_call
17
19
  from typing_extensions import Self
18
20
 
@@ -47,6 +49,7 @@ def dict_generator(
47
49
  yield [*pre, in_dict]
48
50
 
49
51
 
52
+ @final
50
53
  class CalibrationPair(ShpModel):
51
54
  """SI-value [SI-Unit] = raw-value * gain + offset."""
52
55
 
@@ -56,11 +59,11 @@ class CalibrationPair(ShpModel):
56
59
 
57
60
  def raw_to_si(self, values_raw: Calc_t, *, allow_negative: bool = True) -> Calc_t:
58
61
  """Convert between physical units and raw unsigned integers."""
59
- values_si = values_raw * self.gain + self.offset
62
+ values_si = self.gain * values_raw + self.offset
60
63
  if not allow_negative:
61
64
  if isinstance(values_si, np.ndarray):
62
65
  values_si[values_si < 0.0] = 0.0
63
- # if pyright still complains, cast with .astype(float)
66
+ # if type-checker still complains, cast with .astype(float)
64
67
  else:
65
68
  values_si = float(max(values_si, 0.0))
66
69
  elif not isinstance(values_si, np.ndarray):
@@ -99,6 +102,7 @@ cal_pair_adc_V = CalibrationPair.from_fn(adc_voltage_to_raw, unit="V")
99
102
  cal_pair_adc_C = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
100
103
 
101
104
 
105
+ @final
102
106
  class CalibrationHarvester(ShpModel):
103
107
  """Container for all calibration-pairs for that device."""
104
108
 
@@ -140,6 +144,7 @@ cal_emu_legacy = { # legacy translator
140
144
  }
141
145
 
142
146
 
147
+ @final
143
148
  class CalibrationEmulator(ShpModel):
144
149
  """Container for all calibration-pairs for that device.
145
150
 
@@ -177,6 +182,7 @@ class CalibrationEmulator(ShpModel):
177
182
  return cal_set
178
183
 
179
184
 
185
+ @final
180
186
  class CapeData(ShpModel):
181
187
  """Representation of Beaglebone Cape information.
182
188
 
@@ -190,15 +196,17 @@ class CapeData(ShpModel):
190
196
  """
191
197
 
192
198
  header: conbytes(max_length=4) = b"\xaa\x55\x33\xee"
193
- eeprom_revision: constr(max_length=2) = "A2"
194
- board_name: constr(max_length=32) = "BeagleBone SHEPHERD2 Cape"
195
- version: constr(max_length=4) = "24B0"
196
- manufacturer: constr(max_length=16) = "NES TU DRESDEN"
197
- part_number: constr(max_length=16) = "BB-SHPRD"
199
+ eeprom_revision: Annotated[str, StringConstraints(max_length=2)] = "A2"
200
+ board_name: Annotated[str, StringConstraints(max_length=32)] = "BeagleBone SHEPHERD2 Cape"
201
+ version: Annotated[str, StringConstraints(max_length=4)] = "24B0"
202
+ manufacturer: Annotated[str, StringConstraints(max_length=16)] = "NES TU DRESDEN"
203
+ part_number: Annotated[str, StringConstraints(max_length=16)] = "BB-SHPRD"
198
204
 
199
- serial_number: constr(max_length=12)
205
+ serial_number: Annotated[str, StringConstraints(max_length=12)]
200
206
 
201
- cal_date: constr(max_length=12) = Field(default_factory=local_iso_date)
207
+ cal_date: Annotated[str, StringConstraints(max_length=12)] = Field(
208
+ default_factory=local_iso_date
209
+ )
202
210
  # ⤷ produces something like '2023-01-01'
203
211
 
204
212
  def __repr__(self) -> str: # TODO: override useful?
@@ -206,6 +214,7 @@ class CapeData(ShpModel):
206
214
  return str(self.model_dump())
207
215
 
208
216
 
217
+ @final
209
218
  class CalibrationCape(ShpModel):
210
219
  """Represents calibration data of shepherd cape.
211
220
 
@@ -231,6 +240,7 @@ class CalibrationCape(ShpModel):
231
240
  ----
232
241
  data: Byte string containing calibration data.
233
242
  cape: data can be supplied
243
+
234
244
  Returns:
235
245
  CalibrationCape object with extracted calibration data.
236
246
 
@@ -240,9 +250,9 @@ class CalibrationCape(ShpModel):
240
250
  lw2 = [elem for elem in lw1 if isinstance(elem[-1], float)]
241
251
  values = struct.unpack(">" + len(lw2) * "d", data)
242
252
  # ⤷ X => double float, big endian
243
- for _i, walk in enumerate(lw2):
253
+ for i_, walk in enumerate(lw2):
244
254
  # hardcoded fixed depth ... bad but easy
245
- dv[walk[0]][walk[1]][walk[2]] = float(values[_i])
255
+ dv[walk[0]][walk[1]][walk[2]] = float(values[i_])
246
256
  dv["cape"] = cape
247
257
  return cls(**dv)
248
258
 
@@ -260,6 +270,7 @@ class CalibrationCape(ShpModel):
260
270
  return struct.pack(">" + len(values) * "d", *values)
261
271
 
262
272
 
273
+ @final
263
274
  class CalibrationSeries(ShpModel):
264
275
  """Cal-Data for a typical recording of a testbed experiment."""
265
276
 
@@ -9,12 +9,15 @@ from pydantic import StringConstraints
9
9
  from pydantic import model_validator
10
10
  from typing_extensions import Self
11
11
 
12
+ from shepherd_core.logger import log
13
+ from shepherd_core.version import version as core_ver
14
+
12
15
  from .shepherd import ShpModel
13
16
 
14
17
  # constr -> to_lower=True, max_length=16, regex=r"^[\w]+$"
15
18
  # ⤷ Regex = AlphaNum
16
19
  IdInt = Annotated[int, Field(ge=0, lt=2**128)]
17
- NameStr = Annotated[str, StringConstraints(max_length=32, pattern=r"^[^<>:;,?\"\*|\/\\]+$")]
20
+ NameStr = Annotated[str, StringConstraints(max_length=50, pattern=r"^[^<>:;,?\"\*|\/\\]+$")]
18
21
  # ⤷ Regex = FileSystem-Compatible ASCII
19
22
  SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
20
23
  # ⤷ Regex = All Printable ASCII-Characters with Space
@@ -41,6 +44,8 @@ class ContentModel(ShpModel):
41
44
  comment: SafeStr | None = None
42
45
  created: datetime = Field(default_factory=datetime.now)
43
46
  updated_last: datetime = Field(default_factory=datetime.now)
47
+ deprecated: str | None = None
48
+ """ ⤷ deprecation-comments provoke a warning during validation of model."""
44
49
  # TODO: add dedicated 'inherit_from' field?
45
50
 
46
51
  # Ownership & Access
@@ -50,7 +55,8 @@ class ContentModel(ShpModel):
50
55
  visible2group: bool = False
51
56
  visible2all: bool = False
52
57
 
53
- # TODO: we probably need to remember the lib-version for content &| experiment
58
+ sw_ver: Annotated[str, Field(default=core_ver)]
59
+ """ ⤷ store core-version with content for compatibility management."""
54
60
 
55
61
  def __str__(self) -> str:
56
62
  return self.name
@@ -62,4 +68,6 @@ class ContentModel(ShpModel):
62
68
  raise ValueError(
63
69
  "Public instances require a description (check visible2*- and description-field)"
64
70
  )
71
+ if isinstance(self.deprecated, str) and len(self.deprecated) > 0:
72
+ log.warning(f"Content {self.name} is deprecated: {self.deprecated}")
65
73
  return self
@@ -8,6 +8,7 @@ from datetime import timedelta
8
8
  from ipaddress import IPv4Address
9
9
  from pathlib import Path
10
10
  from typing import Any
11
+ from typing import final
11
12
  from uuid import UUID
12
13
 
13
14
  import yaml
@@ -38,6 +39,7 @@ def generic2str(dumper: SafeDumper, data: Any) -> Node:
38
39
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data))
39
40
 
40
41
 
42
+ # TODO: put in helper-file (similar to models/task/helper_paths.py) and make it callable
41
43
  yaml.add_representer(pathlib.PosixPath, path2str, SafeDumper)
42
44
  yaml.add_representer(pathlib.WindowsPath, path2str, SafeDumper)
43
45
  yaml.add_representer(pathlib.Path, path2str, SafeDumper)
@@ -108,12 +110,15 @@ class ShpModel(BaseModel):
108
110
  )
109
111
  return str(content)
110
112
 
111
- def __getitem__(self, key: str) -> Any:
113
+ def __getitem__(self, key: Any) -> Any:
112
114
  """Allow dict access like model["key"].
113
115
 
114
116
  in addition to model.key.
115
117
  """
116
- return self.__getattribute__(key)
118
+ if isinstance(key, str):
119
+ return self.__getattribute__(key)
120
+ msg = f"Unknown key '{key}' used when selecting from Model."
121
+ raise IndexError(msg)
117
122
 
118
123
  def __contains__(self, item: str) -> bool:
119
124
  """Allow checks like 'x in YClass'."""
@@ -127,8 +132,9 @@ class ShpModel(BaseModel):
127
132
  def items(self) -> Generator[tuple, None, None]:
128
133
  """Fn of dict."""
129
134
  for key in self.keys():
130
- yield key, self[key]
135
+ yield key, self.__getattribute__(key)
131
136
 
137
+ @final
132
138
  @classmethod
133
139
  def schema_to_file(cls, path: str | Path) -> None:
134
140
  """Store schema to yaml (for frontend-generators)."""
@@ -137,6 +143,7 @@ class ShpModel(BaseModel):
137
143
  with Path(path).resolve().with_suffix(".yaml").open("w") as f:
138
144
  f.write(model_yaml)
139
145
 
146
+ @final
140
147
  def to_file(
141
148
  self,
142
149
  path: str | Path,
@@ -178,6 +185,7 @@ class ShpModel(BaseModel):
178
185
  f.write(model_serial)
179
186
  return model_path
180
187
 
188
+ @final
181
189
  @classmethod
182
190
  def from_file(cls, path: str | Path) -> Self:
183
191
  """Load from YAML or pickle file."""
@@ -188,12 +196,13 @@ class ShpModel(BaseModel):
188
196
  with Path(path).open("rb") as shp_file:
189
197
  shp_dict = pickle.load(shp_file) # noqa: S301
190
198
  else:
191
- with Path(path).open() as shp_file:
199
+ with Path(path).open(encoding="utf-8-sig") as shp_file:
192
200
  shp_dict = yaml.safe_load(shp_file)
193
201
  shp_wrap = Wrapper(**shp_dict)
194
202
  if shp_wrap.datatype != cls.__name__:
195
203
  raise ValueError("Model in file does not match the actual Class")
196
204
  return cls(**shp_wrap.parameters)
197
205
 
206
+ @final
198
207
  def get_hash(self) -> str:
199
208
  return hashlib.sha3_224(str(self.model_dump()).encode("utf-8")).hexdigest()
@@ -2,6 +2,7 @@
2
2
 
3
3
  from datetime import datetime
4
4
  from typing import Annotated
5
+ from typing import final
5
6
 
6
7
  from pydantic import BaseModel
7
8
  from pydantic import StringConstraints
@@ -12,6 +13,7 @@ SafeStrClone = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
12
13
  # ⤷ copy avoids circular import
13
14
 
14
15
 
16
+ @final
15
17
  class Wrapper(BaseModel):
16
18
  """Generalized web- & file-interface for all models with dynamic typecasting."""
17
19