splight-lib 4.0.0.dev6__tar.gz → 4.3.0.dev1__tar.gz

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 (87) hide show
  1. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/PKG-INFO +1 -1
  2. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/setup.py +1 -1
  3. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/datalake/abstract.py +25 -0
  4. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/datalake/remote_client.py +50 -0
  5. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/component/abstract.py +33 -23
  6. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/execution.py +26 -48
  7. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/asset.py +2 -0
  8. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/base.py +21 -0
  9. splight-lib-4.3.0.dev1/splight_lib/models/metadata.py +20 -0
  10. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/native.py +5 -0
  11. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/restclient/client.py +84 -0
  12. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/PKG-INFO +1 -1
  13. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/SOURCES.txt +1 -0
  14. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/LICENSE.txt +0 -0
  15. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/README.md +0 -0
  16. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/pyproject.toml +0 -0
  17. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/setup.cfg +0 -0
  18. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/__init__.py +0 -0
  19. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/abstract/__init__.py +0 -0
  20. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/abstract/client.py +0 -0
  21. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/auth/__init__.py +0 -0
  22. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/auth/exceptions.py +0 -0
  23. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/auth/mac_auth.py +0 -0
  24. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/auth/token.py +0 -0
  25. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/__init__.py +0 -0
  26. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/__init__.py +0 -0
  27. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/abstract.py +0 -0
  28. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/classmap.py +0 -0
  29. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/exceptions.py +0 -0
  30. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/local_client.py +0 -0
  31. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/communication/remote_client.py +0 -0
  32. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/__init__.py +0 -0
  33. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/abstract.py +0 -0
  34. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/builder.py +0 -0
  35. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/classmap.py +0 -0
  36. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/local_client.py +0 -0
  37. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/database/remote_client.py +0 -0
  38. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/datalake/__init__.py +0 -0
  39. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/datalake/builder.py +0 -0
  40. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/datalake/local_client.py +0 -0
  41. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/exceptions.py +0 -0
  42. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/file_handler.py +0 -0
  43. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/filter.py +0 -0
  44. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/hub/__init__.py +0 -0
  45. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/hub/abstract.py +0 -0
  46. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/client/hub/client.py +0 -0
  47. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/communication/__init__.py +0 -0
  48. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/communication/event_handler.py +0 -0
  49. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/component/__init__.py +0 -0
  50. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/component/exceptions.py +0 -0
  51. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/component/spec.py +0 -0
  52. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/constants.py +0 -0
  53. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/encryption.py +0 -0
  54. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/logging/__init__.py +0 -0
  55. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/logging/_internal.py +0 -0
  56. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/logging/component.py +0 -0
  57. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/logging/constants.py +0 -0
  58. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/logging/logging.py +0 -0
  59. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/__init__.py +0 -0
  60. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/alert.py +0 -0
  61. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/attribute.py +0 -0
  62. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/communication.py +0 -0
  63. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/component.py +0 -0
  64. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/dashboard.py +0 -0
  65. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/data_address.py +0 -0
  66. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/event.py +0 -0
  67. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/exceptions.py +0 -0
  68. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/file.py +0 -0
  69. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/hub.py +0 -0
  70. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/pipeline.py +0 -0
  71. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/query.py +0 -0
  72. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/secret.py +0 -0
  73. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/models/setpoint.py +0 -0
  74. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/restclient/__init__.py +0 -0
  75. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/restclient/exceptions.py +0 -0
  76. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/restclient/types.py +0 -0
  77. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/settings.py +0 -0
  78. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/testing/__init__.py +0 -0
  79. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/utils/__init__.py +0 -0
  80. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/utils/custom_model.py +0 -0
  81. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/utils/hub.py +0 -0
  82. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/version.py +0 -0
  83. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib/webhook.py +0 -0
  84. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/dependency_links.txt +0 -0
  85. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/not-zip-safe +0 -0
  86. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/requires.txt +0 -0
  87. {splight-lib-4.0.0.dev6 → splight-lib-4.3.0.dev1}/splight_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-lib
3
- Version: 4.0.0.dev6
3
+ Version: 4.3.0.dev1
4
4
  Summary: Library for public use. Splight
5
5
  Home-page: UNKNOWN
6
6
  Author: Splight
@@ -14,7 +14,7 @@ test_requires = [
14
14
 
15
15
  setup(
16
16
  name="splight-lib",
17
- version="4.0.0.dev6",
17
+ version="4.3.0.dev1",
18
18
  author="Splight",
19
19
  author_email="factory@splight-ae.com",
20
20
  packages=find_packages(),
@@ -13,12 +13,22 @@ class AbstractDatalakeClient(AbstractClient):
13
13
  kwargs["count_func"] = "None"
14
14
  return QuerySet(self, *args, **kwargs)
15
15
 
16
+ async def async_get(self, *args, **kwargs):
17
+ # TODO: consider using an async QuerySet
18
+ return await self._async_raw_get(*args, **kwargs)
19
+
16
20
  @abstractmethod
17
21
  def save(
18
22
  self, collection: str, instances: Union[List[Dict], Dict]
19
23
  ) -> List[dict]:
20
24
  pass
21
25
 
26
+ @abstractmethod
27
+ async def async_save(
28
+ self, collection: str, instances: Union[List[Dict], Dict]
29
+ ) -> List[dict]:
30
+ pass
31
+
22
32
  @abstractmethod
23
33
  def delete(self, collection: str, **kwargs) -> None:
24
34
  pass
@@ -65,6 +75,21 @@ class AbstractDatalakeClient(AbstractClient):
65
75
  ) -> List[Dict]:
66
76
  pass
67
77
 
78
+ @abstractmethod
79
+ async def _async_raw_get(
80
+ self,
81
+ resource_name: str,
82
+ collection: str,
83
+ limit_: int = 50,
84
+ skip_: int = 0,
85
+ sort: Union[List, str] = ["timestamp__desc"],
86
+ group_id: Optional[Union[List, str]] = None,
87
+ group_fields: Optional[Union[List, str]] = None,
88
+ tzinfo: timezone = timezone(timedelta()),
89
+ **filters,
90
+ ) -> List[Dict]:
91
+ pass
92
+
68
93
  @abstractmethod
69
94
  def execute_query(
70
95
  self,
@@ -49,6 +49,20 @@ class RemoteDatalakeClient(AbstractDatalakeClient, AbstractRemoteClient):
49
49
  response.raise_for_status()
50
50
  return instances
51
51
 
52
+ @retry(SPLIGHT_REQUEST_EXCEPTIONS, tries=3, delay=2, jitter=1)
53
+ async def async_save(
54
+ self,
55
+ collection: str,
56
+ instances: Union[List[Dict], Dict],
57
+ ) -> List[dict]:
58
+ instances = instances if isinstance(instances, list) else [instances]
59
+ url = self._base_url / f"{self._PREFIX}/save/"
60
+ response = await self._restclient.async_post(
61
+ url, params={"source": collection}, json=instances
62
+ )
63
+ response.raise_for_status()
64
+ return instances
65
+
52
66
  @retry(SPLIGHT_REQUEST_EXCEPTIONS, tries=3, delay=2, jitter=1)
53
67
  def _raw_get(
54
68
  self,
@@ -85,6 +99,42 @@ class RemoteDatalakeClient(AbstractDatalakeClient, AbstractRemoteClient):
85
99
 
86
100
  return output
87
101
 
102
+ @retry(SPLIGHT_REQUEST_EXCEPTIONS, tries=3, delay=2, jitter=1)
103
+ async def _async_raw_get(
104
+ self,
105
+ resource_name: str,
106
+ collection: str,
107
+ limit_: int = 50,
108
+ skip_: int = 0,
109
+ sort: Union[List, str] = ["timestamp__desc"],
110
+ group_id: Optional[Union[List, str]] = None,
111
+ group_fields: Optional[Union[List, str]] = None,
112
+ tzinfo: timezone = timezone(timedelta()),
113
+ **filters,
114
+ ) -> List[Dict]:
115
+ # GET /datalake/data/
116
+ url = self._base_url / f"{self._PREFIX}/data/"
117
+
118
+ filters.update(
119
+ {
120
+ "source": collection,
121
+ "output_format": resource_name,
122
+ "sort": sort,
123
+ "limit_": limit_,
124
+ "skip_": skip_,
125
+ "group_id": group_id,
126
+ "group_fields": group_fields,
127
+ # "tzinfo": tzinfo
128
+ }
129
+ )
130
+
131
+ params = self._parse_params(**filters)
132
+ response = await self._restclient.async_get(url, params=params)
133
+ response.raise_for_status()
134
+ output = response.json()["results"]
135
+
136
+ return output
137
+
88
138
  @retry(SPLIGHT_REQUEST_EXCEPTIONS, tries=3, delay=2, jitter=1)
89
139
  def delete(self, collection: str, **kwargs) -> None:
90
140
  # DELETE /datalake/delete/
@@ -1,10 +1,10 @@
1
- from abc import abstractmethod
2
1
  import os
3
2
  import sys
3
+ from abc import ABC, abstractmethod
4
4
  from functools import partial
5
5
  from tempfile import NamedTemporaryFile
6
6
  from time import sleep
7
- from typing import Dict, List, Optional, Type
7
+ from typing import Callable, Dict, List, Optional, Type
8
8
 
9
9
  from pydantic import BaseModel, create_model
10
10
 
@@ -37,11 +37,7 @@ from splight_lib.models.component import (
37
37
  )
38
38
  from splight_lib.models.event import EventNames
39
39
  from splight_lib.models.setpoint import SetPoint
40
- from splight_lib.restclient import (
41
- ConnectError,
42
- HTTPError,
43
- Timeout,
44
- )
40
+ from splight_lib.restclient import ConnectError, HTTPError, Timeout
45
41
  from splight_lib.settings import settings
46
42
 
47
43
  REQUEST_EXCEPTIONS = (ConnectError, HTTPError, Timeout)
@@ -78,9 +74,7 @@ class HealthCheckProcessor:
78
74
  if not is_alive:
79
75
  exc = self._engine.get_last_exception()
80
76
  self._log_exception(exc)
81
- self._logger.info(
82
- "Healthcheck finished", tags=LogTags.RUNTIME
83
- )
77
+ self._logger.info("Healthcheck finished", tags=LogTags.RUNTIME)
84
78
  self._health_file.close()
85
79
  self._logger.info(
86
80
  "Healthcheck file removed: %s",
@@ -101,7 +95,7 @@ class HealthCheckProcessor:
101
95
  self._logger.exception(exc, exc_info=(exc_type, exc, stack))
102
96
 
103
97
 
104
- class SplightBaseComponent:
98
+ class SplightBaseComponent(ABC):
105
99
  def __init__(
106
100
  self,
107
101
  component_id: Optional[str] = None,
@@ -159,6 +153,8 @@ class SplightBaseComponent:
159
153
  self._custom_types = self._get_custom_type_model(component_objects)
160
154
  self._routines = self._get_routine_model(routines_objects)
161
155
 
156
+ self.start = self._wrap_start(self.start)
157
+
162
158
  @property
163
159
  def input(self) -> BaseModel:
164
160
  return self._input
@@ -185,6 +181,30 @@ class SplightBaseComponent:
185
181
  else:
186
182
  sys.exit(0)
187
183
 
184
+ def _wrap_start(self, original_start: Callable) -> Callable:
185
+ """Wraps the start method to wait for all threads to finish.
186
+
187
+ Parameters
188
+ ----------
189
+ original_start: Callable
190
+ The implemented start method from the component
191
+
192
+ Returns
193
+ -------
194
+ Callable
195
+ The start method wrapped
196
+ """
197
+
198
+ def wrapper():
199
+ original_start()
200
+ for thread in self._execution_engine.threads:
201
+ thread.join()
202
+
203
+ self._health_check_thread.join()
204
+ self._register_exit()
205
+
206
+ return wrapper
207
+
188
208
  def _get_custom_type_model(
189
209
  self, component_object: Dict[str, Type[ComponentObjectInstance]]
190
210
  ) -> BaseModel:
@@ -300,20 +320,10 @@ class SplightBaseComponent:
300
320
  ),
301
321
  )
302
322
 
323
+ @abstractmethod
303
324
  def start(self):
304
- self.run_component()
305
-
306
- # The following it for waiting for all threads to finish
307
- for thread in self._execution_engine.threads:
308
- thread.join()
309
-
310
- self._health_check_thread.join()
311
- self._register_exit()
325
+ raise NotImplementedError()
312
326
 
313
327
  def stop(self):
314
328
  self._execution_engine.terminate_all()
315
329
  sys.exit(1)
316
-
317
- @abstractmethod
318
- def run_component(self) -> None:
319
- raise NotImplementedError()
@@ -1,4 +1,3 @@
1
- import atexit
2
1
  import sys
3
2
  import threading
4
3
  import time
@@ -153,17 +152,20 @@ class TaskMap:
153
152
 
154
153
 
155
154
  class Scheduler:
156
- def __init__(self, *args, **kwargs) -> None:
155
+ def __init__(self, event: Event) -> None:
156
+ # The _event attr is used for controling the scheduler loop meanwhile
157
+ # the _tasks_event is used to notify the scheduler that a task should
158
+ # be executed
159
+ self._event = event
157
160
  self._tasks = TaskMap()
158
- self._event = Event()
161
+ self._tasks_event = Event()
159
162
  self._mutex = Lock()
160
163
  self._to_add: List[Task] = []
161
164
  self._to_remove: List[Task] = []
162
165
 
163
166
  def start(self) -> None:
164
167
  """Scheduler infinite loop."""
165
- self._stop = False
166
- while not self._stop:
168
+ while self._event.is_set():
167
169
  # Update the task list
168
170
  self._update_task_list()
169
171
  # Run the scheduler task
@@ -172,11 +174,12 @@ class Scheduler:
172
174
  next_event_time = near_event - time.time()
173
175
 
174
176
  # Wait until next event or someone trigger the event
175
- self._event.wait(timeout=next_event_time)
176
- self._event.clear()
177
+ self._tasks_event.wait(timeout=next_event_time)
178
+ self._tasks_event.clear()
177
179
 
178
180
  def stop(self) -> None:
179
- self._stop = True
181
+ if self._event.is_set():
182
+ self._event.clear()
180
183
 
181
184
  def _schedule(self) -> float:
182
185
  """Scheduler main task."""
@@ -197,7 +200,7 @@ class Scheduler:
197
200
  Unlock the scheduler if is locked.
198
201
  This will run the scheduler main task.
199
202
  """
200
- self._event.set()
203
+ self._tasks_event.set()
201
204
 
202
205
  def schedule(self, task: Task) -> None:
203
206
  """
@@ -242,20 +245,8 @@ class Thread(DefaultThread):
242
245
  def __init__(self, target: Callable, args: Tuple = (), **kwargs) -> None:
243
246
  target = self.store_result(target)
244
247
  self.result = Empty()
245
- self._exc = None
246
248
  super().__init__(target=target, args=args, name=target, **kwargs)
247
249
 
248
- @property
249
- def exc(self):
250
- return self._exc
251
-
252
- def run(self):
253
- try:
254
- super().run()
255
- except Exception as exc:
256
- self._exc = exc
257
- raise exc
258
-
259
250
  def store_result(self, func: Callable) -> Callable:
260
251
  @wraps(func)
261
252
  def wrapper(*args, **kwargs):
@@ -288,6 +279,7 @@ class Popen(DefaultPopen):
288
279
 
289
280
  class ExecutionClient(AbstractClient):
290
281
  def __init__(self, *args, **kwargs):
282
+ self._event = Event()
291
283
  self.processes: List[Popen] = []
292
284
  self.threads: List[Thread] = []
293
285
 
@@ -297,6 +289,10 @@ class ExecutionClient(AbstractClient):
297
289
  self._exc = None
298
290
  self._thread_exc = None
299
291
 
292
+ @property
293
+ def event(self) -> Event:
294
+ return self._event
295
+
300
296
  def _register_exit_functions(self) -> None:
301
297
  excepthook = sys.excepthook
302
298
  thread_exchook = threading.excepthook
@@ -315,7 +311,6 @@ class ExecutionClient(AbstractClient):
315
311
  self.terminate_all()
316
312
  thread_exchook(args)
317
313
 
318
- # atexit.register(self.terminate_all)
319
314
  sys.excepthook = wrap_excepthook
320
315
  threading.excepthook = wrap_thread_excepthook
321
316
 
@@ -323,13 +318,17 @@ class ExecutionClient(AbstractClient):
323
318
  self.terminate_all()
324
319
 
325
320
  def terminate_all(self) -> None:
326
- if hasattr(self, "_scheduler"):
327
- self._scheduler.stop()
321
+ # Set the event to False to stop all threads
322
+ # This will also stop the scheduler
323
+ self._event.clear()
328
324
 
329
325
  for p in self.processes:
330
326
  p.terminate()
331
327
 
332
328
  def start(self, job=Union[Popen, Thread, Task]):
329
+ # Set the event in true so the threads can run
330
+ if not self._event.is_set():
331
+ self._event.set()
333
332
  logger.info("Executing new job.", tags=LogTags.RUNTIME)
334
333
  if isinstance(job, Popen):
335
334
  return self._start_process(job)
@@ -361,7 +360,7 @@ class ExecutionClient(AbstractClient):
361
360
  logger.debug("Starting Task", tags=LogTags.RUNTIME)
362
361
  if not getattr(self, "_scheduler", None):
363
362
  # Instantiate and start Scheduler thread
364
- self._scheduler = Scheduler()
363
+ self._scheduler = Scheduler(self._event)
365
364
  self._start_thread(
366
365
  Thread(target=self._scheduler.start, daemon=False)
367
366
  )
@@ -373,23 +372,8 @@ class ExecutionClient(AbstractClient):
373
372
  return self._scheduler.unschedule(job)
374
373
 
375
374
  def is_alive(self) -> bool:
376
- # We need to also check the main thread just in case some component is
377
- # not using the execution client explicitly and is doing some
378
- # processing directly in the main thread so the healtcheck should
379
- # say the component is healthy.
380
- # main_thread = threading.main_thread()
381
- # threads_status = [
382
- # p.is_alive() or p.exit_ok() for p in self.processes + self.threads
383
- # ]
384
- threads_status = [
385
- p.is_alive() for p in self.processes + self.threads
386
- ]
375
+ threads_status = [p.is_alive() for p in self.processes + self.threads]
387
376
  return all(threads_status)
388
- # (
389
- # main_thread.is_alive() or all(threads_status)
390
- # if threads_status
391
- # else main_thread.is_alive()
392
- # )
393
377
 
394
378
  def healthcheck(self) -> Tuple[bool, ComponentStatus]:
395
379
  """Check if the component is alive and return the status.
@@ -414,16 +398,10 @@ class ExecutionClient(AbstractClient):
414
398
  def get_last_exception(self) -> Optional[Exception]:
415
399
  """Get the last exception thrown in one of the threads.
416
400
  It assumes that there is only one thread that crashed
417
- Also, only works for the thread not the processes.
401
+ It only works for the thread not the processes.
418
402
  """
419
- # broken_thread = [x.exc for x in self.threads if x.exc]
420
403
  if self._exc:
421
404
  return self._exc[1]
422
405
  if self._thread_exc:
423
406
  return self._thread_exc[1]
424
- # if broken_thread:
425
- # print("THREAD EXC")
426
- # print(self._thread_exc)
427
- # print()
428
- # return broken_thread[0]
429
407
  return None
@@ -4,6 +4,7 @@ from geojson_pydantic import GeometryCollection
4
4
 
5
5
  from splight_lib.models.attribute import Attribute
6
6
  from splight_lib.models.base import SplightDatabaseBaseModel
7
+ from splight_lib.models.metadata import Metadata
7
8
 
8
9
 
9
10
  class Asset(SplightDatabaseBaseModel):
@@ -12,6 +13,7 @@ class Asset(SplightDatabaseBaseModel):
12
13
  description: Optional[str] = None
13
14
  tags: List[str] = []
14
15
  attributes: List[Attribute] = []
16
+ metadata: List[Metadata] = []
15
17
  verified: bool = False
16
18
  geometry: Optional[GeometryCollection]
17
19
  centroid_coordinates: Optional[Tuple[float, float]]
@@ -94,6 +94,19 @@ class SplightDatalakeBaseModel(BaseModel):
94
94
  instances = [cls.parse_obj(item) for item in instances]
95
95
  return instances
96
96
 
97
+ @classmethod
98
+ async def async_get(
99
+ cls, **params: Dict
100
+ ) -> List["SplightDatalakeBaseModel"]:
101
+ dl_client = cls.__get_datalake_client()
102
+ instances = await dl_client.async_get(
103
+ resource_name=cls.__name__,
104
+ collection=cls._collection_name,
105
+ **params,
106
+ )
107
+ instances = [cls.parse_obj(item) for item in instances]
108
+ return instances
109
+
97
110
  @classmethod
98
111
  def get_dataframe(cls, **params: Dict) -> pd.DataFrame:
99
112
  dl_client = cls.__get_datalake_client()
@@ -111,6 +124,14 @@ class SplightDatalakeBaseModel(BaseModel):
111
124
  instances=json.loads(self.json()),
112
125
  )
113
126
 
127
+ async def async_save(self):
128
+ dl_client = self.__get_datalake_client()
129
+
130
+ await dl_client.async_save(
131
+ collection=self._collection_name,
132
+ instances=json.loads(self.json()),
133
+ )
134
+
114
135
  @classmethod
115
136
  def save_dataframe(cls, dataframe: pd.DataFrame):
116
137
  dl_client = cls.__get_datalake_client()
@@ -0,0 +1,20 @@
1
+ from enum import auto
2
+ from typing import Optional
3
+
4
+ from strenum import PascalCaseStrEnum
5
+
6
+ from splight_lib.models.base import SplightDatabaseBaseModel
7
+
8
+
9
+ class MetadataType(PascalCaseStrEnum):
10
+ NUMBER = auto()
11
+ BOOLEAN = auto()
12
+ STRING = auto()
13
+
14
+
15
+ class Metadata(SplightDatabaseBaseModel):
16
+ id: Optional[str]
17
+ name: Optional[str]
18
+ asset: Optional[str]
19
+ type: MetadataType = MetadataType.NUMBER
20
+ value: Optional[str]
@@ -24,6 +24,11 @@ class NativeOutput(SplightDatalakeBaseModel):
24
24
  params["output_format"] = cls._output_format
25
25
  return super().get(**params)
26
26
 
27
+ @classmethod
28
+ async def async_get(cls, **params: Dict) -> List["NativeOutput"]:
29
+ params["output_format"] = cls._output_format
30
+ return await super().async_get(**params)
31
+
27
32
  @classmethod
28
33
  def get_dataframe(cls, **params: Dict) -> pd.DataFrame:
29
34
  params["output_format"] = cls._output_format
@@ -183,9 +183,33 @@ class SplightRestClient:
183
183
  default_encoding=default_encoding,
184
184
  event_hooks=event_hooks,
185
185
  )
186
+ self._async_client = httpx.AsyncClient(
187
+ auth=auth,
188
+ params=params,
189
+ headers=headers,
190
+ cookies=cookies,
191
+ timeout=timeout,
192
+ verify=verify,
193
+ cert=cert,
194
+ http1=http1,
195
+ http2=http2,
196
+ proxies=proxies,
197
+ follow_redirects=allow_redirects,
198
+ base_url=base_url,
199
+ limits=limits,
200
+ max_redirects=max_redirects,
201
+ transport=transport,
202
+ app=app,
203
+ trust_env=trust_env,
204
+ default_encoding=default_encoding,
205
+ event_hooks=event_hooks,
206
+ )
186
207
 
187
208
  def update_headers(self, new_headers: HeaderTypes):
188
209
  self._client.headers = self._client._merge_headers(new_headers)
210
+ self._async_client.headers = self._async_client._merge_headers(
211
+ new_headers
212
+ )
189
213
 
190
214
  def get(
191
215
  self,
@@ -214,6 +238,33 @@ class SplightRestClient:
214
238
  )
215
239
  return SplightResponse.from_response(raw_response)
216
240
 
241
+ async def async_get(
242
+ self,
243
+ url: URLTypes,
244
+ *,
245
+ params: Optional[QueryParamTypes] = None,
246
+ headers: Optional[HeaderTypes] = None,
247
+ cookies: Optional[CookieTypes] = None,
248
+ auth: Union[AuthTypes, DefaultClient] = DEFAULT_CLIENT,
249
+ allow_redirects: Union[bool, DefaultClient] = DEFAULT_CLIENT,
250
+ timeout: Union[TimeoutTypes, DefaultClient] = DEFAULT_CLIENT,
251
+ ) -> SplightResponse:
252
+ """Send a GET request to the specified URL.
253
+
254
+ Parameters: See class docstring.
255
+ """
256
+ raw_response = await self._async_client.request(
257
+ self._GET_METHOD,
258
+ str(url),
259
+ params=params,
260
+ headers=headers,
261
+ cookies=cookies,
262
+ auth=auth,
263
+ follow_redirects=allow_redirects,
264
+ timeout=timeout,
265
+ )
266
+ return SplightResponse.from_response(raw_response)
267
+
217
268
  def options(
218
269
  self,
219
270
  url: URLTypes,
@@ -301,6 +352,39 @@ class SplightRestClient:
301
352
  )
302
353
  return SplightResponse.from_response(raw_response)
303
354
 
355
+ async def async_post(
356
+ self,
357
+ url: URLTypes,
358
+ *,
359
+ data: Optional[RequestData] = None,
360
+ files: Optional[RequestFiles] = None,
361
+ json: Optional[Any] = None,
362
+ params: Optional[QueryParamTypes] = None,
363
+ headers: Optional[HeaderTypes] = None,
364
+ cookies: Optional[CookieTypes] = None,
365
+ auth: Union[AuthTypes, DefaultClient] = DEFAULT_CLIENT,
366
+ allow_redirects: Union[bool, DefaultClient] = DEFAULT_CLIENT,
367
+ timeout: Union[TimeoutTypes, DefaultClient] = DEFAULT_CLIENT,
368
+ ) -> SplightResponse:
369
+ """Send a POST request to the specified URL.
370
+
371
+ Parameters: See class docstring.
372
+ """
373
+ raw_response = await self._async_client.request(
374
+ self._POST_METHOD,
375
+ str(url),
376
+ data=data,
377
+ files=files,
378
+ json=json,
379
+ params=params,
380
+ headers=headers,
381
+ cookies=cookies,
382
+ auth=auth,
383
+ follow_redirects=allow_redirects,
384
+ timeout=timeout,
385
+ )
386
+ return SplightResponse.from_response(raw_response)
387
+
304
388
  def put(
305
389
  self,
306
390
  url: URLTypes,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-lib
3
- Version: 4.0.0.dev6
3
+ Version: 4.3.0.dev1
4
4
  Summary: Library for public use. Splight
5
5
  Home-page: UNKNOWN
6
6
  Author: Splight
@@ -69,6 +69,7 @@ splight_lib/models/event.py
69
69
  splight_lib/models/exceptions.py
70
70
  splight_lib/models/file.py
71
71
  splight_lib/models/hub.py
72
+ splight_lib/models/metadata.py
72
73
  splight_lib/models/native.py
73
74
  splight_lib/models/pipeline.py
74
75
  splight_lib/models/query.py