shepherd-core 2024.4.1__py3-none-any.whl → 2024.5.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 (76) hide show
  1. shepherd_core/__init__.py +3 -3
  2. shepherd_core/calibration_hw_def.py +9 -1
  3. shepherd_core/commons.py +2 -0
  4. shepherd_core/data_models/__init__.py +11 -0
  5. shepherd_core/data_models/base/__init__.py +4 -1
  6. shepherd_core/data_models/base/cal_measurement.py +18 -6
  7. shepherd_core/data_models/base/calibration.py +39 -15
  8. shepherd_core/data_models/base/content.py +10 -2
  9. shepherd_core/data_models/base/shepherd.py +21 -12
  10. shepherd_core/data_models/base/timezone.py +5 -0
  11. shepherd_core/data_models/base/wrapper.py +3 -3
  12. shepherd_core/data_models/content/__init__.py +5 -4
  13. shepherd_core/data_models/content/_external_fixtures.yaml +410 -0
  14. shepherd_core/data_models/content/energy_environment.py +7 -5
  15. shepherd_core/data_models/content/energy_environment_fixture.yaml +53 -0
  16. shepherd_core/data_models/content/firmware.py +12 -5
  17. shepherd_core/data_models/content/firmware_datatype.py +7 -0
  18. shepherd_core/data_models/content/virtual_harvester.py +24 -19
  19. shepherd_core/data_models/content/virtual_harvester_fixture.yaml +160 -0
  20. shepherd_core/data_models/content/virtual_source.py +22 -10
  21. shepherd_core/data_models/content/virtual_source_fixture.yaml +230 -0
  22. shepherd_core/data_models/experiment/__init__.py +5 -4
  23. shepherd_core/data_models/experiment/experiment.py +7 -6
  24. shepherd_core/data_models/experiment/observer_features.py +14 -7
  25. shepherd_core/data_models/experiment/target_config.py +11 -7
  26. shepherd_core/data_models/readme.md +88 -0
  27. shepherd_core/data_models/task/__init__.py +10 -3
  28. shepherd_core/data_models/task/emulation.py +10 -7
  29. shepherd_core/data_models/task/firmware_mod.py +4 -2
  30. shepherd_core/data_models/task/harvest.py +5 -4
  31. shepherd_core/data_models/task/observer_tasks.py +3 -1
  32. shepherd_core/data_models/task/programming.py +3 -1
  33. shepherd_core/data_models/task/testbed_tasks.py +3 -1
  34. shepherd_core/data_models/testbed/__init__.py +5 -2
  35. shepherd_core/data_models/testbed/cape.py +7 -5
  36. shepherd_core/data_models/testbed/cape_fixture.yaml +94 -0
  37. shepherd_core/data_models/testbed/gpio.py +10 -8
  38. shepherd_core/data_models/testbed/gpio_fixture.yaml +166 -0
  39. shepherd_core/data_models/testbed/mcu.py +9 -9
  40. shepherd_core/data_models/testbed/mcu_fixture.yaml +19 -0
  41. shepherd_core/data_models/testbed/observer.py +9 -4
  42. shepherd_core/data_models/testbed/observer_fixture.yaml +221 -0
  43. shepherd_core/data_models/testbed/target.py +4 -2
  44. shepherd_core/data_models/testbed/target_fixture.yaml +137 -0
  45. shepherd_core/data_models/testbed/testbed.py +5 -2
  46. shepherd_core/data_models/testbed/testbed_fixture.yaml +25 -0
  47. shepherd_core/decoder_waveform/__init__.py +2 -0
  48. shepherd_core/decoder_waveform/uart.py +43 -25
  49. shepherd_core/fw_tools/__init__.py +2 -0
  50. shepherd_core/fw_tools/converter.py +20 -9
  51. shepherd_core/fw_tools/converter_elf.py +3 -0
  52. shepherd_core/fw_tools/patcher.py +17 -5
  53. shepherd_core/fw_tools/validation.py +27 -6
  54. shepherd_core/inventory/__init__.py +15 -5
  55. shepherd_core/inventory/python.py +4 -0
  56. shepherd_core/inventory/system.py +6 -2
  57. shepherd_core/inventory/target.py +4 -0
  58. shepherd_core/logger.py +5 -0
  59. shepherd_core/reader.py +38 -23
  60. shepherd_core/testbed_client/__init__.py +2 -0
  61. shepherd_core/testbed_client/cache_path.py +2 -0
  62. shepherd_core/testbed_client/client.py +15 -8
  63. shepherd_core/testbed_client/fixtures.py +27 -11
  64. shepherd_core/testbed_client/user_model.py +8 -3
  65. shepherd_core/vsource/__init__.py +2 -0
  66. shepherd_core/vsource/virtual_converter_model.py +10 -3
  67. shepherd_core/vsource/virtual_harvester_model.py +7 -1
  68. shepherd_core/vsource/virtual_source_model.py +9 -5
  69. shepherd_core/writer.py +26 -21
  70. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/METADATA +2 -1
  71. shepherd_core-2024.5.1.dist-info/RECORD +75 -0
  72. shepherd_core-2024.4.1.dist-info/RECORD +0 -64
  73. /shepherd_core/data_models/{doc_virtual_source.py → virtual_source_doc.txt} +0 -0
  74. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/WHEEL +0 -0
  75. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/top_level.txt +0 -0
  76. {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/zip-safe +0 -0
@@ -1,3 +1,5 @@
1
+ """Content-Converters for firmwares."""
2
+
1
3
  import base64
2
4
  import hashlib
3
5
  import shutil
@@ -15,21 +17,25 @@ from .validation import is_hex
15
17
 
16
18
  @validate_call
17
19
  def firmware_to_hex(file_path: Path) -> Path:
18
- """Generic converter that handles ELF & HEX"""
20
+ """Convert ELF-Files to HEX.
21
+
22
+ Generic converter that handles ELF & HEX.
23
+ """
19
24
  if not file_path.is_file():
20
- raise ValueError("Fn needs an existing file as input")
25
+ raise FileNotFoundError("Fn needs an existing file as input")
21
26
  if is_elf(file_path):
22
27
  return elf_to_hex(file_path)
23
28
  if is_hex(file_path):
24
29
  return file_path
25
- raise ValueError("FW2Hex: unknown file '%s', it should be ELF or HEX", file_path.name)
30
+ raise FileNotFoundError("FW2Hex: unknown file '%s', it should be ELF or HEX", file_path.name)
26
31
 
27
32
 
28
33
  @validate_call
29
34
  def file_to_base64(file_path: Path) -> str:
30
- """Compress and encode content of file
35
+ """Compress and encode content of file.
36
+
31
37
  - base64 adds ~33 % overhead
32
- - zstd compression ~ 1:3
38
+ - zstd compression reduces to ~ 1:3
33
39
  """
34
40
  if not file_path.is_file():
35
41
  raise ValueError("Fn needs an existing file as input")
@@ -41,9 +47,10 @@ def file_to_base64(file_path: Path) -> str:
41
47
 
42
48
  @validate_call
43
49
  def base64_to_file(content: str, file_path: Path) -> None:
44
- """DeCompress and decode Content of file
50
+ """DeCompress and decode Content of file.
51
+
45
52
  - base64 adds ~33 % overhead
46
- - zstd compression ~ 1:3
53
+ - zstd compression reduces to ~ 1:3
47
54
  """
48
55
  file_cmpress = base64.b64decode(content)
49
56
  file_content = zstd.ZstdDecompressor().decompress(file_cmpress)
@@ -55,6 +62,7 @@ def base64_to_file(content: str, file_path: Path) -> None:
55
62
 
56
63
  @validate_call
57
64
  def file_to_hash(file_path: Path) -> str:
65
+ """Convert file-content to hash-value."""
58
66
  if not file_path.is_file():
59
67
  raise ValueError("Fn needs an existing file as input")
60
68
  with file_path.resolve().open("rb") as file:
@@ -64,6 +72,7 @@ def file_to_hash(file_path: Path) -> str:
64
72
 
65
73
  @validate_call
66
74
  def base64_to_hash(content: str) -> str:
75
+ """Convert base64-content to hash-value."""
67
76
  file_cmpress = base64.b64decode(content)
68
77
  file_content = zstd.ZstdDecompressor().decompress(file_cmpress)
69
78
  return hashlib.sha3_224(file_content).hexdigest()
@@ -71,8 +80,10 @@ def base64_to_hash(content: str) -> str:
71
80
 
72
81
  @validate_call
73
82
  def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path: Path) -> Path:
74
- """- base64-string will be transformed into file
75
- - if data is a path the file will be copied to the destination
83
+ """Make embedded firmware-data usable in filesystem.
84
+
85
+ - base64-string will be transformed to file
86
+ - if data is a path the file will be copied to the destination.
76
87
  """
77
88
  if data_type == FirmwareDType.base64_elf:
78
89
  file = file_path.with_suffix(".elf")
@@ -1,3 +1,5 @@
1
+ """Converter for ELF-files."""
2
+
1
3
  import subprocess
2
4
  from pathlib import Path
3
5
  from typing import Optional
@@ -9,6 +11,7 @@ from pydantic import validate_call
9
11
 
10
12
  @validate_call
11
13
  def elf_to_hex(file_elf: Path, file_hex: Optional[Path] = None) -> Path:
14
+ """Convert ELF to hex file using objcopy."""
12
15
  if not file_elf.is_file():
13
16
  raise ValueError("Fn needs an existing file as input")
14
17
  if not file_hex:
@@ -1,3 +1,5 @@
1
+ """Read and modify symbols in ELF-files."""
2
+
1
3
  from pathlib import Path
2
4
  from typing import Optional
3
5
 
@@ -15,13 +17,14 @@ try:
15
17
  except ImportError as e:
16
18
  ELF = None
17
19
  elf_error_text = (
18
- "Please install functionality with 'pip install shepherd_core[elf] -U' first, "
20
+ "Please install functionality with 'pip install shepherd-core[elf] -U' first, "
19
21
  f"underlying exception: {e.msg}"
20
22
  )
21
23
 
22
24
 
23
25
  @validate_call
24
26
  def find_symbol(file_elf: Path, symbol: str) -> bool:
27
+ """Find a symbol in the ELF file."""
25
28
  if symbol is None or not is_elf(file_elf):
26
29
  return False
27
30
  if ELF is None:
@@ -47,7 +50,10 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
47
50
 
48
51
  @validate_call
49
52
  def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> Optional[int]:
50
- """Interpreted as int"""
53
+ """Read value of symbol in ELF-File.
54
+
55
+ Will be interpreted as int.
56
+ """
51
57
  if not find_symbol(file_elf, symbol):
52
58
  return None
53
59
  if ELF is None:
@@ -60,10 +66,12 @@ def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> O
60
66
 
61
67
 
62
68
  def read_uid(file_elf: Path) -> Optional[int]:
69
+ """Read value of UID-symbol for shepherd testbed."""
63
70
  return read_symbol(file_elf, symbol=uid_str_default, length=uid_len_default)
64
71
 
65
72
 
66
73
  def read_arch(file_elf: Path) -> Optional[str]:
74
+ """Determine chip-architecture from elf-metadata."""
67
75
  if not is_elf(file_elf):
68
76
  return None
69
77
  if ELF is None:
@@ -83,9 +91,12 @@ def modify_symbol_value(
83
91
  *,
84
92
  overwrite: bool = False,
85
93
  ) -> Optional[Path]:
86
- """Replaces value of symbol in ELF-File, hardcoded for uint16_t (2 byte)
87
- testbed uses FN to patch firmware with custom target-ID
88
- NOTE: can overwrite provided file
94
+ """Replace value of uint16-symbol in ELF-File.
95
+
96
+ Hardcoded for uint16_t (2 byte).
97
+ The testbed uses this to patch firmware with custom target-ID.
98
+
99
+ NOTE: can overwrite provided file.
89
100
 
90
101
  """
91
102
  if not find_symbol(file_elf, symbol):
@@ -121,4 +132,5 @@ def modify_symbol_value(
121
132
 
122
133
 
123
134
  def modify_uid(file_elf: Path, value: int) -> Optional[Path]:
135
+ """Replace value of UID-symbol for shepherd testbed."""
124
136
  return modify_symbol_value(file_elf, symbol=uid_str_default, value=value, overwrite=True)
@@ -1,4 +1,9 @@
1
- """TODO: Work in Progress"""
1
+ """Helper-functions for firmware-validation.
2
+
3
+ TODO: Work in Progress.
4
+ - Each arch should have its own file and
5
+ - detection-functions that register in main validator.
6
+ """
2
7
 
3
8
  import tempfile
4
9
  from pathlib import Path
@@ -18,13 +23,14 @@ except ImportError as e:
18
23
  ELF = None
19
24
  ELFError = None
20
25
  elf_error_text = (
21
- "Please install functionality with 'pip install shepherd_core[elf] -U' first, "
26
+ "Please install functionality with 'pip install shepherd-core[elf] -U' first, "
22
27
  f"underlying exception: {e.msg}"
23
28
  )
24
29
 
25
30
 
26
31
  @validate_call
27
32
  def is_hex(file: Path) -> bool:
33
+ """Check if file is containing intel-hex data."""
28
34
  try:
29
35
  _ = IntelHex(file.as_posix())
30
36
  except ValueError: # parsing
@@ -35,7 +41,9 @@ def is_hex(file: Path) -> bool:
35
41
 
36
42
 
37
43
  def is_hex_msp430(file: Path) -> bool:
38
- """Observations:
44
+ """Try to detect specifics for that MCU.
45
+
46
+ Observations:
39
47
  - addresses begin at 0x4000
40
48
  - value @0xFFFE (IVT) is start_address (of pgm-code)
41
49
  """
@@ -56,7 +64,9 @@ def is_hex_msp430(file: Path) -> bool:
56
64
 
57
65
 
58
66
  def is_hex_nrf52(file: Path) -> bool:
59
- """Observations:
67
+ """Try to detect specifics for that MCU.
68
+
69
+ Observations:
60
70
  - addresses begin at 0x0
61
71
  - only one segment (.get_segments), todo
62
72
  """
@@ -79,6 +89,7 @@ def is_hex_nrf52(file: Path) -> bool:
79
89
 
80
90
  @validate_call
81
91
  def is_elf(file: Path) -> bool:
92
+ """Check if file is an ELF file."""
82
93
  if ELF is None:
83
94
  raise RuntimeError(elf_error_text)
84
95
  if not file.is_file():
@@ -91,7 +102,11 @@ def is_elf(file: Path) -> bool:
91
102
  return True
92
103
 
93
104
 
94
- def is_elf_msp430(file: Path) -> bool: # TODO: allow detection without conversion
105
+ def is_elf_msp430(file: Path) -> bool:
106
+ """Check if file is an ELF for that MCU.
107
+
108
+ TODO: allow detection without conversion
109
+ """
95
110
  if is_elf(file):
96
111
  with tempfile.TemporaryDirectory() as path:
97
112
  file_hex = Path(path) / "file.hex"
@@ -102,7 +117,11 @@ def is_elf_msp430(file: Path) -> bool: # TODO: allow detection without conversi
102
117
  return False
103
118
 
104
119
 
105
- def is_elf_nrf52(file: Path) -> bool: # TODO: allow detection without conversion
120
+ def is_elf_nrf52(file: Path) -> bool:
121
+ """Check if file is an ELF for that MCU.
122
+
123
+ TODO: allow detection without conversion
124
+ """
106
125
  if is_elf(file):
107
126
  with tempfile.TemporaryDirectory() as path:
108
127
  file_hex = Path(path) / "file.hex"
@@ -114,6 +133,7 @@ def is_elf_nrf52(file: Path) -> bool: # TODO: allow detection without conversio
114
133
 
115
134
 
116
135
  def determine_type(file: Path) -> FirmwareDType:
136
+ """Figure out file-type (hex or elf)."""
117
137
  if not file.is_file():
118
138
  raise ValueError("Fn needs an existing file as input")
119
139
  if is_hex(file):
@@ -124,6 +144,7 @@ def determine_type(file: Path) -> FirmwareDType:
124
144
 
125
145
 
126
146
  def determine_arch(file: Path) -> str:
147
+ """Figure out arch (msp430 or nrf52)."""
127
148
  file_t = determine_type(file)
128
149
  if file_t == FirmwareDType.path_elf:
129
150
  if is_elf_nrf52(file):
@@ -1,7 +1,9 @@
1
- """Creates an overview for shepherd-host-machines with:
1
+ """Creates an overview for shepherd-host-machines.
2
+
3
+ This will collect:
2
4
  - relevant software-versions
3
5
  - system-parameters
4
- - hardware-config
6
+ - hardware-config.
5
7
  """
6
8
 
7
9
  from datetime import datetime
@@ -28,7 +30,11 @@ __all__ = [
28
30
 
29
31
 
30
32
  class Inventory(PythonInventory, SystemInventory, TargetInventory):
31
- # has all child-parameters
33
+ """Complete inventory for one device.
34
+
35
+ Has all child-parameters.
36
+ """
37
+
32
38
  hostname: str
33
39
  created: datetime
34
40
 
@@ -47,12 +53,16 @@ class Inventory(PythonInventory, SystemInventory, TargetInventory):
47
53
 
48
54
 
49
55
  class InventoryList(ShpModel):
56
+ """Collection of inventories for several devices."""
57
+
50
58
  elements: Annotated[List[Inventory], Field(min_length=1)]
51
59
 
52
60
  def to_csv(self, path: Path) -> None:
53
- """TODO: pretty messed up (raw lists and dicts for sub-elements)
61
+ """Generate a CSV.
62
+
63
+ TODO: pretty messed up (raw lists and dicts for sub-elements)
54
64
  numpy.savetxt -> too basic
55
- np.concatenate(content).reshape((len(content), len(content[0])))
65
+ np.concatenate(content).reshape((len(content), len(content[0]))).
56
66
  """
57
67
  if path.is_dir():
58
68
  path = path / "inventory.yaml"
@@ -1,3 +1,5 @@
1
+ """Python related inventory model."""
2
+
1
3
  import platform
2
4
  from contextlib import suppress
3
5
  from importlib import import_module
@@ -10,6 +12,8 @@ from ..data_models import ShpModel
10
12
 
11
13
 
12
14
  class PythonInventory(ShpModel):
15
+ """Python related inventory model."""
16
+
13
17
  # program versions
14
18
  h5py: Optional[str] = None
15
19
  numpy: Optional[str] = None
@@ -1,3 +1,5 @@
1
+ """System / OS related inventory model."""
2
+
1
3
  import platform
2
4
  import subprocess
3
5
  import time
@@ -7,8 +9,8 @@ from typing import Optional
7
9
 
8
10
  from typing_extensions import Self
9
11
 
10
- from .. import local_now
11
- from .. import logger
12
+ from ..data_models.base.timezone import local_now
13
+ from ..logger import logger
12
14
 
13
15
  try:
14
16
  import psutil
@@ -22,6 +24,8 @@ from ..data_models import ShpModel
22
24
 
23
25
 
24
26
  class SystemInventory(ShpModel):
27
+ """System / OS related inventory model."""
28
+
25
29
  uptime: PositiveInt
26
30
  # ⤷ seconds
27
31
  timestamp: datetime
@@ -1,3 +1,5 @@
1
+ """Hardware related inventory model."""
2
+
1
3
  from typing import List
2
4
  from typing import Optional
3
5
 
@@ -8,6 +10,8 @@ from ..data_models import ShpModel
8
10
 
9
11
 
10
12
  class TargetInventory(ShpModel):
13
+ """Hardware related inventory model."""
14
+
11
15
  cape: Optional[str] = None
12
16
  targets: List[str] = [] # noqa: RUF012
13
17
 
shepherd_core/logger.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Log handler of shepherd."""
2
+
1
3
  import logging
2
4
  import logging.handlers
3
5
  from typing import Union
@@ -12,10 +14,12 @@ verbose_level: int = 2
12
14
 
13
15
 
14
16
  def get_verbose_level() -> int:
17
+ """Get log level of shepherd."""
15
18
  return verbose_level
16
19
 
17
20
 
18
21
  def set_log_verbose_level(log_: Union[logging.Logger, logging.Handler], verbose: int) -> None:
22
+ """Set log level of shepherd."""
19
23
  if verbose == 0:
20
24
  log_.setLevel(logging.ERROR)
21
25
  logging.basicConfig(level=logging.ERROR)
@@ -39,6 +43,7 @@ def set_log_verbose_level(log_: Union[logging.Logger, logging.Handler], verbose:
39
43
 
40
44
 
41
45
  def increase_verbose_level(verbose: int) -> None:
46
+ """Increase log level of shepherd."""
42
47
  global verbose_level # noqa: PLW0603
43
48
  if verbose >= verbose_level:
44
49
  verbose_level = min(max(verbose, 0), 3)
shepherd_core/reader.py CHANGED
@@ -1,4 +1,6 @@
1
- """Reader-Baseclass"""
1
+ """Reader-Baseclass."""
2
+
3
+ from __future__ import annotations
2
4
 
3
5
  import contextlib
4
6
  import errno
@@ -7,7 +9,7 @@ import math
7
9
  import os
8
10
  from itertools import product
9
11
  from pathlib import Path
10
- from types import TracebackType
12
+ from typing import TYPE_CHECKING
11
13
  from typing import Any
12
14
  from typing import ClassVar
13
15
  from typing import Dict
@@ -30,6 +32,9 @@ from .data_models.base.calibration import CalibrationSeries
30
32
  from .data_models.content.energy_environment import EnergyDType
31
33
  from .decoder_waveform import Uart
32
34
 
35
+ if TYPE_CHECKING:
36
+ from types import TracebackType
37
+
33
38
 
34
39
  class Reader:
35
40
  """Sequentially Reads shepherd-data from HDF5 file.
@@ -96,7 +101,8 @@ class Reader:
96
101
  self.h5file = h5py.File(self.file_path, "r") # = readonly
97
102
  self._reader_opened = True
98
103
  except OSError as _xcp:
99
- raise TypeError(f"Unable to open HDF5-File '{self.file_path.name}'") from _xcp
104
+ msg = f"Unable to open HDF5-File '{self.file_path.name}'"
105
+ raise TypeError(msg) from _xcp
100
106
 
101
107
  if self.is_valid():
102
108
  self._logger.debug("File is available now")
@@ -159,7 +165,7 @@ class Reader:
159
165
  )
160
166
 
161
167
  def _refresh_file_stats(self) -> None:
162
- """Update internal states, helpful after resampling or other changes in data-group"""
168
+ """Update internal states, helpful after resampling or other changes in data-group."""
163
169
  self.h5file.flush()
164
170
  if (self.ds_time.shape[0] > 1) and (self.ds_time[1] != self.ds_time[0]):
165
171
  # this assumes isochronal sampling
@@ -181,10 +187,11 @@ class Reader:
181
187
  is_raw: bool = False,
182
188
  omit_ts: bool = False,
183
189
  ) -> Generator[tuple, None, None]:
184
- """Generator that reads the specified range of buffers from the hdf5 file.
185
- can be configured on first call
190
+ """Read the specified range of buffers from the hdf5 file.
191
+
192
+ Generator - can be configured on first call
186
193
  TODO: reconstruct - start/end mark samples &
187
- each call can request a certain number of samples
194
+ each call can request a certain number of samples.
188
195
 
189
196
  Args:
190
197
  ----
@@ -218,7 +225,7 @@ class Reader:
218
225
  )
219
226
 
220
227
  def get_calibration_data(self) -> CalibrationSeries:
221
- """Reads calibration-data from hdf5 file.
228
+ """Read calibration-data from hdf5 file.
222
229
 
223
230
  :return: Calibration data as CalibrationSeries object
224
231
  """
@@ -248,12 +255,14 @@ class Reader:
248
255
  try:
249
256
  if "datatype" in self.h5file["data"].attrs:
250
257
  return EnergyDType[self.h5file["data"].attrs["datatype"]]
251
- return None
252
258
  except KeyError:
253
259
  return None
260
+ else:
261
+ return None
254
262
 
255
263
  def get_hrv_config(self) -> dict:
256
- """Essential info for harvester
264
+ """Essential info for harvester.
265
+
257
266
  :return: config-dict directly for vHarvester to be used during emulation
258
267
  """
259
268
  return {
@@ -262,7 +271,7 @@ class Reader:
262
271
  }
263
272
 
264
273
  def is_valid(self) -> bool:
265
- """Checks file for plausibility
274
+ """Check file for plausibility, validity / correctness.
266
275
 
267
276
  :return: state of validity
268
277
  """
@@ -390,7 +399,7 @@ class Reader:
390
399
  return True
391
400
 
392
401
  def __getitem__(self, key: str) -> Any:
393
- """Returns attribute or (if none found) a handle for a group or dataset (if found)
402
+ """Query attribute or (if none found) a handle for a group or dataset (if found).
394
403
 
395
404
  :param key: attribute, group, dataset
396
405
  :return: value of that key, or handle of object
@@ -402,9 +411,10 @@ class Reader:
402
411
  raise KeyError
403
412
 
404
413
  def energy(self) -> float:
405
- """Determine the recorded energy of the trace
414
+ """Determine the recorded energy of the trace.
415
+
406
416
  # multiprocessing: https://stackoverflow.com/a/71898911
407
- # -> failed with multiprocessing.pool and pathos.multiprocessing.ProcessPool
417
+ # -> failed with multiprocessing.pool and pathos.multiprocessing.ProcessPool.
408
418
 
409
419
  :return: sampled energy in Ws (watt-seconds)
410
420
  """
@@ -430,7 +440,8 @@ class Reader:
430
440
  def _dset_statistics(
431
441
  self, dset: h5py.Dataset, cal: Optional[CalibrationPair] = None
432
442
  ) -> Dict[str, float]:
433
- """Some basic stats for a provided dataset
443
+ """Create basic stats for a provided dataset.
444
+
434
445
  :param dset: dataset to evaluate
435
446
  :param cal: calibration (if wanted)
436
447
  :return: dict with entries for mean, min, max, std
@@ -472,8 +483,9 @@ class Reader:
472
483
  return stats
473
484
 
474
485
  def _data_timediffs(self) -> List[float]:
475
- """Calculate list of (unique) time-deltas between buffers [s]
476
- -> optimized version that only looks at the start of each buffer
486
+ """Calculate list of unique time-deltas [s] between buffers.
487
+
488
+ Optimized version that only looks at the start of each buffer.
477
489
 
478
490
  :return: list of (unique) time-deltas between buffers [s]
479
491
  """
@@ -503,8 +515,9 @@ class Reader:
503
515
  return list(diffs)
504
516
 
505
517
  def check_timediffs(self) -> bool:
506
- """Validate equal time-deltas
507
- -> unexpected time-jumps hint at a corrupted file or faulty measurement
518
+ """Validate equal time-deltas.
519
+
520
+ Unexpected time-jumps hint at a corrupted file or faulty measurement.
508
521
 
509
522
  :return: True if OK
510
523
  """
@@ -532,7 +545,8 @@ class Reader:
532
545
  *,
533
546
  minimal: bool = False,
534
547
  ) -> Dict[str, dict]:
535
- """Recursive FN to capture the structure of the file
548
+ """Recursive FN to capture the structure of the file.
549
+
536
550
  :param node: starting node, leave free to go through whole file
537
551
  :param minimal: just provide a bare tree (much faster)
538
552
  :return: structure of that node with everything inside it
@@ -583,7 +597,7 @@ class Reader:
583
597
  return metadata
584
598
 
585
599
  def save_metadata(self, node: Union[h5py.Dataset, h5py.Group, None] = None) -> dict:
586
- """Get structure of file and dump content to yaml-file with same name as original
600
+ """Get structure of file and dump content to yaml-file with same name as original.
587
601
 
588
602
  :param node: starting node, leave free to go through whole file
589
603
  :return: structure of that node with everything inside it
@@ -612,8 +626,9 @@ class Reader:
612
626
 
613
627
  @staticmethod
614
628
  def get_filter_for_redundant_states(data: np.ndarray) -> np.ndarray:
615
- """Input is 1D state-vector, kep only first from identical & sequential states
616
- algo: create an offset-by-one vector and compare against original
629
+ """Input is 1D state-vector, kep only first from identical & sequential states.
630
+
631
+ Algo: create an offset-by-one vector and compare against original.
617
632
  """
618
633
  if len(data.shape) > 1:
619
634
  ValueError("Array must be 1D")
@@ -1,3 +1,5 @@
1
+ """Client to access a testbed-instance for controlling experiments."""
2
+
1
3
  from .client import tb_client
2
4
  from .user_model import User
3
5
 
@@ -1,3 +1,5 @@
1
+ """Generalized path for the file-cache."""
2
+
1
3
  import os
2
4
  from pathlib import Path
3
5
 
@@ -1,3 +1,5 @@
1
+ """Client-Class to access a testbed instance."""
2
+
1
3
  from importlib import import_module
2
4
  from pathlib import Path
3
5
  from typing import Optional
@@ -16,6 +18,8 @@ from .user_model import User
16
18
 
17
19
 
18
20
  class TestbedClient:
21
+ """Client-Class to access a testbed instance."""
22
+
19
23
  _instance: Optional[Self] = None
20
24
 
21
25
  def __init__(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> None:
@@ -41,8 +45,10 @@ class TestbedClient:
41
45
 
42
46
  @validate_call
43
47
  def connect(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> bool:
44
- """server: either "local" to use demo-fixtures or something like "https://HOST:PORT"
45
- token: your account validation
48
+ """Establish connection to testbed-server.
49
+
50
+ server: either "local" to use demo-fixtures or something like "https://HOST:PORT"
51
+ token: your account validation.
46
52
  """
47
53
  if isinstance(token, Path):
48
54
  with token.resolve().open() as file:
@@ -77,19 +83,19 @@ class TestbedClient:
77
83
 
78
84
  def query_ids(self, model_type: str) -> list:
79
85
  if self._connected:
80
- raise RuntimeError("Not Implemented, TODO")
86
+ raise NotImplementedError("TODO")
81
87
  return list(self._fixtures[model_type].elements_by_id.keys())
82
88
 
83
89
  def query_names(self, model_type: str) -> list:
84
90
  if self._connected:
85
- raise RuntimeError("Not Implemented, TODO")
91
+ raise NotImplementedError("TODO")
86
92
  return list(self._fixtures[model_type].elements_by_name.keys())
87
93
 
88
94
  def query_item(
89
95
  self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
90
96
  ) -> dict:
91
97
  if self._connected:
92
- raise RuntimeError("Not Implemented, TODO")
98
+ raise NotImplementedError("TODO")
93
99
  if uid is not None:
94
100
  return self._fixtures[model_type].query_id(uid)
95
101
  if name is not None:
@@ -115,11 +121,11 @@ class TestbedClient:
115
121
 
116
122
  def try_inheritance(self, model_type: str, values: dict) -> (dict, list):
117
123
  if self._connected:
118
- raise RuntimeError("Not Implemented, TODO")
124
+ raise NotImplementedError("TODO")
119
125
  return self._fixtures[model_type].inheritance(values)
120
126
 
121
127
  def try_completing_model(self, model_type: str, values: dict) -> (dict, list):
122
- """Init by name/id, for none existing instances raise Exception"""
128
+ """Init by name/id, for none existing instances raise Exception."""
123
129
  if len(values) == 1 and next(iter(values.keys())) in {"id", "name"}:
124
130
  value = next(iter(values.values()))
125
131
  if (
@@ -131,7 +137,8 @@ class TestbedClient:
131
137
  # TODO: still depending on _fixture
132
138
  values = self.query_item(model_type, uid=value)
133
139
  else:
134
- raise ValueError(f"Query {model_type} by name / ID failed - {values} is unknown!")
140
+ msg = f"Query {model_type} by name / ID failed - {values} is unknown!"
141
+ raise ValueError(msg)
135
142
  return self.try_inheritance(model_type, values)
136
143
 
137
144
  def fill_in_user_data(self, values: dict) -> dict: