shepherd-core 2025.2.2__py3-none-any.whl → 2025.4.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.
- shepherd_core/calibration_hw_def.py +11 -11
- shepherd_core/commons.py +4 -4
- shepherd_core/data_models/base/cal_measurement.py +10 -11
- shepherd_core/data_models/base/calibration.py +11 -8
- shepherd_core/data_models/base/content.py +1 -1
- shepherd_core/data_models/base/shepherd.py +6 -7
- shepherd_core/data_models/base/wrapper.py +2 -2
- shepherd_core/data_models/content/energy_environment.py +4 -3
- shepherd_core/data_models/content/firmware.py +9 -7
- shepherd_core/data_models/content/virtual_harvester.py +30 -22
- shepherd_core/data_models/content/virtual_source.py +17 -16
- shepherd_core/data_models/experiment/experiment.py +15 -14
- shepherd_core/data_models/experiment/observer_features.py +7 -8
- shepherd_core/data_models/experiment/target_config.py +12 -12
- shepherd_core/data_models/readme.md +2 -1
- shepherd_core/data_models/task/__init__.py +5 -5
- shepherd_core/data_models/task/emulation.py +13 -14
- shepherd_core/data_models/task/firmware_mod.py +11 -11
- shepherd_core/data_models/task/harvest.py +7 -6
- shepherd_core/data_models/task/observer_tasks.py +7 -7
- shepherd_core/data_models/task/programming.py +11 -11
- shepherd_core/data_models/task/testbed_tasks.py +8 -8
- shepherd_core/data_models/testbed/cape.py +7 -6
- shepherd_core/data_models/testbed/gpio.py +8 -7
- shepherd_core/data_models/testbed/mcu.py +8 -7
- shepherd_core/data_models/testbed/observer.py +9 -7
- shepherd_core/data_models/testbed/target.py +9 -7
- shepherd_core/data_models/testbed/testbed.py +11 -10
- shepherd_core/decoder_waveform/uart.py +5 -5
- shepherd_core/fw_tools/converter.py +4 -3
- shepherd_core/fw_tools/patcher.py +14 -15
- shepherd_core/fw_tools/validation.py +3 -2
- shepherd_core/inventory/__init__.py +4 -4
- shepherd_core/inventory/python.py +1 -1
- shepherd_core/inventory/system.py +11 -8
- shepherd_core/inventory/target.py +3 -3
- shepherd_core/logger.py +2 -2
- shepherd_core/reader.py +43 -43
- shepherd_core/testbed_client/client_abc_fix.py +20 -13
- shepherd_core/testbed_client/client_web.py +18 -11
- shepherd_core/testbed_client/fixtures.py +24 -44
- shepherd_core/testbed_client/user_model.py +6 -5
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/target_model.py +3 -3
- shepherd_core/vsource/virtual_converter_model.py +3 -3
- shepherd_core/vsource/virtual_harvester_model.py +7 -9
- shepherd_core/vsource/virtual_harvester_simulation.py +6 -5
- shepherd_core/vsource/virtual_source_model.py +6 -5
- shepherd_core/vsource/virtual_source_simulation.py +7 -6
- shepherd_core/writer.py +33 -34
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.2.dist-info}/METADATA +3 -4
- shepherd_core-2025.4.2.dist-info/RECORD +81 -0
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.2.dist-info}/WHEEL +1 -1
- shepherd_core-2025.2.2.dist-info/RECORD +0 -81
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.2.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.2.dist-info}/zip-safe +0 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""Current implementation of a file-based database."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
-
import os
|
|
5
4
|
import pickle
|
|
5
|
+
from collections.abc import Mapping
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from datetime import timedelta
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any
|
|
10
|
-
from typing import Dict
|
|
11
|
-
from typing import List
|
|
12
10
|
from typing import Optional
|
|
13
11
|
from typing import Union
|
|
14
12
|
|
|
@@ -16,10 +14,11 @@ import yaml
|
|
|
16
14
|
from pydantic import validate_call
|
|
17
15
|
from typing_extensions import Self
|
|
18
16
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
17
|
+
from shepherd_core.data_models.base.timezone import local_now
|
|
18
|
+
from shepherd_core.data_models.base.timezone import local_tz
|
|
19
|
+
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
20
|
+
from shepherd_core.logger import logger
|
|
21
|
+
|
|
23
22
|
from .cache_path import cache_user_path
|
|
24
23
|
|
|
25
24
|
# Proposed field-name:
|
|
@@ -35,8 +34,8 @@ class Fixture:
|
|
|
35
34
|
|
|
36
35
|
def __init__(self, model_type: str) -> None:
|
|
37
36
|
self.model_type: str = model_type.lower()
|
|
38
|
-
self.elements_by_name:
|
|
39
|
-
self.elements_by_id:
|
|
37
|
+
self.elements_by_name: dict[str, dict] = {}
|
|
38
|
+
self.elements_by_id: dict[int, dict] = {}
|
|
40
39
|
# Iterator reset
|
|
41
40
|
self._iter_index: int = 0
|
|
42
41
|
self._iter_list: list = list(self.elements_by_name.values())
|
|
@@ -44,25 +43,26 @@ class Fixture:
|
|
|
44
43
|
def insert(self, data: Wrapper) -> None:
|
|
45
44
|
# ⤷ TODO: could get easier
|
|
46
45
|
# - when not model_name but class used
|
|
47
|
-
# - use doubleref name->id->data (
|
|
46
|
+
# - use doubleref name->id->data (saves RAM)
|
|
48
47
|
if data.datatype.lower() != self.model_type.lower():
|
|
49
48
|
return
|
|
50
49
|
if "name" not in data.parameters:
|
|
51
50
|
return
|
|
52
51
|
name = str(data.parameters["name"]).lower()
|
|
53
52
|
_id = data.parameters["id"]
|
|
54
|
-
|
|
55
|
-
self.elements_by_name[name] =
|
|
56
|
-
self.elements_by_id[_id] =
|
|
53
|
+
data_model = data.parameters
|
|
54
|
+
self.elements_by_name[name] = data_model
|
|
55
|
+
self.elements_by_id[_id] = data_model
|
|
57
56
|
# update iterator
|
|
58
|
-
self._iter_list
|
|
57
|
+
self._iter_list = list(self.elements_by_name.values())
|
|
59
58
|
|
|
60
59
|
def __getitem__(self, key: Union[str, int]) -> dict:
|
|
61
60
|
if isinstance(key, str):
|
|
62
61
|
key = key.lower()
|
|
63
62
|
if key in self.elements_by_name:
|
|
64
63
|
return self.elements_by_name[key]
|
|
65
|
-
|
|
64
|
+
if key.isdigit():
|
|
65
|
+
key = int(key)
|
|
66
66
|
if key in self.elements_by_id:
|
|
67
67
|
return self.elements_by_id[int(key)]
|
|
68
68
|
msg = f"{self.model_type} '{key}' not found!"
|
|
@@ -86,7 +86,9 @@ class Fixture:
|
|
|
86
86
|
def refs(self) -> dict:
|
|
87
87
|
return {_i["id"]: _i["name"] for _i in self.elements_by_id.values()}
|
|
88
88
|
|
|
89
|
-
def inheritance(
|
|
89
|
+
def inheritance(
|
|
90
|
+
self, values: dict[str, Any], chain: Optional[list[str]] = None
|
|
91
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
90
92
|
if chain is None:
|
|
91
93
|
chain = []
|
|
92
94
|
values = copy.copy(values)
|
|
@@ -144,7 +146,7 @@ class Fixture:
|
|
|
144
146
|
return values, chain
|
|
145
147
|
|
|
146
148
|
@staticmethod
|
|
147
|
-
def fill_model(model:
|
|
149
|
+
def fill_model(model: Mapping, base: dict) -> dict:
|
|
148
150
|
base = copy.copy(base)
|
|
149
151
|
for key, value in model.items():
|
|
150
152
|
# keep previous entries
|
|
@@ -182,7 +184,7 @@ class Fixtures:
|
|
|
182
184
|
self.file_path = Path(__file__).parent.parent.resolve() / "data_models"
|
|
183
185
|
else:
|
|
184
186
|
self.file_path = file_path
|
|
185
|
-
self.components:
|
|
187
|
+
self.components: dict[str, Fixture] = {}
|
|
186
188
|
cache_file = cache_user_path / "fixtures.pickle"
|
|
187
189
|
sheep_detect = Path("/lib/firmware/am335x-pru0-fw").exists()
|
|
188
190
|
|
|
@@ -201,7 +203,10 @@ class Fixtures:
|
|
|
201
203
|
if self.file_path.is_file():
|
|
202
204
|
files = [self.file_path]
|
|
203
205
|
elif self.file_path.is_dir():
|
|
204
|
-
files =
|
|
206
|
+
files = list(
|
|
207
|
+
self.file_path.glob("**/*" + self.suffix)
|
|
208
|
+
) # for py>=3.12: case_sensitive=False
|
|
209
|
+
logger.debug(" -> got %s %s-files", len(files), self.suffix)
|
|
205
210
|
else:
|
|
206
211
|
raise ValueError("Path must either be file or directory (or empty)")
|
|
207
212
|
|
|
@@ -245,28 +250,3 @@ class Fixtures:
|
|
|
245
250
|
def to_file(file: Path) -> None:
|
|
246
251
|
msg = f"TODO (val={file})"
|
|
247
252
|
raise NotImplementedError(msg)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def get_files(start_path: Path, suffix: str, recursion_depth: int = 0) -> List[Path]:
|
|
251
|
-
"""Generate a recursive list of all files in a directory."""
|
|
252
|
-
if recursion_depth == 0:
|
|
253
|
-
suffix = suffix.lower().split(".")[-1]
|
|
254
|
-
dir_items = os.scandir(start_path)
|
|
255
|
-
recursion_depth += 1
|
|
256
|
-
files = []
|
|
257
|
-
|
|
258
|
-
for item in dir_items:
|
|
259
|
-
if item.is_dir():
|
|
260
|
-
files += get_files(Path(item.path), suffix, recursion_depth)
|
|
261
|
-
continue
|
|
262
|
-
|
|
263
|
-
item_name = str(item.name).lower()
|
|
264
|
-
item_ext = item_name.split(".")[-1]
|
|
265
|
-
if item_ext == suffix and item_ext != item_name:
|
|
266
|
-
files.append(Path(item.path))
|
|
267
|
-
if not suffix and item_ext == item_name:
|
|
268
|
-
files.append(Path(item.path))
|
|
269
|
-
|
|
270
|
-
if recursion_depth == 1 and len(files) > 0:
|
|
271
|
-
logger.debug(" -> got %s files with the suffix '%s'", len(files), suffix)
|
|
272
|
-
return files
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import secrets
|
|
4
4
|
from hashlib import pbkdf2_hmac
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from typing import Any
|
|
5
7
|
from typing import Optional
|
|
6
8
|
from typing import Union
|
|
7
9
|
from uuid import uuid4
|
|
@@ -14,11 +16,10 @@ from pydantic import SecretStr
|
|
|
14
16
|
from pydantic import StringConstraints
|
|
15
17
|
from pydantic import model_validator
|
|
16
18
|
from pydantic import validate_call
|
|
17
|
-
from typing_extensions import Annotated
|
|
18
19
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
20
|
+
from shepherd_core.data_models.base.content import NameStr
|
|
21
|
+
from shepherd_core.data_models.base.content import SafeStr
|
|
22
|
+
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@validate_call
|
|
@@ -63,7 +64,7 @@ class User(ShpModel):
|
|
|
63
64
|
|
|
64
65
|
@model_validator(mode="before")
|
|
65
66
|
@classmethod
|
|
66
|
-
def query_database(cls, values: dict) -> dict:
|
|
67
|
+
def query_database(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
67
68
|
# TODO:
|
|
68
69
|
|
|
69
70
|
# post correction
|
shepherd_core/version.py
CHANGED
|
@@ -79,9 +79,9 @@ class DiodeTarget(TargetABC):
|
|
|
79
79
|
|
|
80
80
|
def step(self, voltage_uV: int, *, pwr_good: bool) -> float:
|
|
81
81
|
if pwr_good or not self.ctrl:
|
|
82
|
-
V_CC = voltage_uV * 1e-6
|
|
83
|
-
V_D = V_CC / 2
|
|
84
|
-
I_R = I_D = 0
|
|
82
|
+
V_CC: float = voltage_uV * 1e-6
|
|
83
|
+
V_D: float = V_CC / 2
|
|
84
|
+
I_R = I_D = 0.0
|
|
85
85
|
# there is no direct formular, but this iteration converges fast
|
|
86
86
|
for _ in range(10):
|
|
87
87
|
# low voltages tend to produce log(x<0)=err
|
|
@@ -17,9 +17,9 @@ Compromises:
|
|
|
17
17
|
import math
|
|
18
18
|
from typing import Optional
|
|
19
19
|
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
20
|
+
from shepherd_core.data_models import CalibrationEmulator
|
|
21
|
+
from shepherd_core.data_models.content.virtual_source import LUT_SIZE
|
|
22
|
+
from shepherd_core.data_models.content.virtual_source import ConverterPRUConfig
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class PruCalibration:
|
|
@@ -16,10 +16,8 @@ Compromises:
|
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
from ..data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
22
|
-
from ..logger import logger
|
|
19
|
+
from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
20
|
+
from shepherd_core.logger import logger
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
class VirtualHarvesterModel:
|
|
@@ -94,7 +92,7 @@ class VirtualHarvesterModel:
|
|
|
94
92
|
self.voltage_nxt: int = 0
|
|
95
93
|
self.current_nxt: int = 0
|
|
96
94
|
|
|
97
|
-
def ivcurve_sample(self, _voltage_uV: int, _current_nA: int) ->
|
|
95
|
+
def ivcurve_sample(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
|
|
98
96
|
if self._cfg.window_size <= 1:
|
|
99
97
|
return _voltage_uV, _current_nA
|
|
100
98
|
if self._cfg.algorithm >= self.HRV_MPPT_OPT:
|
|
@@ -108,7 +106,7 @@ class VirtualHarvesterModel:
|
|
|
108
106
|
# next line is only implied in C
|
|
109
107
|
return _voltage_uV, _current_nA
|
|
110
108
|
|
|
111
|
-
def ivcurve_2_cv(self, _voltage_uV: int, _current_nA: int) ->
|
|
109
|
+
def ivcurve_2_cv(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
|
|
112
110
|
compare_now = _voltage_uV < self.voltage_set_uV
|
|
113
111
|
step_size_now = abs(_voltage_uV - self.voltage_last)
|
|
114
112
|
distance_now = abs(_voltage_uV - self.voltage_set_uV)
|
|
@@ -146,7 +144,7 @@ class VirtualHarvesterModel:
|
|
|
146
144
|
self.compare_last = compare_now
|
|
147
145
|
return self.voltage_hold, self.current_hold
|
|
148
146
|
|
|
149
|
-
def ivcurve_2_mppt_voc(self, _voltage_uV: int, _current_nA: int) ->
|
|
147
|
+
def ivcurve_2_mppt_voc(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
|
|
150
148
|
self.interval_step = self.interval_step + 1
|
|
151
149
|
if self.interval_step >= self._cfg.interval_n:
|
|
152
150
|
self.interval_step = 0
|
|
@@ -176,7 +174,7 @@ class VirtualHarvesterModel:
|
|
|
176
174
|
|
|
177
175
|
return _voltage_uV, _current_nA
|
|
178
176
|
|
|
179
|
-
def ivcurve_2_mppt_po(self, _voltage_uV: int, _current_nA: int) ->
|
|
177
|
+
def ivcurve_2_mppt_po(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
|
|
180
178
|
self.interval_step = self.interval_step + 1
|
|
181
179
|
if self.interval_step >= self._cfg.interval_n:
|
|
182
180
|
self.interval_step = 0
|
|
@@ -220,7 +218,7 @@ class VirtualHarvesterModel:
|
|
|
220
218
|
|
|
221
219
|
return _voltage_uV, _current_nA
|
|
222
220
|
|
|
223
|
-
def ivcurve_2_mppt_opt(self, _voltage_uV: int, _current_nA: int) ->
|
|
221
|
+
def ivcurve_2_mppt_opt(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
|
|
224
222
|
self.age_now += 1
|
|
225
223
|
self.age_nxt += 1
|
|
226
224
|
|
|
@@ -13,11 +13,12 @@ from typing import Optional
|
|
|
13
13
|
|
|
14
14
|
from tqdm import tqdm
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
16
|
+
from shepherd_core.data_models.base.calibration import CalibrationHarvester
|
|
17
|
+
from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
18
|
+
from shepherd_core.data_models.content.virtual_harvester import VirtualHarvesterConfig
|
|
19
|
+
from shepherd_core.reader import Reader
|
|
20
|
+
from shepherd_core.writer import Writer
|
|
21
|
+
|
|
21
22
|
from .virtual_harvester_model import VirtualHarvesterModel
|
|
22
23
|
|
|
23
24
|
|
|
@@ -12,11 +12,12 @@ NOTE: DO NOT OPTIMIZE -> stay close to original code-base
|
|
|
12
12
|
|
|
13
13
|
from typing import Optional
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
15
|
+
from shepherd_core.data_models.base.calibration import CalibrationEmulator
|
|
16
|
+
from shepherd_core.data_models.content.energy_environment import EnergyDType
|
|
17
|
+
from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
18
|
+
from shepherd_core.data_models.content.virtual_source import ConverterPRUConfig
|
|
19
|
+
from shepherd_core.data_models.content.virtual_source import VirtualSourceConfig
|
|
20
|
+
|
|
20
21
|
from .virtual_converter_model import PruCalibration
|
|
21
22
|
from .virtual_converter_model import VirtualConverterModel
|
|
22
23
|
from .virtual_harvester_model import VirtualHarvesterModel
|
|
@@ -14,13 +14,14 @@ from typing import Optional
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
from tqdm import tqdm
|
|
16
16
|
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
|
|
17
|
+
from shepherd_core.data_models.base.calibration import CalibrationEmulator
|
|
18
|
+
from shepherd_core.data_models.content.virtual_source import VirtualSourceConfig
|
|
19
|
+
from shepherd_core.logger import logger
|
|
20
|
+
from shepherd_core.reader import Reader
|
|
21
|
+
from shepherd_core.writer import Writer
|
|
22
|
+
|
|
23
23
|
from .target_model import TargetABC
|
|
24
|
+
from .virtual_source_model import VirtualSourceModel
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def simulate_source(
|
shepherd_core/writer.py
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import math
|
|
5
5
|
import pathlib
|
|
6
|
+
from collections.abc import Mapping
|
|
6
7
|
from datetime import timedelta
|
|
7
8
|
from itertools import product
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from types import TracebackType
|
|
10
11
|
from typing import Any
|
|
11
12
|
from typing import Optional
|
|
12
|
-
from typing import Type
|
|
13
13
|
from typing import Union
|
|
14
14
|
|
|
15
15
|
import h5py
|
|
@@ -17,11 +17,10 @@ import numpy as np
|
|
|
17
17
|
import yaml
|
|
18
18
|
from pydantic import validate_call
|
|
19
19
|
from typing_extensions import Self
|
|
20
|
-
from yaml import
|
|
20
|
+
from yaml import Node
|
|
21
21
|
from yaml import SafeDumper
|
|
22
|
-
from yaml import ScalarNode
|
|
23
22
|
|
|
24
|
-
from .commons import
|
|
23
|
+
from .commons import SAMPLERATE_SPS_DEFAULT
|
|
25
24
|
from .data_models.base.calibration import CalibrationEmulator as CalEmu
|
|
26
25
|
from .data_models.base.calibration import CalibrationHarvester as CalHrv
|
|
27
26
|
from .data_models.base.calibration import CalibrationSeries as CalSeries
|
|
@@ -33,13 +32,13 @@ from .reader import Reader
|
|
|
33
32
|
|
|
34
33
|
# copy of core/models/base/shepherd - needed also here
|
|
35
34
|
def path2str(
|
|
36
|
-
dumper:
|
|
37
|
-
) ->
|
|
35
|
+
dumper: SafeDumper, data: Union[pathlib.Path, pathlib.WindowsPath, pathlib.PosixPath]
|
|
36
|
+
) -> Node:
|
|
38
37
|
"""Add a yaml-representation for a specific datatype."""
|
|
39
38
|
return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
def time2int(dumper:
|
|
41
|
+
def time2int(dumper: SafeDumper, data: timedelta) -> Node:
|
|
43
42
|
"""Add a yaml-representation for a specific datatype."""
|
|
44
43
|
return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
|
|
45
44
|
|
|
@@ -92,11 +91,10 @@ class Writer(Reader):
|
|
|
92
91
|
|
|
93
92
|
"""
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
datatype_default: EnergyDType = EnergyDType.ivsample
|
|
94
|
+
MODE_DEFAULT: str = "harvester"
|
|
95
|
+
DATATYPE_DEFAULT: EnergyDType = EnergyDType.ivsample
|
|
98
96
|
|
|
99
|
-
|
|
97
|
+
_CHUNK_SHAPE: tuple = (Reader.BUFFER_SAMPLES_N,)
|
|
100
98
|
|
|
101
99
|
@validate_call
|
|
102
100
|
def __init__(
|
|
@@ -110,7 +108,7 @@ class Writer(Reader):
|
|
|
110
108
|
*,
|
|
111
109
|
modify_existing: bool = False,
|
|
112
110
|
force_overwrite: bool = False,
|
|
113
|
-
verbose:
|
|
111
|
+
verbose: bool = True,
|
|
114
112
|
) -> None:
|
|
115
113
|
self._modify = modify_existing
|
|
116
114
|
if compression is not None:
|
|
@@ -123,41 +121,42 @@ class Writer(Reader):
|
|
|
123
121
|
# -> logger gets configured in reader()
|
|
124
122
|
|
|
125
123
|
if self._modify or force_overwrite or not file_path.exists():
|
|
126
|
-
|
|
127
|
-
self._logger.info("Storing data to '%s'",
|
|
124
|
+
file_path = file_path.resolve()
|
|
125
|
+
self._logger.info("Storing data to '%s'", file_path)
|
|
128
126
|
elif file_path.exists() and not file_path.is_file():
|
|
129
127
|
msg = f"Path is not a file ({file_path})"
|
|
130
128
|
raise TypeError(msg)
|
|
131
129
|
else:
|
|
132
130
|
base_dir = file_path.resolve().parents[0]
|
|
133
|
-
|
|
131
|
+
file_path_new = unique_path(base_dir / file_path.stem, file_path.suffix)
|
|
134
132
|
self._logger.warning(
|
|
135
133
|
"File '%s' already exists -> storing under '%s' instead",
|
|
136
134
|
file_path,
|
|
137
|
-
|
|
135
|
+
file_path_new.name,
|
|
138
136
|
)
|
|
137
|
+
file_path = file_path_new
|
|
139
138
|
|
|
140
139
|
# open file
|
|
141
140
|
if self._modify:
|
|
142
|
-
self.h5file = h5py.File(
|
|
141
|
+
self.h5file = h5py.File(file_path, "r+") # = rw
|
|
143
142
|
else:
|
|
144
|
-
if not
|
|
145
|
-
|
|
146
|
-
self.h5file = h5py.File(
|
|
143
|
+
if not file_path.parent.exists():
|
|
144
|
+
file_path.parent.mkdir(parents=True)
|
|
145
|
+
self.h5file = h5py.File(file_path, "w")
|
|
147
146
|
# ⤷ write, truncate if exist
|
|
148
147
|
self._create_skeleton()
|
|
149
148
|
|
|
150
149
|
# Handle Mode
|
|
151
|
-
if isinstance(mode, str) and mode not in self.
|
|
152
|
-
msg = f"Can't handle mode '{mode}' (choose one of {self.
|
|
150
|
+
if isinstance(mode, str) and mode not in self.MODE_TO_DTYPE:
|
|
151
|
+
msg = f"Can't handle mode '{mode}' (choose one of {self.MODE_TO_DTYPE})"
|
|
153
152
|
raise ValueError(msg)
|
|
154
153
|
|
|
155
154
|
if mode is not None:
|
|
156
155
|
self.h5file.attrs["mode"] = mode
|
|
157
156
|
if "mode" not in self.h5file.attrs:
|
|
158
|
-
self.h5file.attrs["mode"] = self.
|
|
157
|
+
self.h5file.attrs["mode"] = self.MODE_DEFAULT
|
|
159
158
|
|
|
160
|
-
_dtypes = self.
|
|
159
|
+
_dtypes = self.MODE_TO_DTYPE[self.get_mode()]
|
|
161
160
|
|
|
162
161
|
# Handle Datatype
|
|
163
162
|
if isinstance(datatype, str):
|
|
@@ -169,7 +168,7 @@ class Writer(Reader):
|
|
|
169
168
|
if isinstance(datatype, EnergyDType):
|
|
170
169
|
self.h5file["data"].attrs["datatype"] = datatype.name
|
|
171
170
|
if "datatype" not in self.h5file["data"].attrs:
|
|
172
|
-
self.h5file["data"].attrs["datatype"] = self.
|
|
171
|
+
self.h5file["data"].attrs["datatype"] = self.DATATYPE_DEFAULT.name
|
|
173
172
|
if self.get_datatype() not in _dtypes:
|
|
174
173
|
msg = (
|
|
175
174
|
f"Can't handle value '{self.get_datatype()}' of datatype (choose one of {_dtypes})"
|
|
@@ -203,7 +202,7 @@ class Writer(Reader):
|
|
|
203
202
|
settings = list(self.h5file.id.get_access_plist().get_cache())
|
|
204
203
|
self._logger.debug("H5Py Cache_setting=%s (_mdc, _nslots, _nbytes, _w0)", settings)
|
|
205
204
|
|
|
206
|
-
super().__init__(file_path=
|
|
205
|
+
super().__init__(file_path=file_path, verbose=verbose)
|
|
207
206
|
|
|
208
207
|
def __enter__(self) -> Self:
|
|
209
208
|
super().__enter__()
|
|
@@ -211,7 +210,7 @@ class Writer(Reader):
|
|
|
211
210
|
|
|
212
211
|
def __exit__(
|
|
213
212
|
self,
|
|
214
|
-
typ: Optional[
|
|
213
|
+
typ: Optional[type[BaseException]] = None,
|
|
215
214
|
exc: Optional[BaseException] = None,
|
|
216
215
|
tb: Optional[TracebackType] = None,
|
|
217
216
|
extra_arg: int = 0,
|
|
@@ -250,7 +249,7 @@ class Writer(Reader):
|
|
|
250
249
|
(0,),
|
|
251
250
|
dtype="u8",
|
|
252
251
|
maxshape=(None,),
|
|
253
|
-
chunks=self.
|
|
252
|
+
chunks=self._CHUNK_SHAPE,
|
|
254
253
|
compression=self._compression,
|
|
255
254
|
)
|
|
256
255
|
grp_data["time"].attrs["unit"] = "s"
|
|
@@ -261,7 +260,7 @@ class Writer(Reader):
|
|
|
261
260
|
(0,),
|
|
262
261
|
dtype="u4",
|
|
263
262
|
maxshape=(None,),
|
|
264
|
-
chunks=self.
|
|
263
|
+
chunks=self._CHUNK_SHAPE,
|
|
265
264
|
compression=self._compression,
|
|
266
265
|
)
|
|
267
266
|
grp_data["current"].attrs["unit"] = "A"
|
|
@@ -272,7 +271,7 @@ class Writer(Reader):
|
|
|
272
271
|
(0,),
|
|
273
272
|
dtype="u4",
|
|
274
273
|
maxshape=(None,),
|
|
275
|
-
chunks=self.
|
|
274
|
+
chunks=self._CHUNK_SHAPE,
|
|
276
275
|
compression=self._compression,
|
|
277
276
|
)
|
|
278
277
|
grp_data["voltage"].attrs["unit"] = "V"
|
|
@@ -346,10 +345,10 @@ class Writer(Reader):
|
|
|
346
345
|
def _align(self) -> None:
|
|
347
346
|
"""Align datasets with buffer-size of shepherd."""
|
|
348
347
|
self._refresh_file_stats()
|
|
349
|
-
n_buff = self.ds_voltage.size / self.
|
|
350
|
-
size_new = int(math.floor(n_buff) * self.
|
|
348
|
+
n_buff = self.ds_voltage.size / self.BUFFER_SAMPLES_N
|
|
349
|
+
size_new = int(math.floor(n_buff) * self.BUFFER_SAMPLES_N)
|
|
351
350
|
if size_new < self.ds_voltage.size:
|
|
352
|
-
if self.samplerate_sps !=
|
|
351
|
+
if self.samplerate_sps != SAMPLERATE_SPS_DEFAULT:
|
|
353
352
|
self._logger.debug("skipped alignment due to altered samplerate")
|
|
354
353
|
return
|
|
355
354
|
self._logger.info(
|
|
@@ -364,7 +363,7 @@ class Writer(Reader):
|
|
|
364
363
|
"""Conveniently store relevant key-value data (attribute) in H5-structure."""
|
|
365
364
|
self.h5file.attrs.__setitem__(key, item)
|
|
366
365
|
|
|
367
|
-
def store_config(self, data:
|
|
366
|
+
def store_config(self, data: Mapping) -> None:
|
|
368
367
|
"""Get a better self-describing Output-File.
|
|
369
368
|
|
|
370
369
|
TODO: use data-model?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: shepherd_core
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.4.2
|
|
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.8
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.9
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -27,7 +26,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
27
26
|
Classifier: License :: OSI Approved :: MIT License
|
|
28
27
|
Classifier: Operating System :: OS Independent
|
|
29
28
|
Classifier: Natural Language :: English
|
|
30
|
-
Requires-Python: >=3.
|
|
29
|
+
Requires-Python: >=3.9
|
|
31
30
|
Description-Content-Type: text/markdown
|
|
32
31
|
Requires-Dist: h5py
|
|
33
32
|
Requires-Dist: numpy
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
shepherd_core/__init__.py,sha256=fCld2mcl0y0h6kRyPal3DP-sWXnKl_0aYWYBdg4QuUk,1270
|
|
2
|
+
shepherd_core/calibration_hw_def.py,sha256=aL94bA1Sf14L5A3PLdVvQVYtGi28S4NUWA65wbim8bw,2895
|
|
3
|
+
shepherd_core/commons.py,sha256=rTxtndtiJ4cOHYRPRbdZdqp6T90CKFFN-I-YAFzhm4Q,200
|
|
4
|
+
shepherd_core/logger.py,sha256=i8j3alm8icAx_h1_IZ6SgVgC9W5J7S-bDc31mPDWl-w,1812
|
|
5
|
+
shepherd_core/reader.py,sha256=Xu_MSADsR4Xzh8YH-N50mKIHZlhPyFigv8umQSsYsJ0,28320
|
|
6
|
+
shepherd_core/version.py,sha256=dd_uzGz0uQwh1pttFJSjXzIKovnBzMAXJGgpRa5QR34,76
|
|
7
|
+
shepherd_core/writer.py,sha256=HRiCnJRM4hIBT6UBZJZ5ZOZ79OyBpBN4a2kX-XmOYRE,14574
|
|
8
|
+
shepherd_core/data_models/__init__.py,sha256=bnHSP_HBOYm4WuoiHs_vyiRsWlvkyDjsarMNfKedN-Q,1836
|
|
9
|
+
shepherd_core/data_models/readme.md,sha256=DHPVmkWqDksWomRHRTVWVHy9wXF9oMJrITgKs4Pnz2g,2494
|
|
10
|
+
shepherd_core/data_models/virtual_source_doc.txt,sha256=KizMcfGKj7BnHIbaJHT7KeTF01SV__UXv01qV_DGHSs,6057
|
|
11
|
+
shepherd_core/data_models/base/__init__.py,sha256=PSJ6acWViqBm0Eiom8DIgKfFVrp5lzYr8OsDvP79vwI,94
|
|
12
|
+
shepherd_core/data_models/base/cal_measurement.py,sha256=c-vjACNxsQY8LU2Msw0COrsTY-8pToJ5ZWkOztJ9tVY,3380
|
|
13
|
+
shepherd_core/data_models/base/calibration.py,sha256=oUTfY6iUWUbBbOw5aMCRkdEfHzIV8aUrhwqek0QzPJM,10849
|
|
14
|
+
shepherd_core/data_models/base/content.py,sha256=t3hw5Aes4-tVrWGOMSBGpcD642VM1RAWIo9jtbXifd0,2452
|
|
15
|
+
shepherd_core/data_models/base/shepherd.py,sha256=P1xW10_vIbGNyQ3Ary67vyovw9kmDtXhN-yPFYR-0Is,6085
|
|
16
|
+
shepherd_core/data_models/base/timezone.py,sha256=2T6E46hJ1DAvmqKfu6uIgCK3RSoAKjGXRyzYNaqKyjY,665
|
|
17
|
+
shepherd_core/data_models/base/wrapper.py,sha256=7QwvI30GuORH7WmyGLnRMsZ3xkRRkXIAvZ-pYRAL-WI,755
|
|
18
|
+
shepherd_core/data_models/content/__init__.py,sha256=69aiNG0h5t1OF7HsLg_ke5eaQKsKyMK8o6Kfaby5vlY,525
|
|
19
|
+
shepherd_core/data_models/content/_external_fixtures.yaml,sha256=0CH7YSWT_hzL-jcg4JjgN9ryQOzbS8S66_pd6GbMnHw,12259
|
|
20
|
+
shepherd_core/data_models/content/energy_environment.py,sha256=emlVFbcPA0-_jgpYI0Xwebm81_gWLYD8Tex0XVtJr4c,1618
|
|
21
|
+
shepherd_core/data_models/content/energy_environment_fixture.yaml,sha256=UBXTdGT7MK98zx5w_RBCu-f9uNCKxRgiFBQFbmDUxPc,1301
|
|
22
|
+
shepherd_core/data_models/content/firmware.py,sha256=KLByo1GuUMJ8ZbM6WccB8IJArorNetBxqOpuUb2_QIE,5926
|
|
23
|
+
shepherd_core/data_models/content/firmware_datatype.py,sha256=XPU9LOoT3h5qFOlE8WU0vAkw-vymNxzor9kVFyEqsWg,255
|
|
24
|
+
shepherd_core/data_models/content/virtual_harvester.py,sha256=PQ3CykoMgMoklmb_vypRdU512BuVhV-KjPTmBPKTzCM,11227
|
|
25
|
+
shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=LZe5ue1xYhXZwB3a32sva-L4uKhkQA5AtG9JzW4B2hQ,4564
|
|
26
|
+
shepherd_core/data_models/content/virtual_source.py,sha256=ftCcWCYKHw854lGwuxLf_ZIOY6O9vLixHHC77BqcSQA,15441
|
|
27
|
+
shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=1o-31mGgn7eyCNidKoOUp9vZh3K4Al0kJgmz54Q2DAE,11191
|
|
28
|
+
shepherd_core/data_models/experiment/__init__.py,sha256=lorsx0M-JWPIrt_UZfexsLwaITv5slFb3krBOt0idm8,618
|
|
29
|
+
shepherd_core/data_models/experiment/experiment.py,sha256=h24HaZltat0mKbOT1h9O58B5noTIUcGQbBIzmPi4oo8,4232
|
|
30
|
+
shepherd_core/data_models/experiment/observer_features.py,sha256=u6nJthpluZrxw9ym_rvkTrlOQTKlc1TY5b1HPqbO4Jg,5185
|
|
31
|
+
shepherd_core/data_models/experiment/target_config.py,sha256=ZODI4_MwBLxKTQ1DwFrLZAuKprxDiLyg5-JYOQxXp_w,3808
|
|
32
|
+
shepherd_core/data_models/task/__init__.py,sha256=3wTIJ1L5wcRDLR6JRaT0bNh7vABckSoOd5erPwhhCGI,3331
|
|
33
|
+
shepherd_core/data_models/task/emulation.py,sha256=PfSWci9V47EWpJRTKyHBxQZodHs3yQXMce0Mslm_-Ns,6665
|
|
34
|
+
shepherd_core/data_models/task/firmware_mod.py,sha256=WLmevU9-Q5QnyANbkrSCdFbnVuSi8N8fXdQ2l_Um9e8,3034
|
|
35
|
+
shepherd_core/data_models/task/harvest.py,sha256=rQ5Aq40m4WHpEFfiwn309GjLIPu4aKzF4Fvbo4j7PLs,3470
|
|
36
|
+
shepherd_core/data_models/task/observer_tasks.py,sha256=icUPtoIqic8IpE0U9Fc7btMj4CpYx1PoAu_wAqqZkU8,3608
|
|
37
|
+
shepherd_core/data_models/task/programming.py,sha256=6UHb8rT1M3bglt_3fHql6MsHE8sTvi9ii7Uhug5O4PM,2552
|
|
38
|
+
shepherd_core/data_models/task/testbed_tasks.py,sha256=J3aOSVVG1KvEI_83j0TK0gc5rN_v_T-URFRhrEs8czY,2176
|
|
39
|
+
shepherd_core/data_models/testbed/__init__.py,sha256=t9nwml5pbu7ZWghimOyZ8ujMIgnRgFkl23pNb5d_KdU,581
|
|
40
|
+
shepherd_core/data_models/testbed/cape.py,sha256=vfS05D0rC1-_wMiHeLw69VE9PxXC6PHl9ndtrv219_k,1396
|
|
41
|
+
shepherd_core/data_models/testbed/cape_fixture.yaml,sha256=uwZxe6hsqvofn5tzg4sffjbVtTVUkextL1GCri_z2A4,2197
|
|
42
|
+
shepherd_core/data_models/testbed/gpio.py,sha256=pOY_7Zs32sRGuqJZrZDrjvr69QkFXC8iXgav7kk2heY,2334
|
|
43
|
+
shepherd_core/data_models/testbed/gpio_fixture.yaml,sha256=yXvoXAau2hancKi2yg1xIkErPWQa6gIxNUG3y8JuF9Y,3076
|
|
44
|
+
shepherd_core/data_models/testbed/mcu.py,sha256=fuq2AWbVFbbzPRPCgIeMNFhJhVNCIsmpjFagnOXkjbY,1514
|
|
45
|
+
shepherd_core/data_models/testbed/mcu_fixture.yaml,sha256=lRZMLs27cTeERSFGkbMt5xgxbn11Gh9G1mQqOZK136I,522
|
|
46
|
+
shepherd_core/data_models/testbed/observer.py,sha256=RO7i9TmHcxx69P-EHN59M9MAJetoHZg5qJUD5SEtrcg,3386
|
|
47
|
+
shepherd_core/data_models/testbed/observer_fixture.yaml,sha256=jqAatTebWrShXBlhqkCUQIrtVqEjl7RVDR9mosS2LJQ,4807
|
|
48
|
+
shepherd_core/data_models/testbed/target.py,sha256=W7U9nCz_xWMRCCgaGuqOV4dGi4ZeQ1SAp7W2FqCkanU,1978
|
|
49
|
+
shepherd_core/data_models/testbed/target_fixture.old1,sha256=ivH9uTgC2Z4L_J4KDHAyIHZnB7iy9EUu1yM3M0s1lQQ,3675
|
|
50
|
+
shepherd_core/data_models/testbed/target_fixture.yaml,sha256=LyOJa7yH17tHIGC25jlkLJ_DKnbFSoGhD-6Uh6q_HqQ,4132
|
|
51
|
+
shepherd_core/data_models/testbed/testbed.py,sha256=K6llqMTn-AU18XXRXdhzibIx-n4CQjtrXnLwqxOQesI,3828
|
|
52
|
+
shepherd_core/data_models/testbed/testbed_fixture.yaml,sha256=LaaU8mXLQboYWYNPpA3CWmMPy2w6T6cve6gLgDaA3l0,717
|
|
53
|
+
shepherd_core/decoder_waveform/__init__.py,sha256=-ohGz0fA2tKxUJk4FAQXKtI93d6YGdy0CrkdhOod1QU,120
|
|
54
|
+
shepherd_core/decoder_waveform/uart.py,sha256=vuw9fKwZb_1mMtQ5fdiZN8Cr1YWSgYKar9FIMK8Bogo,11084
|
|
55
|
+
shepherd_core/fw_tools/__init__.py,sha256=D9GGj9TzLWZfPjG_iV2BsF-Q1TGTYTgEzWTUI5ReVAA,2090
|
|
56
|
+
shepherd_core/fw_tools/converter.py,sha256=F06iJlYYG2w-OCKayrgeO870lOX87xWVTYTAi1X3UPU,3604
|
|
57
|
+
shepherd_core/fw_tools/converter_elf.py,sha256=GQDVqIqMW4twNMvZIV3sowFMezhs2TN-IYREjRP7Xt4,1089
|
|
58
|
+
shepherd_core/fw_tools/patcher.py,sha256=ISbnA2ZLTIV2JZh_b3GorNDGNrLaBN0fFnFnyh-smNU,3946
|
|
59
|
+
shepherd_core/fw_tools/validation.py,sha256=GWm_T3xmRde-jQ8cf27kPosBTFTK3E2M2LSb24pT2g8,4732
|
|
60
|
+
shepherd_core/inventory/__init__.py,sha256=yQxP55yV61xXWfZSSzekQQYopPZCspFpHSyG7VTqtpg,3819
|
|
61
|
+
shepherd_core/inventory/python.py,sha256=pvugNgLZaDllIXX_KiuvpcWUWlJtD2IUKYDRjcTGQss,1262
|
|
62
|
+
shepherd_core/inventory/system.py,sha256=qFTRVXClQZoCeV2KM8RAbst8XL7IaJwwV0pGHPZSJs4,3325
|
|
63
|
+
shepherd_core/inventory/target.py,sha256=zLUNQs2FE7jMDsiRtbeAwqRVcit4e2F1UUOF04xw-XY,520
|
|
64
|
+
shepherd_core/testbed_client/__init__.py,sha256=QtbsBUzHwOoM6rk0qa21ywuz63YV7af1fwUtWW8Vg_4,234
|
|
65
|
+
shepherd_core/testbed_client/cache_path.py,sha256=tS0er9on5fw8wddMCt1jkc2uyYOdSTvX_UmfmYJf6tY,445
|
|
66
|
+
shepherd_core/testbed_client/client_abc_fix.py,sha256=3oDg2RSEP88mygtX820Y-685kYKvqQK_hwp2iFylLUU,4265
|
|
67
|
+
shepherd_core/testbed_client/client_web.py,sha256=zqY4MGMWfTl2_0T1qrQl5Vz9SPjl-wMj8O5yMMQyo9I,6044
|
|
68
|
+
shepherd_core/testbed_client/fixtures.py,sha256=OyL50wEuJ8336RPtinxOFbUsIVtLrgyO2_ROOyiBTEE,9317
|
|
69
|
+
shepherd_core/testbed_client/user_model.py,sha256=f4WZ8IvSCt3s1RG_Bhi43ojiJQsZYyolJ3Ft8HNRYas,2175
|
|
70
|
+
shepherd_core/vsource/__init__.py,sha256=vTvFWuJn4eurPNzEiMd15c1Rd6o3DTWzCfbhOomflZU,771
|
|
71
|
+
shepherd_core/vsource/target_model.py,sha256=BjOlwX_gIOJ91e4OOLB4_OsCpuhq9vm57ERjM-iBhAM,5129
|
|
72
|
+
shepherd_core/vsource/virtual_converter_model.py,sha256=jUnJwP-FFDMtXm1NCLUJfZTvImYH4_A9rc_lXVAZ33I,11628
|
|
73
|
+
shepherd_core/vsource/virtual_harvester_model.py,sha256=8uPTMDth4KI5XarkX9EJtMRZ_8eWUeWCZxgmx4be3Fs,9763
|
|
74
|
+
shepherd_core/vsource/virtual_harvester_simulation.py,sha256=H4m9lB4Hk_sskGi3LoFYARBpq1uY3WsJj-ktF3nk2Ck,2558
|
|
75
|
+
shepherd_core/vsource/virtual_source_model.py,sha256=-8RwBrkIdO0g4zpo7XHnqv8F_qNh_qf5hxEUJoIuAmg,3164
|
|
76
|
+
shepherd_core/vsource/virtual_source_simulation.py,sha256=6FKbaO-zaODp0nf-ksnl-nRAVpFKGQrpxpIBrGIhD0I,5284
|
|
77
|
+
shepherd_core-2025.4.2.dist-info/METADATA,sha256=SD_1RcpaF_Pb-IbG2uAh7xkWHJv8TOaqy8c0zQI7Q1o,7756
|
|
78
|
+
shepherd_core-2025.4.2.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
|
79
|
+
shepherd_core-2025.4.2.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
|
|
80
|
+
shepherd_core-2025.4.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
81
|
+
shepherd_core-2025.4.2.dist-info/RECORD,,
|