shepherd-core 2025.4.1__py3-none-any.whl → 2025.5.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 (61) hide show
  1. shepherd_core/calibration_hw_def.py +11 -11
  2. shepherd_core/commons.py +4 -4
  3. shepherd_core/data_models/__init__.py +2 -0
  4. shepherd_core/data_models/base/cal_measurement.py +10 -11
  5. shepherd_core/data_models/base/calibration.py +7 -6
  6. shepherd_core/data_models/base/content.py +1 -1
  7. shepherd_core/data_models/base/shepherd.py +6 -7
  8. shepherd_core/data_models/base/wrapper.py +2 -2
  9. shepherd_core/data_models/content/_external_fixtures.yaml +32 -32
  10. shepherd_core/data_models/content/energy_environment.py +6 -5
  11. shepherd_core/data_models/content/firmware.py +9 -7
  12. shepherd_core/data_models/content/virtual_harvester.py +34 -26
  13. shepherd_core/data_models/content/virtual_harvester_fixture.yaml +2 -2
  14. shepherd_core/data_models/content/virtual_source.py +20 -17
  15. shepherd_core/data_models/content/virtual_source_fixture.yaml +3 -3
  16. shepherd_core/data_models/experiment/experiment.py +15 -15
  17. shepherd_core/data_models/experiment/observer_features.py +109 -16
  18. shepherd_core/data_models/experiment/target_config.py +17 -12
  19. shepherd_core/data_models/task/__init__.py +11 -8
  20. shepherd_core/data_models/task/emulation.py +32 -17
  21. shepherd_core/data_models/task/firmware_mod.py +11 -11
  22. shepherd_core/data_models/task/harvest.py +7 -6
  23. shepherd_core/data_models/task/observer_tasks.py +7 -7
  24. shepherd_core/data_models/task/programming.py +13 -12
  25. shepherd_core/data_models/task/testbed_tasks.py +8 -8
  26. shepherd_core/data_models/testbed/cape.py +7 -6
  27. shepherd_core/data_models/testbed/gpio.py +8 -7
  28. shepherd_core/data_models/testbed/mcu.py +8 -7
  29. shepherd_core/data_models/testbed/mcu_fixture.yaml +4 -4
  30. shepherd_core/data_models/testbed/observer.py +9 -7
  31. shepherd_core/data_models/testbed/target.py +9 -7
  32. shepherd_core/data_models/testbed/testbed.py +11 -10
  33. shepherd_core/data_models/virtual_source_doc.txt +3 -3
  34. shepherd_core/decoder_waveform/uart.py +5 -5
  35. shepherd_core/fw_tools/converter.py +10 -6
  36. shepherd_core/fw_tools/patcher.py +14 -15
  37. shepherd_core/fw_tools/validation.py +11 -6
  38. shepherd_core/inventory/__init__.py +6 -6
  39. shepherd_core/inventory/python.py +1 -1
  40. shepherd_core/inventory/system.py +11 -8
  41. shepherd_core/inventory/target.py +3 -3
  42. shepherd_core/logger.py +2 -2
  43. shepherd_core/reader.py +105 -78
  44. shepherd_core/testbed_client/client_abc_fix.py +22 -16
  45. shepherd_core/testbed_client/client_web.py +18 -11
  46. shepherd_core/testbed_client/fixtures.py +21 -22
  47. shepherd_core/testbed_client/user_model.py +6 -5
  48. shepherd_core/version.py +1 -1
  49. shepherd_core/vsource/target_model.py +3 -3
  50. shepherd_core/vsource/virtual_converter_model.py +3 -3
  51. shepherd_core/vsource/virtual_harvester_model.py +7 -9
  52. shepherd_core/vsource/virtual_harvester_simulation.py +7 -6
  53. shepherd_core/vsource/virtual_source_model.py +6 -5
  54. shepherd_core/vsource/virtual_source_simulation.py +8 -7
  55. shepherd_core/writer.py +37 -39
  56. {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/METADATA +2 -3
  57. shepherd_core-2025.5.2.dist-info/RECORD +81 -0
  58. {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/WHEEL +1 -1
  59. shepherd_core-2025.4.1.dist-info/RECORD +0 -81
  60. {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/top_level.txt +0 -0
  61. {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/zip-safe +0 -0
@@ -9,7 +9,8 @@ from typing import Union
9
9
  import zstandard as zstd
10
10
  from pydantic import validate_call
11
11
 
12
- from ..data_models.content.firmware_datatype import FirmwareDType
12
+ from shepherd_core.data_models.content.firmware_datatype import FirmwareDType
13
+
13
14
  from .converter_elf import elf_to_hex
14
15
  from .validation import is_elf
15
16
  from .validation import is_hex
@@ -27,7 +28,8 @@ def firmware_to_hex(file_path: Path) -> Path:
27
28
  return elf_to_hex(file_path)
28
29
  if is_hex(file_path):
29
30
  return file_path
30
- raise FileNotFoundError("FW2Hex: unknown file '%s', it should be ELF or HEX", file_path.name)
31
+ msg = (f"FW2Hex: unknown file '{file_path.name}', it should be ELF or HEX",)
32
+ raise FileNotFoundError(msg)
31
33
 
32
34
 
33
35
  @validate_call
@@ -85,10 +87,10 @@ def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path
85
87
  - base64-string will be transformed to file
86
88
  - if data is a path the file will be copied to the destination.
87
89
  """
88
- if data_type == FirmwareDType.base64_elf:
90
+ if data_type == FirmwareDType.base64_elf and isinstance(data, str):
89
91
  file = file_path.with_suffix(".elf")
90
92
  base64_to_file(data, file)
91
- elif data_type == FirmwareDType.base64_hex:
93
+ elif data_type == FirmwareDType.base64_hex and isinstance(data, str):
92
94
  file = file_path.with_suffix(".hex")
93
95
  base64_to_file(data, file)
94
96
  elif isinstance(data, (Path, str)):
@@ -97,10 +99,12 @@ def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path
97
99
  elif data_type == FirmwareDType.path_hex:
98
100
  file = file_path.with_suffix(".hex")
99
101
  else:
100
- raise ValueError("FW-Extraction failed due to unknown datatype '%s'", data_type)
102
+ msg = "FW-Extraction failed due to unknown datatype '{data_type}'"
103
+ raise ValueError(msg)
101
104
  if not file.parent.exists():
102
105
  file.parent.mkdir(parents=True)
103
106
  shutil.copy(data, file)
104
107
  else:
105
- raise ValueError("FW-Extraction failed due to unknown data-type '%s'", type(data))
108
+ msg = f"FW-Extraction failed due to unknown data-type '{type(data)}'"
109
+ raise ValueError(msg)
106
110
  return file
@@ -1,15 +1,16 @@
1
1
  """Read and modify symbols in ELF-files."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Annotated
4
5
  from typing import Optional
5
6
 
6
7
  from pydantic import Field
7
8
  from pydantic import validate_call
8
- from typing_extensions import Annotated
9
9
 
10
- from ..commons import uid_len_default
11
- from ..commons import uid_str_default
12
- from ..logger import logger
10
+ from shepherd_core.commons import UID_NAME
11
+ from shepherd_core.commons import UID_SIZE
12
+ from shepherd_core.logger import logger
13
+
13
14
  from .validation import is_elf
14
15
 
15
16
  try:
@@ -49,7 +50,7 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
49
50
 
50
51
 
51
52
  @validate_call
52
- def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> Optional[int]:
53
+ def read_symbol(file_elf: Path, symbol: str, length: int = UID_SIZE) -> Optional[int]:
53
54
  """Read value of symbol in ELF-File.
54
55
 
55
56
  Will be interpreted as int.
@@ -67,7 +68,7 @@ def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> O
67
68
 
68
69
  def read_uid(file_elf: Path) -> Optional[int]:
69
70
  """Read value of UID-symbol for shepherd testbed."""
70
- return read_symbol(file_elf, symbol=uid_str_default, length=uid_len_default)
71
+ return read_symbol(file_elf, symbol=UID_NAME, length=UID_SIZE)
71
72
 
72
73
 
73
74
  def read_arch(file_elf: Path) -> Optional[str]:
@@ -87,7 +88,7 @@ def read_arch(file_elf: Path) -> Optional[str]:
87
88
  def modify_symbol_value(
88
89
  file_elf: Path,
89
90
  symbol: str,
90
- value: Annotated[int, Field(ge=0, lt=2 ** (8 * uid_len_default))],
91
+ value: Annotated[int, Field(ge=0, lt=2 ** (8 * UID_SIZE))],
91
92
  *,
92
93
  overwrite: bool = False,
93
94
  ) -> Optional[Path]:
@@ -105,20 +106,18 @@ def modify_symbol_value(
105
106
  raise RuntimeError(elf_error_text)
106
107
  elf = ELF(path=file_elf)
107
108
  addr = elf.symbols[symbol]
108
- value_raw = elf.read(address=addr, count=uid_len_default)[-uid_len_default:]
109
+ value_raw = elf.read(address=addr, count=UID_SIZE)[-UID_SIZE:]
109
110
  # ⤷ cutting needed -> msp produces 4b instead of 2
110
111
  value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
111
- value_raw = value.to_bytes(length=uid_len_default, byteorder=elf.endian, signed=False)
112
+ value_raw = value.to_bytes(length=UID_SIZE, byteorder=elf.endian, signed=False)
113
+
112
114
  try:
113
115
  elf.write(address=addr, data=value_raw)
114
116
  except AttributeError:
115
117
  logger.warning("ELF-Modifier failed @%s for symbol '%s'", f"0x{addr:X}", symbol)
116
118
  return None
117
- if overwrite:
118
- file_new = file_elf
119
- else:
120
- file_new = file_elf.with_name(file_elf.stem + "_" + str(value) + file_elf.suffix)
121
- # could be simplified, but py3.8-- doesn't know .with_stem()
119
+
120
+ file_new = file_elf if overwrite else file_elf.with_stem(file_elf.stem + "_" + str(value))
122
121
  elf.save(path=file_new)
123
122
  elf.close()
124
123
  logger.debug(
@@ -133,4 +132,4 @@ def modify_symbol_value(
133
132
 
134
133
  def modify_uid(file_elf: Path, value: int) -> Optional[Path]:
135
134
  """Replace value of UID-symbol for shepherd testbed."""
136
- return modify_symbol_value(file_elf, symbol=uid_str_default, value=value, overwrite=True)
135
+ return modify_symbol_value(file_elf, symbol=UID_NAME, value=value, overwrite=True)
@@ -12,8 +12,9 @@ from intelhex import IntelHex
12
12
  from intelhex import IntelHexError
13
13
  from pydantic import validate_call
14
14
 
15
- from ..data_models.content.firmware_datatype import FirmwareDType
16
- from ..logger import logger
15
+ from shepherd_core.data_models.content.firmware_datatype import FirmwareDType
16
+ from shepherd_core.logger import logger
17
+
17
18
  from .converter_elf import elf_to_hex
18
19
 
19
20
  try:
@@ -136,7 +137,8 @@ def determine_type(file: Path) -> FirmwareDType:
136
137
  return FirmwareDType.path_hex
137
138
  if is_elf(file):
138
139
  return FirmwareDType.path_elf
139
- raise ValueError("Type of file '%s' could not be determined", file.name)
140
+ msg = f"Type of file '{file.name}' could not be determined"
141
+ raise ValueError(msg)
140
142
 
141
143
 
142
144
  def determine_arch(file: Path) -> str:
@@ -147,11 +149,14 @@ def determine_arch(file: Path) -> str:
147
149
  return "msp430"
148
150
  if is_elf_nrf52(file):
149
151
  return "nrf52"
150
- raise ValueError("Arch of ELF '%s' could not be determined", file.name)
152
+ msg = f"Arch of ELF '{file.name}' could not be determined"
153
+ raise ValueError(msg)
151
154
  if file_t == FirmwareDType.path_hex:
152
155
  if is_hex_msp430(file):
153
156
  return "msp430"
154
157
  if is_hex_nrf52(file):
155
158
  return "nrf52"
156
- raise ValueError("Arch of HEX '%s' could not be determined", file.name)
157
- raise ValueError("Arch of file '%s' could not be determined", file.name)
159
+ msg = f"Arch of HEX '{file.name}' could not be determined"
160
+ raise ValueError(msg)
161
+ msg = f"Arch of file '{file.name}' could not be determined"
162
+ raise ValueError(msg)
@@ -9,13 +9,13 @@ This will collect:
9
9
  from datetime import datetime
10
10
  from datetime import timedelta
11
11
  from pathlib import Path
12
- from typing import List
12
+ from typing import Annotated
13
13
 
14
14
  from pydantic import Field
15
- from typing_extensions import Annotated
16
15
  from typing_extensions import Self
17
16
 
18
- from ..data_models import ShpModel
17
+ from shepherd_core.data_models import ShpModel
18
+
19
19
  from .python import PythonInventory
20
20
  from .system import SystemInventory
21
21
  from .target import TargetInventory
@@ -55,7 +55,7 @@ class Inventory(PythonInventory, SystemInventory, TargetInventory):
55
55
  class InventoryList(ShpModel):
56
56
  """Collection of inventories for several devices."""
57
57
 
58
- elements: Annotated[List[Inventory], Field(min_length=1)]
58
+ elements: Annotated[list[Inventory], Field(min_length=1)]
59
59
 
60
60
  def to_csv(self, path: Path) -> None:
61
61
  """Generate a CSV.
@@ -82,8 +82,8 @@ class InventoryList(ShpModel):
82
82
  if (_e.created.timestamp() - ts_earl) > 10:
83
83
  warnings["time_delta"] = f"[{self.hostname}] time-sync has failed"
84
84
 
85
- # turn Dict[hostname][type] = val
86
- # to Dict[type][val] = List[hostnames]
85
+ # turn dict[hostname][type] = val
86
+ # to dict[type][val] = list[hostnames]
87
87
  _inp = {
88
88
  _e.hostname: _e.model_dump(exclude_unset=True, exclude_defaults=True)
89
89
  for _e in self.elements
@@ -8,7 +8,7 @@ from typing import Optional
8
8
  from pydantic import ConfigDict
9
9
  from typing_extensions import Self
10
10
 
11
- from ..data_models import ShpModel
11
+ from shepherd_core.data_models import ShpModel
12
12
 
13
13
 
14
14
  class PythonInventory(ShpModel):
@@ -3,15 +3,18 @@
3
3
  import platform
4
4
  import subprocess
5
5
  import time
6
+ from collections.abc import Mapping
7
+ from collections.abc import Sequence
6
8
  from datetime import datetime
7
9
  from pathlib import Path
8
- from typing import List
10
+ from types import MappingProxyType
11
+ from typing import Any
9
12
  from typing import Optional
10
13
 
11
14
  from typing_extensions import Self
12
15
 
13
- from ..data_models.base.timezone import local_now
14
- from ..logger import logger
16
+ from shepherd_core.data_models.base.timezone import local_now
17
+ from shepherd_core.logger import logger
15
18
 
16
19
  try:
17
20
  import psutil
@@ -21,7 +24,7 @@ except ImportError:
21
24
  from pydantic import ConfigDict
22
25
  from pydantic.types import PositiveInt
23
26
 
24
- from ..data_models import ShpModel
27
+ from shepherd_core.data_models import ShpModel
25
28
 
26
29
 
27
30
  class SystemInventory(ShpModel):
@@ -30,7 +33,7 @@ class SystemInventory(ShpModel):
30
33
  uptime: PositiveInt
31
34
  # ⤷ seconds
32
35
  timestamp: datetime
33
- # time_delta: timedelta = timedelta(0) # noqa: ERA001
36
+ # time_delta: timedelta = timedelta(seconds=0) # noqa: ERA001
34
37
  # ⤷ lag behind earliest observer, TODO: wrong place
35
38
 
36
39
  system: str
@@ -44,13 +47,13 @@ class SystemInventory(ShpModel):
44
47
 
45
48
  hostname: str
46
49
 
47
- interfaces: dict = {} # noqa: RUF012
50
+ interfaces: Mapping[str, Any] = MappingProxyType({})
48
51
  # ⤷ tuple with
49
52
  # ip IPvAnyAddress
50
53
  # mac MACStr
51
54
 
52
- fs_root: List[str] = None
53
- beagle: List[str] = None
55
+ fs_root: Sequence[str] = ()
56
+ beagle: Sequence[str] = ()
54
57
 
55
58
  model_config = ConfigDict(str_min_length=0)
56
59
 
@@ -1,19 +1,19 @@
1
1
  """Hardware related inventory model."""
2
2
 
3
- from typing import List
3
+ from collections.abc import Sequence
4
4
  from typing import Optional
5
5
 
6
6
  from pydantic import ConfigDict
7
7
  from typing_extensions import Self
8
8
 
9
- from ..data_models import ShpModel
9
+ from shepherd_core.data_models import ShpModel
10
10
 
11
11
 
12
12
  class TargetInventory(ShpModel):
13
13
  """Hardware related inventory model."""
14
14
 
15
15
  cape: Optional[str] = None
16
- targets: List[str] = [] # noqa: RUF012
16
+ targets: Sequence[str] = ()
17
17
 
18
18
  model_config = ConfigDict(str_min_length=0)
19
19
 
shepherd_core/logger.py CHANGED
@@ -33,8 +33,8 @@ def set_log_verbose_level(log_: Union[logging.Logger, logging.Handler], verbose:
33
33
  if verbose < 3:
34
34
  # reduce log-overhead when not debugging, also more user-friendly exceptions
35
35
  logging._srcfile = None # noqa: SLF001
36
- logging.logThreads = 0
37
- logging.logProcesses = 0
36
+ logging.logThreads = False
37
+ logging.logProcesses = False
38
38
 
39
39
  if verbose > 2:
40
40
  chromalog.basicConfig(format="%(name)s %(levelname)s: %(message)s")