shepherd-core 2024.7.1__py3-none-any.whl → 2024.7.3__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/__init__.py CHANGED
@@ -19,11 +19,11 @@ from .logger import get_verbose_level
19
19
  from .logger import increase_verbose_level
20
20
  from .logger import logger
21
21
  from .reader import Reader
22
- from .testbed_client.client import TestbedClient
23
- from .testbed_client.client import tb_client
22
+ from .testbed_client.client_web import WebClient
23
+ from .version import version
24
24
  from .writer import Writer
25
25
 
26
- __version__ = "2024.7.1"
26
+ __version__ = version
27
27
 
28
28
  __all__ = [
29
29
  "Reader",
@@ -40,7 +40,6 @@ __all__ = [
40
40
  "local_now",
41
41
  "Calc_t",
42
42
  "Compression",
43
- "TestbedClient",
44
- "tb_client", # using this (instead of the Class) is the cleaner, but less pythonic way
43
+ "WebClient",
45
44
  "Inventory",
46
45
  ]
@@ -7,6 +7,8 @@ from pydantic import BaseModel
7
7
  from pydantic import StringConstraints
8
8
  from typing_extensions import Annotated
9
9
 
10
+ from ...version import version
11
+
10
12
  SafeStrClone = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
11
13
  # ⤷ copy avoids circular import
12
14
 
@@ -19,5 +21,7 @@ class Wrapper(BaseModel):
19
21
  comment: Optional[SafeStrClone] = None
20
22
  created: Optional[datetime] = None
21
23
  # ⤷ Optional metadata
24
+ lib_ver: Optional[str] = version
25
+ # ⤷ for debug-purposes and later comp-checks
22
26
  parameters: dict
23
27
  # ⤷ ShpModel
@@ -35,7 +35,7 @@ class EnergyEnvironment(ContentModel):
35
35
 
36
36
  # TODO: scale up/down voltage/current
37
37
 
38
- # additional descriptive metadata
38
+ # additional descriptive metadata, TODO: these are very solar-centered -> generalize
39
39
  light_source: Optional[str] = None
40
40
  weather_conditions: Optional[str] = None
41
41
  indoor: Optional[bool] = None
@@ -152,6 +152,7 @@ class Firmware(ContentModel, title="Firmware of Target"):
152
152
 
153
153
  if not match:
154
154
  logger.warning("FW-Hash does not match with stored value!")
155
+ # TODO: it might be more appropriate to raise here
155
156
  return match
156
157
 
157
158
  @validate_call
@@ -13,6 +13,7 @@ from pydantic import model_validator
13
13
  from typing_extensions import Annotated
14
14
  from typing_extensions import Self
15
15
 
16
+ from ...version import version
16
17
  from ..base.content import IdInt
17
18
  from ..base.content import NameStr
18
19
  from ..base.content import SafeStr
@@ -54,18 +55,20 @@ class Experiment(ShpModel, title="Config of an Experiment"):
54
55
  # targets
55
56
  target_configs: Annotated[List[TargetConfig], Field(min_length=1, max_length=128)]
56
57
 
57
- # TODO: we probably need to remember the lib-version for content &| experiment
58
+ # for debug-purposes and later comp-checks
59
+ lib_ver: Optional[str] = version
58
60
 
59
61
  @model_validator(mode="after")
60
62
  def post_validation(self) -> Self:
61
- self.validate_targets(self.target_configs)
62
- self.validate_observers(self.target_configs)
63
+ testbed = Testbed() # this will query the first (and only) entry of client
64
+ self._validate_targets(self.target_configs)
65
+ self._validate_observers(self.target_configs, testbed)
63
66
  if self.duration and self.duration.total_seconds() < 0:
64
67
  raise ValueError("Duration of experiment can't be negative.")
65
68
  return self
66
69
 
67
70
  @staticmethod
68
- def validate_targets(configs: List[TargetConfig]) -> None:
71
+ def _validate_targets(configs: List[TargetConfig]) -> None:
69
72
  target_ids = []
70
73
  custom_ids = []
71
74
  for _config in configs:
@@ -83,10 +86,8 @@ class Experiment(ShpModel, title="Config of an Experiment"):
83
86
  raise ValueError("Custom Target-ID are faulty (some form of id-collisions)!")
84
87
 
85
88
  @staticmethod
86
- def validate_observers(configs: List[TargetConfig]) -> None:
89
+ def _validate_observers(configs: List[TargetConfig], testbed: Testbed) -> None:
87
90
  target_ids = [_id for _config in configs for _id in _config.target_IDs]
88
-
89
- testbed = Testbed(name="shepherd_tud_nes")
90
91
  obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
91
92
  if len(target_ids) > len(set(obs_ids)):
92
93
  raise ValueError(
@@ -32,8 +32,9 @@ class TestbedTasks(ShpModel):
32
32
  @validate_call
33
33
  def from_xp(cls, xp: Experiment, tb: Optional[Testbed] = None) -> Self:
34
34
  if tb is None:
35
- # TODO: just for testing OK
36
- tb = Testbed(name="shepherd_tud_nes")
35
+ # TODO: is tb-argument really needed? prob. not
36
+ tb = Testbed() # this will query the first (and only) entry of client
37
+
37
38
  tgt_ids = xp.get_target_ids()
38
39
  obs_tasks = [ObserverTasks.from_xp(xp, tb, _id) for _id in tgt_ids]
39
40
  return cls(
@@ -11,6 +11,7 @@ from pydantic import model_validator
11
11
  from typing_extensions import Annotated
12
12
  from typing_extensions import Self
13
13
 
14
+ from ... import logger
14
15
  from ...testbed_client import tb_client
15
16
  from ..base.content import IdInt
16
17
  from ..base.content import NameStr
@@ -42,6 +43,14 @@ class Testbed(ShpModel):
42
43
  @model_validator(mode="before")
43
44
  @classmethod
44
45
  def query_database(cls, values: dict) -> dict:
46
+ # allow instantiating an empty Testbed
47
+ # -> query the first (and only) entry of client
48
+ if len(values) == 0:
49
+ ids = tb_client.query_ids(cls.__name__)
50
+ if len(ids) > 1:
51
+ logger.warning("More than one testbed defined?!?")
52
+ values = {"id": ids[0]}
53
+
45
54
  values, _ = tb_client.try_completing_model(cls.__name__, values)
46
55
  return values
47
56
 
@@ -5,6 +5,7 @@ import subprocess
5
5
  import time
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
+ from typing import List
8
9
  from typing import Optional
9
10
 
10
11
  from typing_extensions import Self
@@ -48,8 +49,8 @@ class SystemInventory(ShpModel):
48
49
  # ip IPvAnyAddress
49
50
  # mac MACStr
50
51
 
51
- fs_root: Optional[str] = None
52
- beagle: Optional[str] = None
52
+ fs_root: List[str] = None
53
+ beagle: List[str] = None
53
54
 
54
55
  model_config = ConfigDict(str_min_length=0)
55
56
 
@@ -71,20 +72,20 @@ class SystemInventory(ShpModel):
71
72
  uptime = time.time() - psutil.boot_time()
72
73
 
73
74
  fs_cmd = ["/usr/bin/df", "-h", "/"]
74
- fs_out = None
75
+ fs_out = []
75
76
  if Path(fs_cmd[0]).is_file():
76
77
  reply = subprocess.run( # noqa: S603
77
78
  fs_cmd, timeout=30, capture_output=True, check=False
78
79
  )
79
- fs_out = str(reply.stdout)
80
+ fs_out = str(reply.stdout).split(r"\n")
80
81
 
81
82
  beagle_cmd = ["/usr/bin/beagle-version"]
82
- beagle_out = None
83
+ beagle_out = []
83
84
  if Path(beagle_cmd[0]).is_file():
84
85
  reply = subprocess.run( # noqa: S603
85
86
  beagle_cmd, timeout=30, capture_output=True, check=False
86
87
  )
87
- beagle_out = str(reply.stdout)
88
+ beagle_out = str(reply.stdout).split(r"\n")
88
89
 
89
90
  ptp_cmd = ["/usr/sbin/ptp4l", "-v"]
90
91
  ptp_out = None
shepherd_core/reader.py CHANGED
@@ -536,9 +536,9 @@ class Reader:
536
536
  def count_errors_in_log(self, group_name: str = "sheep", min_level: int = 40) -> int:
537
537
  if group_name not in self.h5file:
538
538
  return 0
539
- if "level" not in self.h5file["sheep"]:
539
+ if "level" not in self.h5file[group_name]:
540
540
  return 0
541
- _lvl = self.h5file["sheep"]["level"]
541
+ _lvl = self.h5file[group_name]["level"]
542
542
  if _lvl.shape[0] < 1:
543
543
  return 0
544
544
  _items = [1 for _x in _lvl[:] if _x >= min_level]
@@ -688,4 +688,11 @@ class Reader:
688
688
  gpio_wf = pin_wf.astype(float)
689
689
  gpio_wf[:, 0] = gpio_wf[:, 0] / 1e9
690
690
 
691
- return Uart(gpio_wf).get_lines()
691
+ try:
692
+ return Uart(gpio_wf).get_lines()
693
+ except TypeError:
694
+ self._logger.error("TypeError: Extracting UART from GPIO failed - will skip file.")
695
+ return None
696
+ except ValueError:
697
+ self._logger.error("ValueError: Extracting UART from GPIO failed - will skip file.")
698
+ return None
@@ -1,9 +1,11 @@
1
1
  """Client to access a testbed-instance for controlling experiments."""
2
2
 
3
- from .client import tb_client
3
+ from .client_abc_fix import tb_client
4
+ from .client_web import WebClient
4
5
  from .user_model import User
5
6
 
6
7
  __all__ = [
7
8
  "tb_client",
9
+ "WebClient",
8
10
  "User",
9
11
  ]
@@ -0,0 +1,126 @@
1
+ """AbstractBase-Class & Client-Class to access the file based fixtures.
2
+
3
+ Fixtures == OffLineDemoInstances
4
+ offline: core - fixtClient
5
+ webDev: core - webClient <-> webSrv - fixtClient
6
+ webUser: core - webClient <-> webSrv - DbClient
7
+ webInfra: core - webClient+ <-> webSrv - DbClient
8
+
9
+ Users, Sheep and ServerApps should have access to the same DB via WebClient
10
+
11
+ Note: ABC and FixClient can't be in separate files when tb_client should
12
+ default to FixClient (circular import)
13
+
14
+ TODO: Comfort functions missing
15
+ - fixtures to DB, and vice versa
16
+ """
17
+
18
+ from abc import ABC
19
+ from abc import abstractmethod
20
+ from typing import List
21
+ from typing import Optional
22
+
23
+ from ..data_models.base.shepherd import ShpModel
24
+ from ..data_models.base.wrapper import Wrapper
25
+ from .fixtures import Fixtures
26
+
27
+
28
+ class AbcClient(ABC):
29
+ """AbstractBase-Class to access a testbed instance."""
30
+
31
+ def __init__(self) -> None:
32
+ global tb_client # noqa: PLW0603
33
+ tb_client = self
34
+
35
+ @abstractmethod
36
+ def insert(self, data: ShpModel) -> bool:
37
+ """Insert (and probably replace) entry.
38
+
39
+ TODO: fixtures get replaced, but is that wanted for web?
40
+ """
41
+
42
+ @abstractmethod
43
+ def query_ids(self, model_type: str) -> List[int]:
44
+ pass
45
+
46
+ @abstractmethod
47
+ def query_names(self, model_type: str) -> List[str]:
48
+ pass
49
+
50
+ @abstractmethod
51
+ def query_item(
52
+ self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
53
+ ) -> dict:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def try_inheritance(self, model_type: str, values: dict) -> (dict, list):
58
+ # TODO: maybe internal? yes
59
+ pass
60
+
61
+ def try_completing_model(self, model_type: str, values: dict) -> (dict, list):
62
+ """Init by name/id, for none existing instances raise Exception.
63
+
64
+ This is the main entry-point for querying a model (used be the core-lib).
65
+ """
66
+ if len(values) == 1 and next(iter(values.keys())) in {"id", "name"}:
67
+ try:
68
+ values = self.query_item(model_type, name=values.get("name"), uid=values.get("id"))
69
+ except ValueError as err:
70
+ raise ValueError(
71
+ "Query %s by name / ID failed - %s is unknown!", model_type, values
72
+ ) from err
73
+ return self.try_inheritance(model_type, values)
74
+
75
+ @abstractmethod
76
+ def fill_in_user_data(self, values: dict) -> dict:
77
+ # TODO: is it really helpful and needed?
78
+ pass
79
+
80
+
81
+ class FixturesClient(AbcClient):
82
+ """Client-Class to access the file based fixtures."""
83
+
84
+ def __init__(self) -> None:
85
+ super().__init__()
86
+ self._fixtures: Optional[Fixtures] = Fixtures()
87
+
88
+ def insert(self, data: ShpModel) -> bool:
89
+ wrap = Wrapper(
90
+ datatype=type(data).__name__,
91
+ parameters=data.model_dump(),
92
+ )
93
+ self._fixtures.insert_model(wrap)
94
+ return True
95
+
96
+ def query_ids(self, model_type: str) -> List[int]:
97
+ return list(self._fixtures[model_type].elements_by_id.keys())
98
+
99
+ def query_names(self, model_type: str) -> List[str]:
100
+ return list(self._fixtures[model_type].elements_by_name.keys())
101
+
102
+ def query_item(
103
+ self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
104
+ ) -> dict:
105
+ if uid is not None:
106
+ return self._fixtures[model_type].query_id(uid)
107
+ if name is not None:
108
+ return self._fixtures[model_type].query_name(name)
109
+ raise ValueError("Query needs either uid or name of object")
110
+
111
+ def try_inheritance(self, model_type: str, values: dict) -> (dict, list):
112
+ return self._fixtures[model_type].inheritance(values)
113
+
114
+ def fill_in_user_data(self, values: dict) -> dict:
115
+ """Add fake user-data when offline-client is used.
116
+
117
+ Hotfix until WebClient is working.
118
+ """
119
+ if values.get("owner") is None:
120
+ values["owner"] = "unknown"
121
+ if values.get("group") is None:
122
+ values["group"] = "unknown"
123
+ return values
124
+
125
+
126
+ tb_client: AbcClient = FixturesClient()
@@ -0,0 +1,157 @@
1
+ """Client-Class to access a testbed instance over the web."""
2
+
3
+ from importlib import import_module
4
+ from pathlib import Path
5
+ from typing import List
6
+ from typing import Optional
7
+ from typing import Union
8
+
9
+ from pydantic import validate_call
10
+
11
+ from ..commons import testbed_server_default
12
+ from ..data_models.base.shepherd import ShpModel
13
+ from ..data_models.base.wrapper import Wrapper
14
+ from .client_abc_fix import AbcClient
15
+ from .user_model import User
16
+
17
+
18
+ class WebClient(AbcClient):
19
+ """Client-Class to access a testbed instance over the web.
20
+
21
+ For online-queries the lib can be connected to the testbed-server.
22
+ NOTE: there are 3 states:
23
+ - unconnected -> demo-fixtures are queried (locally)
24
+ - connected -> publicly available data is queried online
25
+ - logged in with valid token -> also private data is queried online
26
+ """
27
+
28
+ testbed_server_default = "https://shepherd.cfaed.tu-dresden.de:8000/testbed"
29
+
30
+ def __init__(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> None:
31
+ """Connect to Testbed-Server with optional token and server-address.
32
+
33
+ server: optional address to shepherd-server-endpoint
34
+ token: your account validation. if omitted, only public data is available
35
+ """
36
+ super().__init__()
37
+ if not hasattr(self, "_token"):
38
+ # add default values
39
+ self._token: str = "basic_public_access"
40
+ self._server: str = testbed_server_default
41
+ self._user: Optional[User] = None
42
+ self._key: Optional[str] = None
43
+ self._connected: bool = False
44
+ self._req = None
45
+
46
+ if not self._connected:
47
+ self._connect(server, token)
48
+
49
+ # ABC Functions below
50
+
51
+ def insert(self, data: ShpModel) -> bool:
52
+ wrap = Wrapper(
53
+ datatype=type(data).__name__,
54
+ parameters=data.model_dump(),
55
+ )
56
+ r = self._req.post(self._server + "/add", data=wrap.model_dump_json(), timeout=2)
57
+ r.raise_for_status()
58
+ return True
59
+
60
+ def query_ids(self, model_type: str) -> List[int]:
61
+ raise NotImplementedError("TODO")
62
+
63
+ def query_names(self, model_type: str) -> List[str]:
64
+ raise NotImplementedError("TODO")
65
+
66
+ def query_item(
67
+ self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
68
+ ) -> dict:
69
+ raise NotImplementedError("TODO")
70
+
71
+ def try_inheritance(self, model_type: str, values: dict) -> (dict, list):
72
+ raise NotImplementedError("TODO")
73
+
74
+ def fill_in_user_data(self, values: dict) -> dict:
75
+ if values.get("owner") is None:
76
+ values["owner"] = self._user.name
77
+ if values.get("group") is None:
78
+ values["group"] = self._user.group
79
+ return values
80
+
81
+ # Below are extra FNs not in ABC
82
+
83
+ @validate_call
84
+ def _connect(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> bool:
85
+ """Establish connection to testbed-server.
86
+
87
+ TODO: totally not finished
88
+ """
89
+ if isinstance(token, Path):
90
+ if not token.exists():
91
+ raise FileNotFoundError("Token-Path does not exist")
92
+ with token.resolve().open() as file:
93
+ self._token = file.read()
94
+ elif isinstance(token, str):
95
+ self._token = self._token
96
+
97
+ if isinstance(server, str):
98
+ self._server = server.lower()
99
+
100
+ self._req = import_module("requests") # here due to slow startup
101
+
102
+ # extended connection-test:
103
+ self._query_session_key()
104
+ self._connected = True
105
+ return self._query_user_data()
106
+
107
+ def _query_session_key(self) -> bool:
108
+ if self._server:
109
+ r = self._req.get(self._server + "/session_key", timeout=2)
110
+ r.raise_for_status()
111
+ self._key = r.json()["value"] # TODO: not finished
112
+ return True
113
+ return False
114
+
115
+ def _query_user_data(self) -> bool:
116
+ if self._server:
117
+ r = self._req.get(self._server + "/user?token=" + self._token, timeout=2)
118
+ # TODO: possibly a security nightmare (send via json or encrypted via public key?)
119
+ r.raise_for_status()
120
+ self._user = User(**r.json())
121
+ return True
122
+ return False
123
+
124
+ def submit_experiment(self, xp: ShpModel) -> str:
125
+ """Transmit XP to server to validate its feasibility.
126
+
127
+ - Experiment will be added to DB (if not present)
128
+ - if the same experiment is resubmitted it will just return the ID of that XP
129
+ - Experiment will be validated by converting it into a task-set (additional validation)
130
+ - optional: the scheduler should validate there are no time-collisions
131
+
132
+ Will return an ID if valid, otherwise an empty string.
133
+ TODO: maybe its better to throw specific errors if validation fails
134
+ TODO: is it better to include these experiment-related FNs in Xp-Class?
135
+ TODO: Experiment-typehint for argument triggers circular import
136
+ """
137
+ raise NotImplementedError("TODO")
138
+
139
+ def schedule_experiment(self, id_xp: str) -> bool:
140
+ """Enqueue XP on testbed."""
141
+ raise NotImplementedError("TODO")
142
+
143
+ def get_experiment_status(self, id_xp: str) -> str:
144
+ """Ask server about current state of XP.
145
+
146
+ - after valid submission: disabled / deactivated
147
+ - after scheduling: scheduled
148
+ - before start-time: preparing
149
+ - during run: active
150
+ - after run: post-processing (collecting & assembling data)
151
+ - finished: ready to download
152
+ """
153
+ raise NotImplementedError("TODO")
154
+
155
+ def get_experiment_results(self, id_xp: str, path: Path) -> bool:
156
+ """Download resulting files."""
157
+ raise NotImplementedError("TODO")
@@ -183,12 +183,18 @@ class Fixtures:
183
183
  else:
184
184
  self.file_path = file_path
185
185
  self.components: Dict[str, Fixture] = {}
186
- save_path = cache_user_path / "fixtures.pickle"
187
-
188
- if save_path.exists() and not file_older_than(save_path, timedelta(hours=24)) and not reset:
189
- # speedup
186
+ cache_file = cache_user_path / "fixtures.pickle"
187
+ sheep_detect = Path("/lib/firmware/am335x-pru0-fw").exists()
188
+
189
+ if (
190
+ not sheep_detect
191
+ and cache_file.exists()
192
+ and not file_older_than(cache_file, timedelta(hours=24))
193
+ and not reset
194
+ ):
195
+ # speedup by loading from cache
190
196
  # TODO: also add version as criterion
191
- with save_path.open("rb", buffering=-1) as fd:
197
+ with cache_file.open("rb", buffering=-1) as fd:
192
198
  self.components = pickle.load(fd) # noqa: S301
193
199
  logger.debug(" -> found & used pickled fixtures")
194
200
  else:
@@ -204,9 +210,9 @@ class Fixtures:
204
210
 
205
211
  if len(self.components) < 1:
206
212
  logger.error(f"No fixture-components found at {self.file_path.as_posix()}")
207
- else:
208
- save_path.parent.mkdir(parents=True, exist_ok=True)
209
- with save_path.open("wb", buffering=-1) as fd:
213
+ elif sheep_detect:
214
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
215
+ with cache_file.open("wb", buffering=-1) as fd:
210
216
  pickle.dump(self.components, fd)
211
217
 
212
218
  @validate_call
@@ -0,0 +1,3 @@
1
+ """Separated string avoids circular imports."""
2
+
3
+ version: str = "2024.7.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shepherd_core
3
- Version: 2024.7.1
3
+ Version: 2024.7.3
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>
@@ -1,8 +1,9 @@
1
- shepherd_core/__init__.py,sha256=8FydSimdy6iX5N_ZPoxm2e4AEJLUg0wikCon87iqsj4,1385
1
+ shepherd_core/__init__.py,sha256=QyqENyf508XfZQ4vDU5o6UL9rmIqkf8kzwgTF9XU1-Y,1270
2
2
  shepherd_core/calibration_hw_def.py,sha256=m5HDxHJ_blnn-C0Og5v6by1ApJRDT7RhytFbL-P5730,2548
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=dZt_q9PJt6FKBMfesk3wLfYdBp0EjCe_-gGZ-Gvd4tk,26473
5
+ shepherd_core/reader.py,sha256=9BuArqou5pmPKUrJH9oiPYlU1DkMxUScL4nftDJuFIs,26790
6
+ shepherd_core/version.py,sha256=_g69sZNO8AhbkOwQt8KmcgsSbdI6Yyb6XSYvZKHehC0,75
6
7
  shepherd_core/writer.py,sha256=xcLCw-YokKaN8TrkwD0IjRmn8xZU0Q8wwWp_1K8JFVY,14475
7
8
  shepherd_core/data_models/__init__.py,sha256=IVjKbT2Ilz5bev325EvAuuhd9LfQgQ1u7qKo6dhVA2k,1866
8
9
  shepherd_core/data_models/readme.md,sha256=1bdfEypY_0NMhXLxOPRnLAsFca0HuHdq7_01yEWxvUs,2470
@@ -13,19 +14,19 @@ shepherd_core/data_models/base/calibration.py,sha256=HPhDEaApOCKxfX2q9RD8YCvA3Gq
13
14
  shepherd_core/data_models/base/content.py,sha256=13j7GSgT73xn27jgDP508thUEJR4U-nCb5n7CJ50c9Y,2463
14
15
  shepherd_core/data_models/base/shepherd.py,sha256=DNrx59o1VBuy_liJuUzZRzmTTYB73D_pUWiNyMQyjYY,6112
15
16
  shepherd_core/data_models/base/timezone.py,sha256=2T6E46hJ1DAvmqKfu6uIgCK3RSoAKjGXRyzYNaqKyjY,665
16
- shepherd_core/data_models/base/wrapper.py,sha256=duOdIXJeSOab4lVhe9ujhnjhJv3ue0mhimuUjy1xy3A,635
17
+ shepherd_core/data_models/base/wrapper.py,sha256=Izp17HFCKNAS3TnWcPn3MM9fWdc3A-F7eDyAsYlyWCw,755
17
18
  shepherd_core/data_models/content/__init__.py,sha256=wVa5lw6bS-fBgeo-SWydg6rw8AsScxqNgDo81dzteaE,537
18
19
  shepherd_core/data_models/content/_external_fixtures.yaml,sha256=0CH7YSWT_hzL-jcg4JjgN9ryQOzbS8S66_pd6GbMnHw,12259
19
- shepherd_core/data_models/content/energy_environment.py,sha256=HeMcM01fS2YzhvIh5bt6lC9vW2sBrPt7X_jGdEe8k_g,1374
20
+ shepherd_core/data_models/content/energy_environment.py,sha256=bXInmHzlRjBAt7mitig35V-zCfj98ZvGEBio0miSxRg,1425
20
21
  shepherd_core/data_models/content/energy_environment_fixture.yaml,sha256=UBXTdGT7MK98zx5w_RBCu-f9uNCKxRgiFBQFbmDUxPc,1301
21
- shepherd_core/data_models/content/firmware.py,sha256=rv5gAOYq4Al-sAg3v5TuYrxONMKLngBsdln3hbmISTA,5659
22
+ shepherd_core/data_models/content/firmware.py,sha256=MyEiaP6bkOm7i_oihDXTxHC7ajc5aqiIDLn7mhap6YY,5722
22
23
  shepherd_core/data_models/content/firmware_datatype.py,sha256=XPU9LOoT3h5qFOlE8WU0vAkw-vymNxzor9kVFyEqsWg,255
23
24
  shepherd_core/data_models/content/virtual_harvester.py,sha256=5eEHAZrgHPHZlTxDGaJrckDQgupFNC3Zax67EcCSqR8,9448
24
25
  shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=-IRyoQU0HXCEtIIcFmkFdz4snLB7bjFFqNcFVGSMiSA,4332
25
26
  shepherd_core/data_models/content/virtual_source.py,sha256=aoD8oam1POid0JG2ppttPA_Jl3y4ko5FNqzoaNKyBD8,14142
26
27
  shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=kx_lpBx0bLKqEHxS09GTnk8kuSbhuGhLgKHeaM6UviE,10481
27
28
  shepherd_core/data_models/experiment/__init__.py,sha256=9TE9_aSnCNRhagsIWLTE8XkyjyMGB7kEGdswl-296v0,645
28
- shepherd_core/data_models/experiment/experiment.py,sha256=QX3-oeBe5dRx1a_RHF6kxjdQAyCRcLiMmSw4gYDjbEg,3949
29
+ shepherd_core/data_models/experiment/experiment.py,sha256=wnn6T3czuh4rz6OSYtMltCTbRpPX55TLVAtQcKO7Uhg,4044
29
30
  shepherd_core/data_models/experiment/observer_features.py,sha256=qxnb7anuQz9ZW5IUlPdUXYPIl5U7O9uXkJqZtMnAb0Y,5156
30
31
  shepherd_core/data_models/experiment/target_config.py,sha256=XIsjbbo7yn_A4q3GMxWbiNzEGA0Kk5gH7-XfQQ7Kg0E,3674
31
32
  shepherd_core/data_models/task/__init__.py,sha256=rZLbgqX-dTWY4026-bqW-IWVHbA6C_xP9y0aeRze8FY,3374
@@ -34,7 +35,7 @@ shepherd_core/data_models/task/firmware_mod.py,sha256=Rw_TA1ykQ7abUd_U0snqZlpZyr
34
35
  shepherd_core/data_models/task/harvest.py,sha256=HHnqWwRsJupaZJxuohs7NrK6VaDyoRzGOaG2h9y3s1Y,3360
35
36
  shepherd_core/data_models/task/observer_tasks.py,sha256=XlH_-EGRrdodTn0c2pjGvpcauc0a9NOnLhysKw8iRwk,3511
36
37
  shepherd_core/data_models/task/programming.py,sha256=Mg9_AZHIdG01FheEJAifIRPSB3iZ0UJITf8zeg2jyws,2323
37
- shepherd_core/data_models/task/testbed_tasks.py,sha256=yU4YhNN1ObYkcts7UOwkWWhCmW67f_Gjx3dhvzMnKWI,2036
38
+ shepherd_core/data_models/task/testbed_tasks.py,sha256=zvIitq0Ek1Ae7baWiBkSQN8nRugyw0N2P4SeVoj_QaY,2090
38
39
  shepherd_core/data_models/testbed/__init__.py,sha256=cL3swgijyIpZIW1vl51OVR2seAlWt6Ke9oB_cBkPniU,612
39
40
  shepherd_core/data_models/testbed/cape.py,sha256=D23ZKXpZRPIIOMn6LCoJrwHiRbSaYg-y7B6fAt1ap64,1246
40
41
  shepherd_core/data_models/testbed/cape_fixture.yaml,sha256=uwZxe6hsqvofn5tzg4sffjbVtTVUkextL1GCri_z2A4,2197
@@ -46,7 +47,7 @@ shepherd_core/data_models/testbed/observer.py,sha256=hlj6buDzUQKYnlhCJZyxnrAPYKo
46
47
  shepherd_core/data_models/testbed/observer_fixture.yaml,sha256=w4VS6lTzaVs5IqWjkHanxcjDhIEydQPCV6z_DlsLFqA,4812
47
48
  shepherd_core/data_models/testbed/target.py,sha256=KeJaLradQ3oHeeowCg_X0lDHDqyi3R3La0YPKC5Rv90,1838
48
49
  shepherd_core/data_models/testbed/target_fixture.yaml,sha256=6YbCV3aTtDUKzC40kPURq9nFwTjT97LNy7imOb_35sk,3668
49
- shepherd_core/data_models/testbed/testbed.py,sha256=jLgidZMB0LhoP99s_OOADFPZgCipio1q3kLLiXg9Ekw,3353
50
+ shepherd_core/data_models/testbed/testbed.py,sha256=0uJ3OwqCKDn78OCJOaMa2XWxTLF1ultjmpHVSx3LyhE,3695
50
51
  shepherd_core/data_models/testbed/testbed_fixture.yaml,sha256=9i2cmYRrHOHTJG9zp40h8h0LgO9DdrCJz8tyGdiQCzc,714
51
52
  shepherd_core/decoder_waveform/__init__.py,sha256=-ohGz0fA2tKxUJk4FAQXKtI93d6YGdy0CrkdhOod1QU,120
52
53
  shepherd_core/decoder_waveform/uart.py,sha256=sHsXHOsDU1j9zMSZO7CCMTMinT4U_S5NgsEkl1lJK1U,11029
@@ -57,19 +58,20 @@ shepherd_core/fw_tools/patcher.py,sha256=D6MHaCvKRRVQYSZozODAp_l7UnqxVsvnulPzpkf
57
58
  shepherd_core/fw_tools/validation.py,sha256=hBLCKIUumPTA6iuXMVbMYph2jamaxeSTxRqsvl3C4-I,4699
58
59
  shepherd_core/inventory/__init__.py,sha256=nRO11HG4eJ_FaXebSkE0dd1H6qvjrX5n3OQHOzKXVvk,3841
59
60
  shepherd_core/inventory/python.py,sha256=OWNnyEt0IDPW9XGW-WloU0FExwgZzYNA05VpRj4cZGc,1250
60
- shepherd_core/inventory/system.py,sha256=GScfYZm0dlWQv4Np6R1JI_XGHVcECAw50Wqef_hW3VU,3121
61
+ shepherd_core/inventory/system.py,sha256=jRzko9QNPLaBiG7urVaeqqvb3GtCEYRwc0DAghRkLVo,3159
61
62
  shepherd_core/inventory/target.py,sha256=Lq11j25tWieXheOxIDaQb-lc-2omxYVex5P6uGiLUyk,507
62
- shepherd_core/testbed_client/__init__.py,sha256=lzi7F5Go-AsbTbiUCf9Rnu6pzmTZqmpIqoS1yCPal_c,175
63
+ shepherd_core/testbed_client/__init__.py,sha256=kV-_g1ZiopWg2Aoq0k7fPD0pDqHrMDVYnNOrMBBWgnY,234
63
64
  shepherd_core/testbed_client/cache_path.py,sha256=tS0er9on5fw8wddMCt1jkc2uyYOdSTvX_UmfmYJf6tY,445
64
- shepherd_core/testbed_client/client.py,sha256=3vbJ9XdWe9YA28AXgzXThHJtYJU5RUUCpDRBH1mDIr8,5917
65
- shepherd_core/testbed_client/fixtures.py,sha256=TDRVtxorJ5IFlFwwVcSDpAm1g51ok1PPMt1WmdBpzaM,9748
65
+ shepherd_core/testbed_client/client_abc_fix.py,sha256=BsSkpvJHURRejlS-YPF1f6QRPC_X0fYEsJpinzsx6Jc,4079
66
+ shepherd_core/testbed_client/client_web.py,sha256=iMh5T91152uugbFsqr2vvxLser0KIo5g426dp_6QWUE,5774
67
+ shepherd_core/testbed_client/fixtures.py,sha256=4Uk583R4r6I5IB78HxOn-9UNH3sbFha7OPEdcSXvMCU,9939
66
68
  shepherd_core/testbed_client/user_model.py,sha256=5M3vWkAGBwdGDUYAanAjrZwpzMBlh3XLOVvNYWiLmms,2107
67
69
  shepherd_core/vsource/__init__.py,sha256=dS33KYLq5GQ9_D8HfdP8iWSocWTghCi2ZZG2AJWNfaM,391
68
70
  shepherd_core/vsource/virtual_converter_model.py,sha256=ZSoWVLfRmFEjeCNoQCg3BctzhdfayINUBDU_AJK1CR0,10404
69
71
  shepherd_core/vsource/virtual_harvester_model.py,sha256=wCbFfsqDRC5Rfu8qANkmkP9XGJOPHJY9-iSnI850JI4,7817
70
72
  shepherd_core/vsource/virtual_source_model.py,sha256=fjN8myTY3I_LpikF_aGAcxes3RGu1GP23P7XKC_UIyA,2737
71
- shepherd_core-2024.7.1.dist-info/METADATA,sha256=KtwQxX6iIfqe3mvFb3ZLVZXS6Ovjy9Vh2K6nu5lxvy8,7630
72
- shepherd_core-2024.7.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
73
- shepherd_core-2024.7.1.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
74
- shepherd_core-2024.7.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
75
- shepherd_core-2024.7.1.dist-info/RECORD,,
73
+ shepherd_core-2024.7.3.dist-info/METADATA,sha256=dbgRhKRkV3GkLlSKX6qbbIFloUtGQuPdM-Sjmx-Vt2Y,7630
74
+ shepherd_core-2024.7.3.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
75
+ shepherd_core-2024.7.3.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
76
+ shepherd_core-2024.7.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
77
+ shepherd_core-2024.7.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,161 +0,0 @@
1
- """Client-Class to access a testbed instance."""
2
-
3
- from importlib import import_module
4
- from pathlib import Path
5
- from typing import Optional
6
- from typing import TypedDict
7
- from typing import Union
8
-
9
- from pydantic import validate_call
10
- from typing_extensions import Self
11
- from typing_extensions import Unpack
12
-
13
- from ..commons import testbed_server_default
14
- from ..data_models.base.shepherd import ShpModel
15
- from ..data_models.base.wrapper import Wrapper
16
- from .fixtures import Fixtures
17
- from .user_model import User
18
-
19
-
20
- class TestbedClient:
21
- """Client-Class to access a testbed instance."""
22
-
23
- _instance: Optional[Self] = None
24
-
25
- def __init__(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> None:
26
- if not hasattr(self, "_token"):
27
- self._token: str = "null"
28
- self._server: Optional[str] = testbed_server_default
29
- self._user: Optional[User] = None
30
- self._key: Optional[str] = None
31
- self._fixtures: Optional[Fixtures] = Fixtures()
32
- self._connected: bool = False
33
- self._req = None
34
- if server is not None:
35
- self.connect(server=server, token=token)
36
-
37
- @classmethod
38
- def __new__(cls, *_args: tuple, **_kwargs: Unpack[TypedDict]) -> Self:
39
- if cls._instance is None:
40
- cls._instance = object.__new__(cls)
41
- return cls._instance
42
-
43
- def __del__(self) -> None:
44
- TestbedClient._instance = None
45
-
46
- @validate_call
47
- def connect(self, server: Optional[str] = None, token: Union[str, Path, None] = None) -> bool:
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.
52
- """
53
- if isinstance(token, Path):
54
- with token.resolve().open() as file:
55
- self._token = file.read()
56
- elif isinstance(token, str):
57
- self._token = token
58
-
59
- if server:
60
- self._server = server.lower()
61
-
62
- if self._server:
63
- self._req = import_module("requests") # here due to slow startup
64
-
65
- # extended connection-test:
66
- self._query_session_key()
67
- self._connected = True
68
- return self._query_user_data()
69
-
70
- return True
71
-
72
- def insert(self, data: ShpModel) -> bool:
73
- wrap = Wrapper(
74
- datatype=type(data).__name__,
75
- parameters=data.model_dump(),
76
- )
77
- if self._connected:
78
- r = self._req.post(self._server + "/add", data=wrap.model_dump_json(), timeout=2)
79
- r.raise_for_status()
80
- else:
81
- self._fixtures.insert_model(wrap)
82
- return True
83
-
84
- def query_ids(self, model_type: str) -> list:
85
- if self._connected:
86
- raise NotImplementedError("TODO")
87
- return list(self._fixtures[model_type].elements_by_id.keys())
88
-
89
- def query_names(self, model_type: str) -> list:
90
- if self._connected:
91
- raise NotImplementedError("TODO")
92
- return list(self._fixtures[model_type].elements_by_name.keys())
93
-
94
- def query_item(
95
- self, model_type: str, uid: Optional[int] = None, name: Optional[str] = None
96
- ) -> dict:
97
- if self._connected:
98
- raise NotImplementedError("TODO")
99
- if uid is not None:
100
- return self._fixtures[model_type].query_id(uid)
101
- if name is not None:
102
- return self._fixtures[model_type].query_name(name)
103
- raise ValueError("Query needs either uid or name of object")
104
-
105
- def _query_session_key(self) -> bool:
106
- if self._server:
107
- r = self._req.get(self._server + "/session_key", timeout=2)
108
- r.raise_for_status()
109
- self._key = r.json()["value"] # TODO: not finished
110
- return True
111
- return False
112
-
113
- def _query_user_data(self) -> bool:
114
- if self._server:
115
- r = self._req.get(self._server + "/user?token=" + self._token, timeout=2)
116
- # TODO: possibly a security nightmare (send via json or encrypted via public key?)
117
- r.raise_for_status()
118
- self._user = User(**r.json())
119
- return True
120
- return False
121
-
122
- def try_inheritance(self, model_type: str, values: dict) -> (dict, list):
123
- if self._connected:
124
- raise NotImplementedError("TODO")
125
- return self._fixtures[model_type].inheritance(values)
126
-
127
- def try_completing_model(self, model_type: str, values: dict) -> (dict, list):
128
- """Init by name/id, for none existing instances raise Exception."""
129
- if len(values) == 1 and next(iter(values.keys())) in {"id", "name"}:
130
- value = next(iter(values.values()))
131
- if (
132
- isinstance(value, str)
133
- and value.lower() in self._fixtures[model_type].elements_by_name
134
- ):
135
- values = self.query_item(model_type, name=value)
136
- elif isinstance(value, int) and value in self._fixtures[model_type].elements_by_id:
137
- # TODO: still depending on _fixture
138
- values = self.query_item(model_type, uid=value)
139
- else:
140
- msg = f"Query {model_type} by name / ID failed - {values} is unknown!"
141
- raise ValueError(msg)
142
- return self.try_inheritance(model_type, values)
143
-
144
- def fill_in_user_data(self, values: dict) -> dict:
145
- if self._user:
146
- # TODO: this looks wrong, should have "is None", why not always overwrite?
147
- if values.get("owner"):
148
- values["owner"] = self._user.name
149
- if values.get("group"):
150
- values["group"] = self._user.group
151
-
152
- # hotfix until testbed.client is working, TODO
153
- if values.get("owner") is None:
154
- values["owner"] = "unknown"
155
- if values.get("group") is None:
156
- values["group"] = "unknown"
157
-
158
- return values
159
-
160
-
161
- tb_client = TestbedClient()