shepherd-core 2023.8.6__py3-none-any.whl → 2023.8.8__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 (49) hide show
  1. shepherd_core/__init__.py +1 -1
  2. shepherd_core/data_models/__init__.py +3 -1
  3. shepherd_core/data_models/base/cal_measurement.py +17 -14
  4. shepherd_core/data_models/base/calibration.py +41 -8
  5. shepherd_core/data_models/base/content.py +17 -13
  6. shepherd_core/data_models/base/shepherd.py +29 -22
  7. shepherd_core/data_models/base/wrapper.py +5 -4
  8. shepherd_core/data_models/content/energy_environment.py +3 -2
  9. shepherd_core/data_models/content/firmware.py +10 -6
  10. shepherd_core/data_models/content/virtual_harvester.py +42 -39
  11. shepherd_core/data_models/content/virtual_source.py +83 -72
  12. shepherd_core/data_models/doc_virtual_source.py +7 -14
  13. shepherd_core/data_models/experiment/experiment.py +20 -15
  14. shepherd_core/data_models/experiment/observer_features.py +33 -31
  15. shepherd_core/data_models/experiment/target_config.py +24 -18
  16. shepherd_core/data_models/task/__init__.py +13 -5
  17. shepherd_core/data_models/task/emulation.py +35 -23
  18. shepherd_core/data_models/task/firmware_mod.py +14 -13
  19. shepherd_core/data_models/task/harvest.py +28 -13
  20. shepherd_core/data_models/task/observer_tasks.py +17 -7
  21. shepherd_core/data_models/task/programming.py +13 -13
  22. shepherd_core/data_models/task/testbed_tasks.py +16 -6
  23. shepherd_core/data_models/testbed/cape.py +3 -2
  24. shepherd_core/data_models/testbed/gpio.py +18 -15
  25. shepherd_core/data_models/testbed/mcu.py +7 -6
  26. shepherd_core/data_models/testbed/observer.py +23 -19
  27. shepherd_core/data_models/testbed/target.py +15 -14
  28. shepherd_core/data_models/testbed/testbed.py +14 -11
  29. shepherd_core/fw_tools/converter.py +7 -7
  30. shepherd_core/fw_tools/converter_elf.py +2 -2
  31. shepherd_core/fw_tools/patcher.py +7 -6
  32. shepherd_core/fw_tools/validation.py +3 -3
  33. shepherd_core/inventory/__init__.py +16 -8
  34. shepherd_core/inventory/python.py +4 -3
  35. shepherd_core/inventory/system.py +5 -5
  36. shepherd_core/inventory/target.py +4 -4
  37. shepherd_core/reader.py +3 -3
  38. shepherd_core/testbed_client/client.py +6 -4
  39. shepherd_core/testbed_client/user_model.py +14 -10
  40. shepherd_core/writer.py +2 -2
  41. {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/METADATA +9 -2
  42. {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/RECORD +49 -49
  43. {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/WHEEL +1 -1
  44. tests/data_models/example_cal_data.yaml +2 -1
  45. tests/data_models/example_cal_meas.yaml +2 -1
  46. tests/data_models/test_base_models.py +19 -2
  47. tests/inventory/test_inventory.py +1 -1
  48. {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/top_level.txt +0 -0
  49. {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/zip-safe +0 -0
@@ -1,10 +1,12 @@
1
1
  from datetime import timedelta
2
2
  from pathlib import Path
3
+ from typing import List
3
4
  from typing import Optional
4
5
 
6
+ from pydantic import Field
5
7
  from pydantic import HttpUrl
6
- from pydantic import conlist
7
- from pydantic import root_validator
8
+ from pydantic import model_validator
9
+ from typing_extensions import Annotated
8
10
 
9
11
  from ...testbed_client import tb_client
10
12
  from ..base.content import IdInt
@@ -22,9 +24,9 @@ class Testbed(ShpModel):
22
24
  description: SafeStr
23
25
  comment: Optional[SafeStr] = None
24
26
 
25
- url: Optional[HttpUrl]
27
+ url: Optional[HttpUrl] = None
26
28
 
27
- observers: conlist(item_type=Observer, min_items=1, max_items=64)
29
+ observers: Annotated[List[Observer], Field(min_length=1, max_length=64)]
28
30
 
29
31
  shared_storage: bool = True
30
32
  data_on_server: Path
@@ -34,20 +36,21 @@ class Testbed(ShpModel):
34
36
  prep_duration: timedelta = timedelta(minutes=5)
35
37
  # TODO: one BBone is currently time-keeper
36
38
 
37
- @root_validator(pre=True)
39
+ @model_validator(mode="before")
40
+ @classmethod
38
41
  def query_database(cls, values: dict) -> dict:
39
42
  values, _ = tb_client.try_completing_model(cls.__name__, values)
40
43
  return values
41
44
 
42
- @root_validator(pre=False)
43
- def post_validation(cls, values: dict) -> dict:
45
+ @model_validator(mode="after")
46
+ def post_validation(self):
44
47
  observers = []
45
48
  ips = []
46
49
  macs = []
47
50
  capes = []
48
51
  targets = []
49
52
  eth_ports = []
50
- for _obs in values.get("observers"):
53
+ for _obs in self.observers:
51
54
  observers.append(_obs.id)
52
55
  ips.append(_obs.ip)
53
56
  macs.append(_obs.mac)
@@ -70,11 +73,11 @@ class Testbed(ShpModel):
70
73
  raise ValueError("Target used more than once in Testbed")
71
74
  if len(eth_ports) > len(set(eth_ports)):
72
75
  raise ValueError("Observers-Ethernet-Port used more than once in Testbed")
73
- if values.get("prep_duration") and values["prep_duration"].total_seconds() < 0:
76
+ if self.prep_duration.total_seconds() < 0:
74
77
  raise ValueError("Task-Duration can't be negative.")
75
- if not values.get("shared_storage"):
78
+ if not self.shared_storage:
76
79
  raise ValueError("Only shared-storage-option is implemented")
77
- return values
80
+ return self
78
81
 
79
82
  def get_observer(self, target_id: int) -> Observer:
80
83
  for _observer in self.observers:
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  from typing import Union
6
6
 
7
7
  import zstandard as zstd
8
- from pydantic import validate_arguments
8
+ from pydantic import validate_call
9
9
 
10
10
  from ..data_models.content.firmware_datatype import FirmwareDType
11
11
  from .converter_elf import elf_to_hex
@@ -13,7 +13,7 @@ from .validation import is_elf
13
13
  from .validation import is_hex
14
14
 
15
15
 
16
- @validate_arguments
16
+ @validate_call
17
17
  def firmware_to_hex(file_path: Path) -> Path:
18
18
  """Generic converter that handles ELF & HEX"""
19
19
  if not file_path.is_file():
@@ -28,7 +28,7 @@ def firmware_to_hex(file_path: Path) -> Path:
28
28
  )
29
29
 
30
30
 
31
- @validate_arguments
31
+ @validate_call
32
32
  def file_to_base64(file_path: Path) -> str:
33
33
  """Compress and encode content of file
34
34
  - base64 adds ~33 % overhead
@@ -42,7 +42,7 @@ def file_to_base64(file_path: Path) -> str:
42
42
  return base64.b64encode(file_cmpress).decode("ascii")
43
43
 
44
44
 
45
- @validate_arguments
45
+ @validate_call
46
46
  def base64_to_file(content: str, file_path: Path) -> None:
47
47
  """DeCompress and decode Content of file
48
48
  - base64 adds ~33 % overhead
@@ -54,7 +54,7 @@ def base64_to_file(content: str, file_path: Path) -> None:
54
54
  file.write(file_content)
55
55
 
56
56
 
57
- @validate_arguments
57
+ @validate_call
58
58
  def file_to_hash(file_path: Path) -> str:
59
59
  if not file_path.is_file():
60
60
  raise ValueError("Fn needs an existing file as input")
@@ -63,14 +63,14 @@ def file_to_hash(file_path: Path) -> str:
63
63
  return hashlib.sha3_224(file_content).hexdigest()
64
64
 
65
65
 
66
- @validate_arguments
66
+ @validate_call
67
67
  def base64_to_hash(content: str) -> str:
68
68
  file_cmpress = base64.b64decode(content)
69
69
  file_content = zstd.ZstdDecompressor().decompress(file_cmpress)
70
70
  return hashlib.sha3_224(file_content).hexdigest()
71
71
 
72
72
 
73
- @validate_arguments
73
+ @validate_call
74
74
  def extract_firmware(
75
75
  data: Union[str, Path], data_type: FirmwareDType, file_path: Path
76
76
  ) -> Path:
@@ -2,12 +2,12 @@ import subprocess # noqa: S404
2
2
  from pathlib import Path
3
3
  from typing import Optional
4
4
 
5
- from pydantic import validate_arguments
5
+ from pydantic import validate_call
6
6
 
7
7
  # extra src-file necessary to prevent circular import
8
8
 
9
9
 
10
- @validate_arguments
10
+ @validate_call
11
11
  def elf_to_hex(file_elf: Path, file_hex: Optional[Path] = None) -> Path:
12
12
  if not file_elf.is_file():
13
13
  raise ValueError("Fn needs an existing file as input")
@@ -1,8 +1,9 @@
1
1
  from pathlib import Path
2
2
  from typing import Optional
3
3
 
4
- from pydantic import conint
5
- from pydantic import validate_arguments
4
+ from pydantic import Field
5
+ from pydantic import validate_call
6
+ from typing_extensions import Annotated
6
7
 
7
8
  from ..commons import uid_len_default
8
9
  from ..commons import uid_str_default
@@ -21,7 +22,7 @@ except ImportError as e:
21
22
  )
22
23
 
23
24
 
24
- @validate_arguments
25
+ @validate_call
25
26
  def find_symbol(file_elf: Path, symbol: str) -> bool:
26
27
  if symbol is None or not is_elf(file_elf):
27
28
  return False
@@ -44,7 +45,7 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
44
45
  return True
45
46
 
46
47
 
47
- @validate_arguments
48
+ @validate_call
48
49
  def read_symbol(
49
50
  file_elf: Path, symbol: str, length: int = uid_len_default
50
51
  ) -> Optional[int]:
@@ -76,11 +77,11 @@ def read_arch(file_elf: Path) -> Optional[str]:
76
77
  return None
77
78
 
78
79
 
79
- @validate_arguments
80
+ @validate_call
80
81
  def modify_symbol_value(
81
82
  file_elf: Path,
82
83
  symbol: str,
83
- value: conint(ge=0, lt=2 ** (8 * uid_len_default)),
84
+ value: Annotated[int, Field(ge=0, lt=2 ** (8 * uid_len_default))],
84
85
  overwrite: bool = False,
85
86
  ) -> Optional[Path]:
86
87
  """replaces value of symbol in ELF-File, hardcoded for uint16_t (2 byte)
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from intelhex import IntelHex
9
9
  from intelhex import IntelHexError
10
- from pydantic import validate_arguments
10
+ from pydantic import validate_call
11
11
 
12
12
  from ..data_models.content.firmware_datatype import FirmwareDType
13
13
  from ..logger import logger
@@ -26,7 +26,7 @@ except ImportError as e:
26
26
  )
27
27
 
28
28
 
29
- @validate_arguments
29
+ @validate_call
30
30
  def is_hex(file: Path):
31
31
  try:
32
32
  _ = IntelHex(file.as_posix())
@@ -80,7 +80,7 @@ def is_hex_nrf52(file: Path) -> bool:
80
80
  # https://github.com/eliben/pyelftools/wiki/User's-guide
81
81
 
82
82
 
83
- @validate_arguments
83
+ @validate_call
84
84
  def is_elf(file: Path) -> bool:
85
85
  if not elf_support:
86
86
  raise RuntimeError(elf_error_text)
@@ -4,8 +4,10 @@
4
4
  - hardware-config
5
5
  """
6
6
  from pathlib import Path
7
+ from typing import List
7
8
 
8
- from pydantic.types import conlist
9
+ from pydantic import Field
10
+ from typing_extensions import Annotated
9
11
 
10
12
  from ..data_models import ShpModel
11
13
  from .python import PythonInventory
@@ -27,15 +29,21 @@ class Inventory(PythonInventory, SystemInventory, TargetInventory):
27
29
  @classmethod
28
30
  def collect(cls):
29
31
  # one by one for more precise error messages
30
- pid = PythonInventory.collect().dict(exclude_unset=True, exclude_defaults=True)
31
- sid = SystemInventory.collect().dict(exclude_unset=True, exclude_defaults=True)
32
- tid = TargetInventory.collect().dict(exclude_unset=True, exclude_defaults=True)
32
+ pid = PythonInventory.collect().model_dump(
33
+ exclude_unset=True, exclude_defaults=True
34
+ )
35
+ sid = SystemInventory.collect().model_dump(
36
+ exclude_unset=True, exclude_defaults=True
37
+ )
38
+ tid = TargetInventory.collect().model_dump(
39
+ exclude_unset=True, exclude_defaults=True
40
+ )
33
41
  model = {**pid, **sid, **tid}
34
42
  return cls(**model)
35
43
 
36
44
 
37
45
  class InventoryList(ShpModel):
38
- items: conlist(item_type=Inventory, min_items=1)
46
+ elements: Annotated[List[Inventory], Field(min_length=1)]
39
47
 
40
48
  def to_csv(self, path: Path) -> None:
41
49
  """TODO: pretty messed up (raw lists and dicts for sub-elements)
@@ -45,8 +53,8 @@ class InventoryList(ShpModel):
45
53
  if path.is_dir():
46
54
  path = path / "inventory.yaml"
47
55
  with open(path.as_posix(), "w") as fd:
48
- fd.write(", ".join(self.items[0].dict().keys()) + "\r\n")
49
- for item in self.items:
50
- content = list(item.dict().values())
56
+ fd.write(", ".join(self.elements[0].model_dump().keys()) + "\r\n")
57
+ for item in self.elements:
58
+ content = list(item.model_dump().values())
51
59
  content = ["" if value is None else str(value) for value in content]
52
60
  fd.write(", ".join(content) + "\r\n")
@@ -3,11 +3,13 @@ from contextlib import suppress
3
3
  from importlib import import_module
4
4
  from typing import Optional
5
5
 
6
+ from pydantic import ConfigDict
7
+
6
8
  from ..data_models import ShpModel
7
9
 
8
10
 
9
11
  class PythonInventory(ShpModel):
10
- # versions
12
+ # program versions
11
13
  python: Optional[str] = None
12
14
  numpy: Optional[str] = None
13
15
  h5py: Optional[str] = None
@@ -16,8 +18,7 @@ class PythonInventory(ShpModel):
16
18
  shepherd_core: Optional[str] = None
17
19
  shepherd_sheep: Optional[str] = None
18
20
 
19
- class Config:
20
- min_anystr_length = 0
21
+ model_config = ConfigDict(str_min_length=0)
21
22
 
22
23
  @classmethod
23
24
  def collect(cls):
@@ -13,6 +13,7 @@ try:
13
13
  except ImportError:
14
14
  psutil_support = False
15
15
 
16
+ from pydantic import ConfigDict
16
17
  from pydantic.types import PositiveInt
17
18
 
18
19
  from ..data_models import ShpModel
@@ -20,7 +21,7 @@ from ..data_models import ShpModel
20
21
 
21
22
  class SystemInventory(ShpModel):
22
23
  uptime: PositiveInt
23
- # seconds
24
+ # seconds
24
25
 
25
26
  system: str
26
27
  release: str
@@ -34,12 +35,11 @@ class SystemInventory(ShpModel):
34
35
  hostname: str
35
36
 
36
37
  interfaces: dict = {}
37
- # tuple with
38
+ # tuple with
38
39
  # ip IPvAnyAddress
39
40
  # mac MACStr
40
41
 
41
- class Config:
42
- min_anystr_length = 0
42
+ model_config = ConfigDict(str_min_length=0)
43
43
 
44
44
  @classmethod
45
45
  def collect(cls):
@@ -61,7 +61,7 @@ class SystemInventory(ShpModel):
61
61
  )
62
62
 
63
63
  model_dict = {
64
- "uptime": uptime,
64
+ "uptime": round(uptime),
65
65
  "system": platform.system(),
66
66
  "release": platform.release(),
67
67
  "version": platform.version(),
@@ -1,16 +1,16 @@
1
+ from typing import List
1
2
  from typing import Optional
2
3
 
3
- from pydantic import conlist
4
+ from pydantic import ConfigDict
4
5
 
5
6
  from ..data_models import ShpModel
6
7
 
7
8
 
8
9
  class TargetInventory(ShpModel):
9
10
  cape: Optional[str] = None
10
- targets: conlist(item_type=str) = []
11
+ targets: List[str] = []
11
12
 
12
- class Config:
13
- min_anystr_length = 0
13
+ model_config = ConfigDict(str_min_length=0)
14
14
 
15
15
  @classmethod
16
16
  def collect(cls):
shepherd_core/reader.py CHANGED
@@ -17,7 +17,7 @@ from typing import Union
17
17
  import h5py
18
18
  import numpy as np
19
19
  import yaml
20
- from pydantic import validate_arguments
20
+ from pydantic import validate_call
21
21
  from tqdm import trange
22
22
 
23
23
  from .commons import samplerate_sps_default
@@ -45,7 +45,7 @@ class BaseReader:
45
45
  "emulator": [EnergyDType.ivsample],
46
46
  }
47
47
 
48
- @validate_arguments
48
+ @validate_call
49
49
  def __init__(self, file_path: Optional[Path], verbose: Optional[bool] = True):
50
50
  if not hasattr(self, "file_path"):
51
51
  self.file_path: Optional[Path] = None
@@ -99,7 +99,7 @@ class BaseReader:
99
99
 
100
100
  # retrieve cal-data
101
101
  if not hasattr(self, "_cal"):
102
- cal_dict = CalibrationSeries().dict()
102
+ cal_dict = CalibrationSeries().model_dump()
103
103
  for ds, param in product(
104
104
  ["current", "voltage", "time"], ["gain", "offset"]
105
105
  ):
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from typing import Optional
4
4
  from typing import Union
5
5
 
6
- from pydantic import validate_arguments
6
+ from pydantic import validate_call
7
7
 
8
8
  from ..commons import testbed_server_default
9
9
  from ..data_models.base.shepherd import ShpModel
@@ -35,7 +35,7 @@ class TestbedClient:
35
35
  def __del__(self):
36
36
  TestbedClient._instance = None
37
37
 
38
- @validate_arguments
38
+ @validate_call
39
39
  def connect(
40
40
  self, server: Optional[str] = None, token: Union[str, Path, None] = None
41
41
  ) -> bool:
@@ -65,10 +65,12 @@ class TestbedClient:
65
65
  def insert(self, data: ShpModel) -> bool:
66
66
  wrap = Wrapper(
67
67
  datatype=type(data).__name__,
68
- parameters=data.dict(),
68
+ parameters=data.model_dump(),
69
69
  )
70
70
  if self._connected:
71
- r = self._req.post(self._server + "/add", data=wrap.json(), timeout=2)
71
+ r = self._req.post(
72
+ self._server + "/add", data=wrap.model_dump_json(), timeout=2
73
+ )
72
74
  r.raise_for_status()
73
75
  else:
74
76
  self._fixtures.insert_model(wrap)
@@ -6,9 +6,10 @@ from pydantic import EmailStr
6
6
  from pydantic import Field
7
7
  from pydantic import SecretBytes
8
8
  from pydantic import SecretStr
9
- from pydantic import constr
10
- from pydantic import root_validator
11
- from pydantic import validate_arguments
9
+ from pydantic import StringConstraints
10
+ from pydantic import model_validator
11
+ from pydantic import validate_call
12
+ from typing_extensions import Annotated
12
13
 
13
14
  from ..data_models.base.content import IdInt
14
15
  from ..data_models.base.content import NameStr
@@ -17,8 +18,10 @@ from ..data_models.base.content import id_default
17
18
  from ..data_models.base.shepherd import ShpModel
18
19
 
19
20
 
20
- @validate_arguments
21
- def hash_password(pw: constr(min_length=20, max_length=100)) -> bytes:
21
+ @validate_call
22
+ def hash_password(
23
+ pw: Annotated[str, StringConstraints(min_length=20, max_length=100)]
24
+ ) -> bytes:
22
25
  # TODO: add salt of testbed -> this fn should be part of Testbed-Object
23
26
  # NOTE: 1M Iterations need 25s on beaglebone
24
27
  return pbkdf2_hmac(
@@ -46,19 +49,20 @@ class User(ShpModel):
46
49
  email: EmailStr
47
50
 
48
51
  pw_hash: Optional[SecretBytes] = None
49
- # ⤷ = hash_password("this_will_become_a_salted_slow_hash") -> slowed BBB down
52
+ # ⤷ was hash_password("this_will_become_a_salted_slow_hash") -> slowed BBB down
50
53
  # ⤷ TODO (min_length=128, max_length=512)
51
54
 
52
55
  token: SecretStr
53
56
  # ⤷ TODO (min_length=128), request with: token.get_secret_value()
57
+ active: bool = False
54
58
 
55
- @root_validator(pre=True)
59
+ @model_validator(mode="before")
60
+ @classmethod
56
61
  def query_database(cls, values: dict) -> dict:
57
62
  # TODO:
58
- return values
59
63
 
60
- @root_validator(pre=False)
61
- def post_validation(cls, values: dict) -> dict:
64
+ # post correction
62
65
  if values.get("token") is None:
63
66
  values["token"] = "shepherd_token_" + secrets.token_urlsafe(nbytes=128)
67
+
64
68
  return values
shepherd_core/writer.py CHANGED
@@ -14,7 +14,7 @@ from typing import Union
14
14
  import h5py
15
15
  import numpy as np
16
16
  import yaml
17
- from pydantic import validate_arguments
17
+ from pydantic import validate_call
18
18
  from yaml import SafeDumper
19
19
 
20
20
  from .commons import samplerate_sps_default
@@ -90,7 +90,7 @@ class BaseWriter(BaseReader):
90
90
 
91
91
  _chunk_shape: tuple = (BaseReader.samples_per_buffer,)
92
92
 
93
- @validate_arguments
93
+ @validate_call
94
94
  def __init__(
95
95
  self,
96
96
  file_path: Path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shepherd-core
3
- Version: 2023.8.6
3
+ Version: 2023.8.8
4
4
  Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
5
5
  Home-page: https://pypi.org/project/shepherd-core/
6
6
  Author: Ingmar Splitt, Kai Geissdoerfer
@@ -34,7 +34,7 @@ Requires-Dist: h5py
34
34
  Requires-Dist: numpy
35
35
  Requires-Dist: pyYAML
36
36
  Requires-Dist: chromalog
37
- Requires-Dist: pydantic[email] <2.0.0
37
+ Requires-Dist: pydantic[email] >2.0.0
38
38
  Requires-Dist: tqdm
39
39
  Requires-Dist: scipy
40
40
  Requires-Dist: intelhex
@@ -100,6 +100,13 @@ The Library is available via PyPI and can be installed with
100
100
  pip install shepherd-data
101
101
  ```
102
102
 
103
+ for bleeding-edge-features or dev-work it is possible to install directly from GitHub-Sources (here `dev`-branch):
104
+
105
+ ```Shell
106
+ pip install git+https://github.com/orgua/shepherd-datalib.git@dev#subdirectory=shepherd_core -U
107
+ ```
108
+
109
+
103
110
  If you are working with ``.elf``-files (embedding into experiments) you make "objcopy" accessible to python. In Ubuntu, you can either install ``build-essential`` or ``binutils-$ARCH`` with arch being ``msp430`` or ``arm-none-eabi`` for the nRF52.
104
111
 
105
112
  ```shell