shepherd-core 2025.6.3__py3-none-any.whl → 2025.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. shepherd_core/data_models/base/cal_measurement.py +4 -5
  2. shepherd_core/data_models/base/calibration.py +8 -10
  3. shepherd_core/data_models/base/content.py +2 -3
  4. shepherd_core/data_models/base/shepherd.py +6 -8
  5. shepherd_core/data_models/base/wrapper.py +3 -4
  6. shepherd_core/data_models/content/energy_environment.py +4 -5
  7. shepherd_core/data_models/content/firmware.py +3 -5
  8. shepherd_core/data_models/content/virtual_harvester.py +5 -6
  9. shepherd_core/data_models/experiment/experiment.py +9 -17
  10. shepherd_core/data_models/experiment/observer_features.py +22 -38
  11. shepherd_core/data_models/experiment/target_config.py +10 -11
  12. shepherd_core/data_models/task/__init__.py +1 -3
  13. shepherd_core/data_models/task/emulation.py +18 -19
  14. shepherd_core/data_models/task/firmware_mod.py +3 -4
  15. shepherd_core/data_models/task/harvest.py +7 -10
  16. shepherd_core/data_models/task/observer_tasks.py +12 -10
  17. shepherd_core/data_models/task/programming.py +2 -2
  18. shepherd_core/data_models/task/testbed_tasks.py +8 -10
  19. shepherd_core/data_models/testbed/cape.py +3 -5
  20. shepherd_core/data_models/testbed/gpio.py +7 -8
  21. shepherd_core/data_models/testbed/mcu.py +1 -2
  22. shepherd_core/data_models/testbed/observer.py +5 -6
  23. shepherd_core/data_models/testbed/target.py +4 -6
  24. shepherd_core/data_models/testbed/testbed.py +2 -3
  25. shepherd_core/decoder_waveform/uart.py +12 -13
  26. shepherd_core/fw_tools/converter.py +1 -2
  27. shepherd_core/fw_tools/converter_elf.py +1 -2
  28. shepherd_core/fw_tools/patcher.py +65 -40
  29. shepherd_core/fw_tools/validation.py +7 -1
  30. shepherd_core/inventory/python.py +8 -9
  31. shepherd_core/inventory/system.py +1 -2
  32. shepherd_core/inventory/target.py +1 -2
  33. shepherd_core/logger.py +1 -2
  34. shepherd_core/reader.py +18 -23
  35. shepherd_core/testbed_client/client_abc_fix.py +2 -7
  36. shepherd_core/testbed_client/client_web.py +5 -9
  37. shepherd_core/testbed_client/fixtures.py +3 -5
  38. shepherd_core/testbed_client/user_model.py +4 -5
  39. shepherd_core/version.py +1 -1
  40. shepherd_core/vsource/virtual_converter_model.py +1 -2
  41. shepherd_core/vsource/virtual_harvester_simulation.py +1 -2
  42. shepherd_core/vsource/virtual_source_model.py +3 -5
  43. shepherd_core/vsource/virtual_source_simulation.py +2 -3
  44. shepherd_core/writer.py +12 -14
  45. {shepherd_core-2025.6.3.dist-info → shepherd_core-2025.8.1.dist-info}/METADATA +4 -8
  46. shepherd_core-2025.8.1.dist-info/RECORD +83 -0
  47. shepherd_core-2025.6.3.dist-info/RECORD +0 -83
  48. {shepherd_core-2025.6.3.dist-info → shepherd_core-2025.8.1.dist-info}/WHEEL +0 -0
  49. {shepherd_core-2025.6.3.dist-info → shepherd_core-2025.8.1.dist-info}/top_level.txt +0 -0
  50. {shepherd_core-2025.6.3.dist-info → shepherd_core-2025.8.1.dist-info}/zip-safe +0 -0
@@ -3,7 +3,6 @@
3
3
  import platform
4
4
  from contextlib import suppress
5
5
  from importlib import import_module
6
- from typing import Optional
7
6
 
8
7
  from pydantic import ConfigDict
9
8
  from typing_extensions import Self
@@ -15,14 +14,14 @@ class PythonInventory(ShpModel):
15
14
  """Python related inventory model."""
16
15
 
17
16
  # program versions
18
- h5py: Optional[str] = None
19
- numpy: Optional[str] = None
20
- pydantic: Optional[str] = None
21
- python: Optional[str] = None
22
- shepherd_core: Optional[str] = None
23
- shepherd_sheep: Optional[str] = None
24
- yaml: Optional[str] = None
25
- zstandard: Optional[str] = None
17
+ h5py: str | None = None
18
+ numpy: str | None = None
19
+ pydantic: str | None = None
20
+ python: str | None = None
21
+ shepherd_core: str | None = None
22
+ shepherd_sheep: str | None = None
23
+ yaml: str | None = None
24
+ zstandard: str | None = None
26
25
 
27
26
  model_config = ConfigDict(str_min_length=0)
28
27
 
@@ -9,7 +9,6 @@ from datetime import datetime
9
9
  from pathlib import Path
10
10
  from types import MappingProxyType
11
11
  from typing import Any
12
- from typing import Optional
13
12
 
14
13
  from typing_extensions import Self
15
14
 
@@ -41,7 +40,7 @@ class SystemInventory(ShpModel):
41
40
  machine: str
42
41
  processor: str
43
42
 
44
- ptp: Optional[str] = None
43
+ ptp: str | None = None
45
44
 
46
45
  hostname: str
47
46
 
@@ -1,7 +1,6 @@
1
1
  """Hardware related inventory model."""
2
2
 
3
3
  from collections.abc import Sequence
4
- from typing import Optional
5
4
 
6
5
  from pydantic import ConfigDict
7
6
  from typing_extensions import Self
@@ -12,7 +11,7 @@ from shepherd_core.data_models import ShpModel
12
11
  class TargetInventory(ShpModel):
13
12
  """Hardware related inventory model."""
14
13
 
15
- cape: Optional[str] = None
14
+ cape: str | None = None
16
15
  targets: Sequence[str] = ()
17
16
 
18
17
  model_config = ConfigDict(str_min_length=0)
shepherd_core/logger.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  import logging.handlers
5
- from typing import Union
6
5
 
7
6
  import chromalog
8
7
 
@@ -18,7 +17,7 @@ def get_verbose_level() -> int:
18
17
  return verbose_level
19
18
 
20
19
 
21
- def set_log_verbose_level(log_: Union[logging.Logger, logging.Handler], verbose: int) -> None:
20
+ def set_log_verbose_level(log_: logging.Logger | logging.Handler, verbose: int) -> None:
22
21
  """Set log level of shepherd."""
23
22
  if verbose == 0:
24
23
  log_.setLevel(logging.ERROR)
shepherd_core/reader.py CHANGED
@@ -12,10 +12,7 @@ from itertools import product
12
12
  from pathlib import Path
13
13
  from types import MappingProxyType
14
14
  from typing import TYPE_CHECKING
15
- from typing import Annotated
16
15
  from typing import Any
17
- from typing import Optional
18
- from typing import Union
19
16
 
20
17
  import h5py
21
18
  import numpy as np
@@ -91,8 +88,6 @@ class Reader:
91
88
  self.file_size: int = 0
92
89
  self.data_rate: float = 0
93
90
 
94
- self.buffers_n: Annotated[int, deprecated("use .chunk_n instead")] = 0
95
-
96
91
  # open file (if not already done by writer)
97
92
  self._reader_opened: bool = False
98
93
  if not hasattr(self, "h5file"):
@@ -161,9 +156,9 @@ class Reader:
161
156
 
162
157
  def __exit__(
163
158
  self,
164
- typ: Optional[type[BaseException]] = None,
165
- exc: Optional[BaseException] = None,
166
- tb: Optional[TracebackType] = None,
159
+ typ: type[BaseException] | None = None,
160
+ exc: BaseException | None = None,
161
+ tb: TracebackType | None = None,
167
162
  extra_arg: int = 0,
168
163
  ) -> None:
169
164
  if self._reader_opened:
@@ -193,7 +188,7 @@ class Reader:
193
188
  self.sample_interval_ns = round(10**9 * self.sample_interval_s)
194
189
  self.samplerate_sps = max(round((self.samples_n - 1) / duration_s), 1)
195
190
  self.runtime_s = round(self.samples_n / self.samplerate_sps, 1)
196
- self.chunks_n = self.buffers_n = int(self.samples_n // self.CHUNK_SAMPLES_N)
191
+ self.chunks_n = int(self.samples_n // self.CHUNK_SAMPLES_N)
197
192
  if isinstance(self.file_path, Path):
198
193
  self.file_size = self.file_path.stat().st_size
199
194
  else:
@@ -203,8 +198,8 @@ class Reader:
203
198
  def read(
204
199
  self,
205
200
  start_n: int = 0,
206
- end_n: Optional[int] = None,
207
- n_samples_per_chunk: Optional[int] = None,
201
+ end_n: int | None = None,
202
+ n_samples_per_chunk: int | None = None,
208
203
  *,
209
204
  is_raw: bool = False,
210
205
  omit_timestamps: bool = False,
@@ -251,8 +246,8 @@ class Reader:
251
246
  def read_buffers(
252
247
  self,
253
248
  start_n: int = 0,
254
- end_n: Optional[int] = None,
255
- n_samples_per_buffer: Optional[int] = None,
249
+ end_n: int | None = None,
250
+ n_samples_per_buffer: int | None = None,
256
251
  *,
257
252
  is_raw: bool = False,
258
253
  omit_ts: bool = False,
@@ -265,7 +260,7 @@ class Reader:
265
260
  omit_timestamps=omit_ts,
266
261
  )
267
262
 
268
- def get_time_start(self) -> Optional[datetime]:
263
+ def get_time_start(self) -> datetime | None:
269
264
  if self.samples_n < 1:
270
265
  return None
271
266
  return datetime.fromtimestamp(self._cal.time.raw_to_si(self.ds_time[0]), tz=local_tz())
@@ -297,7 +292,7 @@ class Reader:
297
292
  return self.h5file.attrs["hostname"]
298
293
  return "unknown"
299
294
 
300
- def get_datatype(self) -> Optional[EnergyDType]:
295
+ def get_datatype(self) -> EnergyDType | None:
301
296
  try:
302
297
  if "datatype" in self.h5file["data"].attrs:
303
298
  return EnergyDType[self.h5file["data"].attrs["datatype"]]
@@ -308,7 +303,7 @@ class Reader:
308
303
  else:
309
304
  return None
310
305
 
311
- def get_voltage_step(self) -> Optional[float]:
306
+ def get_voltage_step(self) -> float | None:
312
307
  """Informs about the voltage step (in volts) used during harvesting the ivcurve.
313
308
 
314
309
  Options for figuring out the real step:
@@ -316,7 +311,7 @@ class Reader:
316
311
  - analyze recorded data for most often used delta
317
312
  - calculate with 'steps_n * (1 + wait_cycles)' (done for calculating window_size)
318
313
  """
319
- voltage_step: Optional[float] = (
314
+ voltage_step: float | None = (
320
315
  self.get_config().get("virtual_harvester", {}).get("voltage_step_mV", None)
321
316
  )
322
317
  if voltage_step is None:
@@ -510,7 +505,7 @@ class Reader:
510
505
  return float(sum(energy_ws))
511
506
 
512
507
  def _dset_statistics(
513
- self, dset: h5py.Dataset, cal: Optional[CalibrationPair] = None
508
+ self, dset: h5py.Dataset, cal: CalibrationPair | None = None
514
509
  ) -> dict[str, float]:
515
510
  """Create basic stats for a provided dataset.
516
511
 
@@ -615,7 +610,7 @@ class Reader:
615
610
 
616
611
  def get_metadata(
617
612
  self,
618
- node: Union[h5py.Dataset, h5py.Group, None] = None,
613
+ node: h5py.Dataset | h5py.Group | None = None,
619
614
  *,
620
615
  minimal: bool = False,
621
616
  ) -> dict[str, dict]:
@@ -670,7 +665,7 @@ class Reader:
670
665
 
671
666
  return metadata
672
667
 
673
- def save_metadata(self, node: Union[h5py.Dataset, h5py.Group, None] = None) -> dict:
668
+ def save_metadata(self, node: h5py.Dataset | h5py.Group | None = None) -> dict:
674
669
  """Get structure of file and dump content to yaml-file with same name as original.
675
670
 
676
671
  :param node: starting node, leave free to go through whole file
@@ -688,7 +683,7 @@ class Reader:
688
683
  metadata = {}
689
684
  return metadata
690
685
 
691
- def get_gpio_pin_num(self, name: str) -> Optional[int]:
686
+ def get_gpio_pin_num(self, name: str) -> int | None:
692
687
  # reverse lookup in a 2D-dict: key1 are pin_num, key2 are descriptor-names
693
688
  if "gpio" not in self.h5file:
694
689
  return None
@@ -709,7 +704,7 @@ class Reader:
709
704
  data_1 = np.concatenate(([not data[0]], data[:-1]))
710
705
  return data != data_1
711
706
 
712
- def gpio_to_waveforms(self, name: Optional[str] = None) -> dict:
707
+ def gpio_to_waveforms(self, name: str | None = None) -> dict:
713
708
  waveforms: dict[str, np.ndarray] = {}
714
709
  if "gpio" not in self.h5file:
715
710
  return waveforms
@@ -744,7 +739,7 @@ class Reader:
744
739
  for row in pin_wf:
745
740
  csv.write(f"{row[0] / 1e9}{separator}{int(row[1])}\n")
746
741
 
747
- def gpio_to_uart(self) -> Optional[np.ndarray]:
742
+ def gpio_to_uart(self) -> np.ndarray | None:
748
743
  wfs = self.gpio_to_waveforms("uart")
749
744
  if len(wfs) < 1:
750
745
  return None
@@ -18,7 +18,6 @@ TODO: Comfort functions missing
18
18
  from abc import ABC
19
19
  from abc import abstractmethod
20
20
  from typing import Any
21
- from typing import Optional
22
21
 
23
22
  from shepherd_core.data_models.base.shepherd import ShpModel
24
23
  from shepherd_core.data_models.base.wrapper import Wrapper
@@ -49,9 +48,7 @@ class AbcClient(ABC):
49
48
  pass
50
49
 
51
50
  @abstractmethod
52
- def query_item(
53
- self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
54
- ) -> dict:
51
+ def query_item(self, model_type: str, uid: int | None = None, name: str | None = None) -> dict:
55
52
  pass
56
53
 
57
54
  @abstractmethod
@@ -103,9 +100,7 @@ class FixturesClient(AbcClient):
103
100
  def query_names(self, model_type: str) -> list[str]:
104
101
  return list(self._fixtures[model_type].elements_by_name.keys())
105
102
 
106
- def query_item(
107
- self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
108
- ) -> dict:
103
+ def query_item(self, model_type: str, uid: int | None = None, name: str | None = None) -> dict:
109
104
  if uid is not None:
110
105
  return self._fixtures[model_type].query_id(uid)
111
106
  if name is not None:
@@ -3,8 +3,6 @@
3
3
  from importlib import import_module
4
4
  from pathlib import Path
5
5
  from typing import Any
6
- from typing import Optional
7
- from typing import Union
8
6
 
9
7
  from pydantic import validate_call
10
8
 
@@ -28,7 +26,7 @@ class WebClient(AbcClient):
28
26
 
29
27
  testbed_server_default = "https://shepherd.cfaed.tu-dresden.de:8000/testbed"
30
28
 
31
- def __init__(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> None:
29
+ def __init__(self, server: str | None = None, token: str | Path | None = None) -> None:
32
30
  """Connect to Testbed-Server with optional token and server-address.
33
31
 
34
32
  server: optional address to shepherd-server-endpoint
@@ -39,8 +37,8 @@ class WebClient(AbcClient):
39
37
  # add default values
40
38
  self._token: str = "basic_public_access" # noqa: S105
41
39
  self._server: str = config.TESTBED_SERVER
42
- self._user: Optional[User] = None
43
- self._key: Optional[str] = None
40
+ self._user: User | None = None
41
+ self._key: str | None = None
44
42
  self._connected: bool = False
45
43
  self._req = None
46
44
 
@@ -66,9 +64,7 @@ class WebClient(AbcClient):
66
64
  def query_names(self, model_type: str) -> list[str]:
67
65
  raise NotImplementedError("TODO")
68
66
 
69
- def query_item(
70
- self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
71
- ) -> dict:
67
+ def query_item(self, model_type: str, uid: int | None = None, name: str | None = None) -> dict:
72
68
  raise NotImplementedError("TODO")
73
69
 
74
70
  def try_inheritance(
@@ -88,7 +84,7 @@ class WebClient(AbcClient):
88
84
  # Below are extra FNs not in ABC
89
85
 
90
86
  @validate_call
91
- def _connect(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> bool:
87
+ def _connect(self, server: str | None = None, token: str | Path | None = None) -> bool:
92
88
  """Establish connection to testbed-server.
93
89
 
94
90
  TODO: totally not finished
@@ -8,8 +8,6 @@ from datetime import datetime
8
8
  from datetime import timedelta
9
9
  from pathlib import Path
10
10
  from typing import Any
11
- from typing import Optional
12
- from typing import Union
13
11
 
14
12
  import yaml
15
13
  from pydantic import validate_call
@@ -57,7 +55,7 @@ class Fixture:
57
55
  # update iterator
58
56
  self._iter_list: list[dict[str, Any]] = list(self.elements_by_name.values())
59
57
 
60
- def __getitem__(self, key: Union[str, int]) -> dict[str, Any]:
58
+ def __getitem__(self, key: str | int) -> dict[str, Any]:
61
59
  original_key = key
62
60
  if isinstance(key, str):
63
61
  key = key.lower()
@@ -89,7 +87,7 @@ class Fixture:
89
87
  return {_i["id"]: _i["name"] for _i in self.elements_by_id.values()}
90
88
 
91
89
  def inheritance(
92
- self, values: dict[str, Any], chain: Optional[list[str]] = None
90
+ self, values: dict[str, Any], chain: list[str] | None = None
93
91
  ) -> tuple[dict[str, Any], list[str]]:
94
92
  if chain is None:
95
93
  chain = []
@@ -177,7 +175,7 @@ class Fixtures:
177
175
  suffix = ".yaml"
178
176
 
179
177
  @validate_call
180
- def __init__(self, file_path: Optional[Path] = None, *, reset: bool = False) -> None:
178
+ def __init__(self, file_path: Path | None = None, *, reset: bool = False) -> None:
181
179
  if file_path is None:
182
180
  self.file_path = Path(__file__).parent.parent.resolve() / "data_models"
183
181
  else:
@@ -4,7 +4,6 @@ import secrets
4
4
  from hashlib import pbkdf2_hmac
5
5
  from typing import Annotated
6
6
  from typing import Any
7
- from typing import Optional
8
7
 
9
8
  from pydantic import EmailStr
10
9
  from pydantic import Field
@@ -44,14 +43,14 @@ class User(ShpModel):
44
43
  default_factory=id_default,
45
44
  )
46
45
  name: NameStr
47
- description: Optional[SafeStr] = None
48
- comment: Optional[SafeStr] = None
46
+ description: SafeStr | None = None
47
+ comment: SafeStr | None = None
49
48
 
50
- name_full: Optional[NameStr] = None
49
+ name_full: NameStr | None = None
51
50
  group: NameStr
52
51
  email: EmailStr
53
52
 
54
- pw_hash: Optional[SecretBytes] = None
53
+ pw_hash: SecretBytes | None = None
55
54
  # ⤷ was hash_password("this_will_become_a_salted_slow_hash") -> slowed BBB down
56
55
  # ⤷ TODO (min_length=128, max_length=512)
57
56
 
shepherd_core/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Separated string avoids circular imports."""
2
2
 
3
- version: str = "2025.06.3"
3
+ version: str = "2025.08.1"
@@ -15,7 +15,6 @@ Compromises:
15
15
  """
16
16
 
17
17
  import math
18
- from typing import Optional
19
18
 
20
19
  from shepherd_core.data_models import CalibrationEmulator
21
20
  from shepherd_core.data_models.content.virtual_source import LUT_SIZE
@@ -32,7 +31,7 @@ class PruCalibration:
32
31
  RESIDUE_MAX_nA: int = NOISE_ESTIMATE_nA * RESIDUE_SIZE_FACTOR
33
32
  negative_residue_nA = 0
34
33
 
35
- def __init__(self, cal_emu: Optional[CalibrationEmulator] = None) -> None:
34
+ def __init__(self, cal_emu: CalibrationEmulator | None = None) -> None:
36
35
  self.cal = cal_emu if cal_emu else CalibrationEmulator()
37
36
 
38
37
  def conv_adc_raw_to_nA(self, current_raw: int) -> float:
@@ -9,7 +9,6 @@ The output file can be analyzed and plotted with shepherds tool suite.
9
9
 
10
10
  from contextlib import ExitStack
11
11
  from pathlib import Path
12
- from typing import Optional
13
12
 
14
13
  from tqdm import tqdm
15
14
 
@@ -23,7 +22,7 @@ from .virtual_harvester_model import VirtualHarvesterModel
23
22
 
24
23
 
25
24
  def simulate_harvester(
26
- config: VirtualHarvesterConfig, path_input: Path, path_output: Optional[Path] = None
25
+ config: VirtualHarvesterConfig, path_input: Path, path_output: Path | None = None
27
26
  ) -> float:
28
27
  """Simulate behavior of virtual harvester algorithms.
29
28
 
@@ -10,8 +10,6 @@ NOTE: DO NOT OPTIMIZE -> stay close to original code-base
10
10
 
11
11
  """
12
12
 
13
- from typing import Optional
14
-
15
13
  from shepherd_core.data_models.base.calibration import CalibrationEmulator
16
14
  from shepherd_core.data_models.content.energy_environment import EnergyDType
17
15
  from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
@@ -28,11 +26,11 @@ class VirtualSourceModel:
28
26
 
29
27
  def __init__(
30
28
  self,
31
- vsrc: Optional[VirtualSourceConfig],
29
+ vsrc: VirtualSourceConfig | None,
32
30
  cal_emu: CalibrationEmulator,
33
31
  dtype_in: EnergyDType = EnergyDType.ivsample,
34
- window_size: Optional[int] = None,
35
- voltage_step_V: Optional[float] = None,
32
+ window_size: int | None = None,
33
+ voltage_step_V: float | None = None,
36
34
  *,
37
35
  log_intermediate: bool = False,
38
36
  ) -> None:
@@ -9,7 +9,6 @@ The output file can be analyzed and plotted with shepherds tool suite.
9
9
 
10
10
  from contextlib import ExitStack
11
11
  from pathlib import Path
12
- from typing import Optional
13
12
 
14
13
  import numpy as np
15
14
  from tqdm import tqdm
@@ -28,7 +27,7 @@ def simulate_source(
28
27
  config: VirtualSourceConfig,
29
28
  target: TargetABC,
30
29
  path_input: Path,
31
- path_output: Optional[Path] = None,
30
+ path_output: Path | None = None,
32
31
  *,
33
32
  monitor_internals: bool = False,
34
33
  ) -> float:
@@ -66,7 +65,7 @@ def simulate_source(
66
65
  stats_internal = np.empty((round(file_inp.runtime_s * file_inp.samplerate_sps), 11))
67
66
  try:
68
67
  # keep dependencies low
69
- from matplotlib import pyplot as plt
68
+ from matplotlib import pyplot as plt # noqa: PLC0415
70
69
  except ImportError:
71
70
  log.warning("Matplotlib not installed, plotting of internals disabled")
72
71
  stats_internal = None
shepherd_core/writer.py CHANGED
@@ -9,8 +9,6 @@ from itertools import product
9
9
  from pathlib import Path
10
10
  from types import TracebackType
11
11
  from typing import Any
12
- from typing import Optional
13
- from typing import Union
14
12
 
15
13
  import h5py
16
14
  import numpy as np
@@ -32,7 +30,7 @@ from .reader import Reader
32
30
 
33
31
  # copy of core/models/base/shepherd - needed also here
34
32
  def path2str(
35
- dumper: SafeDumper, data: Union[pathlib.Path, pathlib.WindowsPath, pathlib.PosixPath]
33
+ dumper: SafeDumper, data: pathlib.Path | pathlib.WindowsPath | pathlib.PosixPath
36
34
  ) -> Node:
37
35
  """Add a yaml-representation for a specific datatype."""
38
36
  return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
@@ -49,7 +47,7 @@ yaml.add_representer(pathlib.Path, path2str, SafeDumper)
49
47
  yaml.add_representer(timedelta, time2int, SafeDumper)
50
48
 
51
49
 
52
- def unique_path(base_path: Union[str, Path], suffix: str) -> Path:
50
+ def unique_path(base_path: str | Path, suffix: str) -> Path:
53
51
  """Find an unused filename in case it already exists.
54
52
 
55
53
  :param base_path: file-path to test
@@ -100,11 +98,11 @@ class Writer(Reader):
100
98
  def __init__(
101
99
  self,
102
100
  file_path: Path,
103
- mode: Optional[str] = None,
104
- datatype: Union[str, EnergyDType, None] = None,
105
- window_samples: Optional[int] = None,
106
- cal_data: Union[CalSeries, CalEmu, CalHrv, None] = None,
107
- compression: Optional[Compression] = Compression.default,
101
+ mode: str | None = None,
102
+ datatype: str | EnergyDType | None = None,
103
+ window_samples: int | None = None,
104
+ cal_data: CalSeries | CalEmu | CalHrv | None = None,
105
+ compression: Compression | None = Compression.default,
108
106
  *,
109
107
  modify_existing: bool = False,
110
108
  force_overwrite: bool = False,
@@ -210,9 +208,9 @@ class Writer(Reader):
210
208
 
211
209
  def __exit__(
212
210
  self,
213
- typ: Optional[type[BaseException]] = None,
214
- exc: Optional[BaseException] = None,
215
- tb: Optional[TracebackType] = None,
211
+ typ: type[BaseException] | None = None,
212
+ exc: BaseException | None = None,
213
+ tb: TracebackType | None = None,
216
214
  extra_arg: int = 0,
217
215
  ) -> None:
218
216
  self._align()
@@ -279,7 +277,7 @@ class Writer(Reader):
279
277
 
280
278
  def append_iv_data_raw(
281
279
  self,
282
- timestamp: Union[np.ndarray, float, int], # noqa: PYI041
280
+ timestamp: np.ndarray | float | int, # noqa: PYI041
283
281
  voltage: np.ndarray,
284
282
  current: np.ndarray,
285
283
  ) -> None:
@@ -318,7 +316,7 @@ class Writer(Reader):
318
316
 
319
317
  def append_iv_data_si(
320
318
  self,
321
- timestamp: Union[np.ndarray, float],
319
+ timestamp: np.ndarray | float,
322
320
  voltage: np.ndarray,
323
321
  current: np.ndarray,
324
322
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shepherd_core
3
- Version: 2025.6.3
3
+ Version: 2025.8.1
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>
@@ -18,7 +18,6 @@ Classifier: Development Status :: 5 - Production/Stable
18
18
  Classifier: Intended Audience :: Developers
19
19
  Classifier: Intended Audience :: Information Technology
20
20
  Classifier: Intended Audience :: Science/Research
21
- Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
@@ -26,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.13
26
25
  Classifier: License :: OSI Approved :: MIT License
27
26
  Classifier: Operating System :: OS Independent
28
27
  Classifier: Natural Language :: English
29
- Requires-Python: >=3.9
28
+ Requires-Python: >=3.10
30
29
  Description-Content-Type: text/markdown
31
30
  Requires-Dist: h5py
32
31
  Requires-Dist: numpy
@@ -44,15 +43,12 @@ Requires-Dist: pwntools-elf-only; extra == "elf"
44
43
  Provides-Extra: inventory
45
44
  Requires-Dist: psutil; extra == "inventory"
46
45
  Provides-Extra: dev
47
- Requires-Dist: twine; extra == "dev"
48
- Requires-Dist: pre-commit; extra == "dev"
49
- Requires-Dist: pyright; extra == "dev"
50
- Requires-Dist: ruff; extra == "dev"
51
- Requires-Dist: mypy; extra == "dev"
52
46
  Requires-Dist: types-PyYAML; extra == "dev"
53
47
  Provides-Extra: test
54
48
  Requires-Dist: pytest; extra == "test"
55
49
  Requires-Dist: coverage; extra == "test"
50
+ Provides-Extra: all
51
+ Requires-Dist: shepherd-core[dev,elf,inventory,test]; extra == "all"
56
52
 
57
53
  # Core Library
58
54