shepherd-core 2025.2.2__py3-none-any.whl → 2025.4.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.
- shepherd_core/data_models/base/calibration.py +6 -4
- shepherd_core/data_models/content/virtual_source.py +1 -1
- shepherd_core/data_models/experiment/experiment.py +3 -2
- shepherd_core/data_models/readme.md +2 -1
- shepherd_core/inventory/__init__.py +2 -2
- shepherd_core/reader.py +9 -7
- shepherd_core/testbed_client/fixtures.py +6 -29
- shepherd_core/version.py +1 -1
- shepherd_core/writer.py +2 -1
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.1.dist-info}/METADATA +2 -2
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.1.dist-info}/RECORD +14 -14
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.1.dist-info}/WHEEL +1 -1
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.1.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.2.2.dist-info → shepherd_core-2025.4.1.dist-info}/zip-safe +0 -0
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import struct
|
|
4
4
|
from typing import Callable
|
|
5
5
|
from typing import Generator
|
|
6
|
+
from typing import Mapping
|
|
6
7
|
from typing import Optional
|
|
8
|
+
from typing import Sequence
|
|
7
9
|
from typing import TypeVar
|
|
8
10
|
from typing import Union
|
|
9
11
|
|
|
@@ -26,18 +28,18 @@ Calc_t = TypeVar("Calc_t", NDArray[np.float64], float)
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def dict_generator(
|
|
29
|
-
in_dict: Union[
|
|
31
|
+
in_dict: Union[Mapping, Sequence], pre: Optional[list] = None
|
|
30
32
|
) -> Generator[list, None, None]:
|
|
31
33
|
"""Recursive helper-function to generate a 1D-List(or not?).
|
|
32
34
|
|
|
33
35
|
TODO: isn't that a 1D-List generator?
|
|
34
36
|
"""
|
|
35
37
|
pre = pre[:] if pre else []
|
|
36
|
-
if isinstance(in_dict,
|
|
38
|
+
if isinstance(in_dict, Mapping):
|
|
37
39
|
for key, value in in_dict.items():
|
|
38
|
-
if isinstance(value,
|
|
40
|
+
if isinstance(value, Mapping):
|
|
39
41
|
yield from dict_generator(value, [*pre, key])
|
|
40
|
-
elif isinstance(value,
|
|
42
|
+
elif isinstance(value, Sequence):
|
|
41
43
|
for v in value:
|
|
42
44
|
yield from dict_generator(v, [*pre, key])
|
|
43
45
|
else:
|
|
@@ -346,7 +346,7 @@ class ConverterPRUConfig(ShpModel):
|
|
|
346
346
|
[min(255, round(256 * ival)) for ival in il] for il in data.LUT_input_efficiency
|
|
347
347
|
],
|
|
348
348
|
LUT_out_inv_efficiency_n4=[
|
|
349
|
-
min((2**14), round((2**4) / value)) if (value > 0) else
|
|
349
|
+
min((2**14), round((2**4) / value)) if (value > 0) else (2**14)
|
|
350
350
|
for value in data.LUT_output_efficiency
|
|
351
351
|
],
|
|
352
352
|
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from datetime import timedelta
|
|
5
|
+
from typing import Iterable
|
|
5
6
|
from typing import List
|
|
6
7
|
from typing import Optional
|
|
7
8
|
from typing import Union
|
|
@@ -68,7 +69,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
68
69
|
return self
|
|
69
70
|
|
|
70
71
|
@staticmethod
|
|
71
|
-
def _validate_targets(configs:
|
|
72
|
+
def _validate_targets(configs: Iterable[TargetConfig]) -> None:
|
|
72
73
|
target_ids = []
|
|
73
74
|
custom_ids = []
|
|
74
75
|
for _config in configs:
|
|
@@ -86,7 +87,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
86
87
|
raise ValueError("Custom Target-ID are faulty (some form of id-collisions)!")
|
|
87
88
|
|
|
88
89
|
@staticmethod
|
|
89
|
-
def _validate_observers(configs:
|
|
90
|
+
def _validate_observers(configs: Iterable[TargetConfig], testbed: Testbed) -> None:
|
|
90
91
|
target_ids = [_id for _config in configs for _id in _config.target_IDs]
|
|
91
92
|
obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
|
|
92
93
|
if len(target_ids) > len(set(obs_ids)):
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
- these do not work
|
|
47
47
|
|
|
48
48
|
```Python
|
|
49
|
+
from typing import Dict
|
|
49
50
|
from pydantic import Field
|
|
50
51
|
from shepherd_core.data_models import ShpModel
|
|
51
52
|
|
|
@@ -54,7 +55,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
54
55
|
super().__init__()
|
|
55
56
|
self.Config.fields["output_path"].description = "test description"
|
|
56
57
|
class Config:
|
|
57
|
-
fields:
|
|
58
|
+
fields: Dict[str, Field] = {}
|
|
58
59
|
fields["output_path"] = Field(description="test description")
|
|
59
60
|
```
|
|
60
61
|
|
|
@@ -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
|
|
86
|
-
# to
|
|
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
|
shepherd_core/reader.py
CHANGED
|
@@ -194,6 +194,7 @@ class Reader:
|
|
|
194
194
|
self,
|
|
195
195
|
start_n: int = 0,
|
|
196
196
|
end_n: Optional[int] = None,
|
|
197
|
+
n_samples_per_buffer: Optional[int] = None,
|
|
197
198
|
*,
|
|
198
199
|
is_raw: bool = False,
|
|
199
200
|
omit_ts: bool = False,
|
|
@@ -201,27 +202,28 @@ class Reader:
|
|
|
201
202
|
"""Read the specified range of buffers from the hdf5 file.
|
|
202
203
|
|
|
203
204
|
Generator - can be configured on first call
|
|
204
|
-
TODO: reconstruct - start/end mark samples &
|
|
205
|
-
each call can request a certain number of samples.
|
|
206
205
|
|
|
207
206
|
Args:
|
|
208
207
|
----
|
|
209
208
|
:param start_n: (int) Index of first buffer to be read
|
|
210
209
|
:param end_n: (int) Index of last buffer to be read
|
|
210
|
+
:param n_samples_per_buffer: (int) allows changing
|
|
211
211
|
:param is_raw: (bool) output original data, not transformed to SI-Units
|
|
212
212
|
:param omit_ts: (bool) optimize reading if timestamp is never used
|
|
213
213
|
Yields: Buffers between start and end (tuple with time, voltage, current)
|
|
214
214
|
|
|
215
215
|
"""
|
|
216
|
-
if
|
|
217
|
-
|
|
216
|
+
if n_samples_per_buffer is None:
|
|
217
|
+
n_samples_per_buffer = self.samples_per_buffer
|
|
218
|
+
end_max = int(self.ds_voltage.shape[0] // n_samples_per_buffer)
|
|
219
|
+
end_n = end_max if end_n is None else min(end_n, end_max)
|
|
218
220
|
self._logger.debug("Reading blocks %d to %d from source-file", start_n, end_n)
|
|
219
221
|
_raw = is_raw
|
|
220
222
|
_wts = not omit_ts
|
|
221
223
|
|
|
222
224
|
for i in range(start_n, end_n):
|
|
223
|
-
idx_start = i *
|
|
224
|
-
idx_end = idx_start +
|
|
225
|
+
idx_start = i * n_samples_per_buffer
|
|
226
|
+
idx_end = idx_start + n_samples_per_buffer
|
|
225
227
|
if _raw:
|
|
226
228
|
yield (
|
|
227
229
|
self.ds_time[idx_start:idx_end] if _wts else None,
|
|
@@ -673,7 +675,7 @@ class Reader:
|
|
|
673
675
|
return data != data_1
|
|
674
676
|
|
|
675
677
|
def gpio_to_waveforms(self, name: Optional[str] = None) -> dict:
|
|
676
|
-
waveforms:
|
|
678
|
+
waveforms: Dict[str, np.ndarray] = {}
|
|
677
679
|
if "gpio" not in self.h5file:
|
|
678
680
|
return waveforms
|
|
679
681
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""Current implementation of a file-based database."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
-
import os
|
|
5
4
|
import pickle
|
|
6
5
|
from datetime import datetime
|
|
7
6
|
from datetime import timedelta
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from typing import Any
|
|
10
9
|
from typing import Dict
|
|
11
|
-
from typing import
|
|
10
|
+
from typing import Mapping
|
|
12
11
|
from typing import Optional
|
|
13
12
|
from typing import Union
|
|
14
13
|
|
|
@@ -144,7 +143,7 @@ class Fixture:
|
|
|
144
143
|
return values, chain
|
|
145
144
|
|
|
146
145
|
@staticmethod
|
|
147
|
-
def fill_model(model:
|
|
146
|
+
def fill_model(model: Mapping, base: dict) -> dict:
|
|
148
147
|
base = copy.copy(base)
|
|
149
148
|
for key, value in model.items():
|
|
150
149
|
# keep previous entries
|
|
@@ -201,7 +200,10 @@ class Fixtures:
|
|
|
201
200
|
if self.file_path.is_file():
|
|
202
201
|
files = [self.file_path]
|
|
203
202
|
elif self.file_path.is_dir():
|
|
204
|
-
files =
|
|
203
|
+
files = list(
|
|
204
|
+
self.file_path.glob("**/*" + self.suffix)
|
|
205
|
+
) # for py>=3.12: case_sensitive=False
|
|
206
|
+
logger.debug(" -> got %s %s-files", len(files), self.suffix)
|
|
205
207
|
else:
|
|
206
208
|
raise ValueError("Path must either be file or directory (or empty)")
|
|
207
209
|
|
|
@@ -245,28 +247,3 @@ class Fixtures:
|
|
|
245
247
|
def to_file(file: Path) -> None:
|
|
246
248
|
msg = f"TODO (val={file})"
|
|
247
249
|
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
|
shepherd_core/version.py
CHANGED
shepherd_core/writer.py
CHANGED
|
@@ -8,6 +8,7 @@ from itertools import product
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from types import TracebackType
|
|
10
10
|
from typing import Any
|
|
11
|
+
from typing import Mapping
|
|
11
12
|
from typing import Optional
|
|
12
13
|
from typing import Type
|
|
13
14
|
from typing import Union
|
|
@@ -364,7 +365,7 @@ class Writer(Reader):
|
|
|
364
365
|
"""Conveniently store relevant key-value data (attribute) in H5-structure."""
|
|
365
366
|
self.h5file.attrs.__setitem__(key, item)
|
|
366
367
|
|
|
367
|
-
def store_config(self, data:
|
|
368
|
+
def store_config(self, data: Mapping) -> None:
|
|
368
369
|
"""Get a better self-describing Output-File.
|
|
369
370
|
|
|
370
371
|
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.1
|
|
4
4
|
Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
|
|
5
5
|
Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
@@ -2,15 +2,15 @@ shepherd_core/__init__.py,sha256=fCld2mcl0y0h6kRyPal3DP-sWXnKl_0aYWYBdg4QuUk,127
|
|
|
2
2
|
shepherd_core/calibration_hw_def.py,sha256=_nMzgNzSnYyqcLnVCGd4tfA2e0avUXbccjmNpFhiDgo,2830
|
|
3
3
|
shepherd_core/commons.py,sha256=vymKXWcy_1bz7ChzzEATUkJ4p3czCzjIdsSehVjJOY8,218
|
|
4
4
|
shepherd_core/logger.py,sha256=4Q4hTI-nccOZ1_A68fo4UEctfu3pJx3IeHfa9VuDDEo,1804
|
|
5
|
-
shepherd_core/reader.py,sha256=
|
|
6
|
-
shepherd_core/version.py,sha256=
|
|
7
|
-
shepherd_core/writer.py,sha256=
|
|
5
|
+
shepherd_core/reader.py,sha256=tgETZPFSPzBk2BYHZ3NyWpY9j6j23FUWv1e3oCQhc6A,28401
|
|
6
|
+
shepherd_core/version.py,sha256=Tb2EjULatpKtVFMAniZ_AGEWDFfyFO0ffsW-t24FgFU,76
|
|
7
|
+
shepherd_core/writer.py,sha256=TD5oXuOFz_PUMWYeXfePz9x2O01DLRBVNMaLcngNjFc,14666
|
|
8
8
|
shepherd_core/data_models/__init__.py,sha256=bnHSP_HBOYm4WuoiHs_vyiRsWlvkyDjsarMNfKedN-Q,1836
|
|
9
|
-
shepherd_core/data_models/readme.md,sha256=
|
|
9
|
+
shepherd_core/data_models/readme.md,sha256=DHPVmkWqDksWomRHRTVWVHy9wXF9oMJrITgKs4Pnz2g,2494
|
|
10
10
|
shepherd_core/data_models/virtual_source_doc.txt,sha256=KizMcfGKj7BnHIbaJHT7KeTF01SV__UXv01qV_DGHSs,6057
|
|
11
11
|
shepherd_core/data_models/base/__init__.py,sha256=PSJ6acWViqBm0Eiom8DIgKfFVrp5lzYr8OsDvP79vwI,94
|
|
12
12
|
shepherd_core/data_models/base/cal_measurement.py,sha256=YScPG7QLynbUHdjcznYqU8O5KRh0XiROGxGSk9BETMk,3357
|
|
13
|
-
shepherd_core/data_models/base/calibration.py,sha256
|
|
13
|
+
shepherd_core/data_models/base/calibration.py,sha256=-eiN9QbuZhw1CLzmyijeMgMVzzDE85p-Q5KryFoo5x8,10788
|
|
14
14
|
shepherd_core/data_models/base/content.py,sha256=13j7GSgT73xn27jgDP508thUEJR4U-nCb5n7CJ50c9Y,2463
|
|
15
15
|
shepherd_core/data_models/base/shepherd.py,sha256=DNrx59o1VBuy_liJuUzZRzmTTYB73D_pUWiNyMQyjYY,6112
|
|
16
16
|
shepherd_core/data_models/base/timezone.py,sha256=2T6E46hJ1DAvmqKfu6uIgCK3RSoAKjGXRyzYNaqKyjY,665
|
|
@@ -23,10 +23,10 @@ shepherd_core/data_models/content/firmware.py,sha256=rH74sJPAjUUheVv0y2Y93BeT63C
|
|
|
23
23
|
shepherd_core/data_models/content/firmware_datatype.py,sha256=XPU9LOoT3h5qFOlE8WU0vAkw-vymNxzor9kVFyEqsWg,255
|
|
24
24
|
shepherd_core/data_models/content/virtual_harvester.py,sha256=cf-DrSacZsct9e8eGoR4PX7S7RXlbxQfJHVsLR9njlM,10749
|
|
25
25
|
shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=LZe5ue1xYhXZwB3a32sva-L4uKhkQA5AtG9JzW4B2hQ,4564
|
|
26
|
-
shepherd_core/data_models/content/virtual_source.py,sha256=
|
|
26
|
+
shepherd_core/data_models/content/virtual_source.py,sha256=EnkjLlh14YBUnoS4yILXraLN0qdnVoEzddJY0N1w3xk,15351
|
|
27
27
|
shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=1o-31mGgn7eyCNidKoOUp9vZh3K4Al0kJgmz54Q2DAE,11191
|
|
28
28
|
shepherd_core/data_models/experiment/__init__.py,sha256=lorsx0M-JWPIrt_UZfexsLwaITv5slFb3krBOt0idm8,618
|
|
29
|
-
shepherd_core/data_models/experiment/experiment.py,sha256=
|
|
29
|
+
shepherd_core/data_models/experiment/experiment.py,sha256=79iHh2Dr_w4N4Bfq_g0KDr6wMpS_T7x-JIGHZCagEGo,4080
|
|
30
30
|
shepherd_core/data_models/experiment/observer_features.py,sha256=qxnb7anuQz9ZW5IUlPdUXYPIl5U7O9uXkJqZtMnAb0Y,5156
|
|
31
31
|
shepherd_core/data_models/experiment/target_config.py,sha256=XIsjbbo7yn_A4q3GMxWbiNzEGA0Kk5gH7-XfQQ7Kg0E,3674
|
|
32
32
|
shepherd_core/data_models/task/__init__.py,sha256=vOsSeB5IBYYwzOLKom8-XuZyjF86m_svtCsw-agiV7o,3295
|
|
@@ -57,7 +57,7 @@ shepherd_core/fw_tools/converter.py,sha256=3igRT33tghrBCao5njuPmePS-Y_lSa6EUHvwC
|
|
|
57
57
|
shepherd_core/fw_tools/converter_elf.py,sha256=GQDVqIqMW4twNMvZIV3sowFMezhs2TN-IYREjRP7Xt4,1089
|
|
58
58
|
shepherd_core/fw_tools/patcher.py,sha256=D6MHaCvKRRVQYSZozODAp_l7UnqxVsvnulPzpkfXWW8,4108
|
|
59
59
|
shepherd_core/fw_tools/validation.py,sha256=dOweyWwVMq0b4Liha39mGUOiblD7Nz5kSFVrlxUx41o,4707
|
|
60
|
-
shepherd_core/inventory/__init__.py,sha256=
|
|
60
|
+
shepherd_core/inventory/__init__.py,sha256=nyDBAE7kdDeh1McyavMWH1cY0WqphjFS1NL4XRAyzUw,3841
|
|
61
61
|
shepherd_core/inventory/python.py,sha256=OWNnyEt0IDPW9XGW-WloU0FExwgZzYNA05VpRj4cZGc,1250
|
|
62
62
|
shepherd_core/inventory/system.py,sha256=Q_mtM2zhUsTvzELgpEVxSxL9aFS1RSGcCX6RFpyZ-r8,3155
|
|
63
63
|
shepherd_core/inventory/target.py,sha256=Lq11j25tWieXheOxIDaQb-lc-2omxYVex5P6uGiLUyk,507
|
|
@@ -65,7 +65,7 @@ shepherd_core/testbed_client/__init__.py,sha256=QtbsBUzHwOoM6rk0qa21ywuz63YV7af1
|
|
|
65
65
|
shepherd_core/testbed_client/cache_path.py,sha256=tS0er9on5fw8wddMCt1jkc2uyYOdSTvX_UmfmYJf6tY,445
|
|
66
66
|
shepherd_core/testbed_client/client_abc_fix.py,sha256=BsSkpvJHURRejlS-YPF1f6QRPC_X0fYEsJpinzsx6Jc,4079
|
|
67
67
|
shepherd_core/testbed_client/client_web.py,sha256=rOg0Gf-s3S8U0z2ssh9_bNnq4aRQINfuuVHionPf38M,5788
|
|
68
|
-
shepherd_core/testbed_client/fixtures.py,sha256=
|
|
68
|
+
shepherd_core/testbed_client/fixtures.py,sha256=W8trHZXyuD0xzb28ZW-2BQ3ZDU2tI3LC4mjrbOUX57E,9215
|
|
69
69
|
shepherd_core/testbed_client/user_model.py,sha256=5M3vWkAGBwdGDUYAanAjrZwpzMBlh3XLOVvNYWiLmms,2107
|
|
70
70
|
shepherd_core/vsource/__init__.py,sha256=vTvFWuJn4eurPNzEiMd15c1Rd6o3DTWzCfbhOomflZU,771
|
|
71
71
|
shepherd_core/vsource/target_model.py,sha256=LaB5ppi2-IIpIepDqDvOliR-BupzccJl44yRxjlF-ms,5113
|
|
@@ -74,8 +74,8 @@ shepherd_core/vsource/virtual_harvester_model.py,sha256=GyA0uGl3r42t5c4roYtEaj22
|
|
|
74
74
|
shepherd_core/vsource/virtual_harvester_simulation.py,sha256=MFT583s73BJZYyhcqgnDUGTPr9s_lN_lKafzJG6kueE,2457
|
|
75
75
|
shepherd_core/vsource/virtual_source_model.py,sha256=9tiOzgrEgdEH5Uuhd8hjmmIkquNDuaNjLlblvInIlzg,3036
|
|
76
76
|
shepherd_core/vsource/virtual_source_simulation.py,sha256=ZOnFd2uPawY2G1salCiLGx9o1OBG99_-EHBtDjx4ZUo,5140
|
|
77
|
-
shepherd_core-2025.
|
|
78
|
-
shepherd_core-2025.
|
|
79
|
-
shepherd_core-2025.
|
|
80
|
-
shepherd_core-2025.
|
|
81
|
-
shepherd_core-2025.
|
|
77
|
+
shepherd_core-2025.4.1.dist-info/METADATA,sha256=3IZbuK5skDrSs1QRlY9uA8MJLsUx0NSYALft4GwVfeQ,7806
|
|
78
|
+
shepherd_core-2025.4.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
79
|
+
shepherd_core-2025.4.1.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
|
|
80
|
+
shepherd_core-2025.4.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
81
|
+
shepherd_core-2025.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|