splight-lib 4.0.0.dev5__tar.gz → 4.1.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.
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/PKG-INFO +1 -1
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/setup.py +1 -1
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/abstract.py +25 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/remote_client.py +50 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/component/abstract.py +42 -21
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/execution.py +46 -40
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/asset.py +2 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/base.py +21 -0
- splight-lib-4.1.0.dev1/splight_lib/models/metadata.py +20 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/native.py +5 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/restclient/client.py +84 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/PKG-INFO +1 -1
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/SOURCES.txt +1 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/LICENSE.txt +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/README.md +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/pyproject.toml +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/setup.cfg +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/abstract/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/abstract/client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/auth/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/auth/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/auth/mac_auth.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/auth/token.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/abstract.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/classmap.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/local_client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/remote_client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/abstract.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/builder.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/classmap.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/local_client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/remote_client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/builder.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/local_client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/file_handler.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/filter.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/hub/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/hub/abstract.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/hub/client.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/communication/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/communication/event_handler.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/component/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/component/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/component/spec.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/constants.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/encryption.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/logging/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/logging/_internal.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/logging/component.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/logging/constants.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/logging/logging.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/alert.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/attribute.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/communication.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/component.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/dashboard.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/data_address.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/event.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/file.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/hub.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/pipeline.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/query.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/secret.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/models/setpoint.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/restclient/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/restclient/exceptions.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/restclient/types.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/settings.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/testing/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/utils/__init__.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/utils/custom_model.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/utils/hub.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/version.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/webhook.py +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/dependency_links.txt +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/not-zip-safe +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/requires.txt +0 -0
- {splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib.egg-info/top_level.txt +0 -0
|
@@ -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,
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/remote_client.py
RENAMED
|
@@ -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,16 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
-
import
|
|
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
|
-
from furl import furl
|
|
10
9
|
from pydantic import BaseModel, create_model
|
|
11
|
-
from retry import retry
|
|
12
10
|
|
|
13
|
-
from splight_lib.auth import SplightAuthToken
|
|
14
11
|
from splight_lib.client.communication import RemoteCommunicationClient
|
|
15
12
|
from splight_lib.communication.event_handler import (
|
|
16
13
|
command_event_handler,
|
|
@@ -18,7 +15,6 @@ from splight_lib.communication.event_handler import (
|
|
|
18
15
|
setpoint_event_handler,
|
|
19
16
|
)
|
|
20
17
|
from splight_lib.component.exceptions import (
|
|
21
|
-
DuplicatedComponentException,
|
|
22
18
|
InvalidBidingObject,
|
|
23
19
|
MissingBindingCallback,
|
|
24
20
|
MissingCommandCallback,
|
|
@@ -41,12 +37,7 @@ from splight_lib.models.component import (
|
|
|
41
37
|
)
|
|
42
38
|
from splight_lib.models.event import EventNames
|
|
43
39
|
from splight_lib.models.setpoint import SetPoint
|
|
44
|
-
from splight_lib.restclient import
|
|
45
|
-
ConnectError,
|
|
46
|
-
HTTPError,
|
|
47
|
-
SplightRestClient,
|
|
48
|
-
Timeout,
|
|
49
|
-
)
|
|
40
|
+
from splight_lib.restclient import ConnectError, HTTPError, Timeout
|
|
50
41
|
from splight_lib.settings import settings
|
|
51
42
|
|
|
52
43
|
REQUEST_EXCEPTIONS = (ConnectError, HTTPError, Timeout)
|
|
@@ -83,16 +74,14 @@ class HealthCheckProcessor:
|
|
|
83
74
|
if not is_alive:
|
|
84
75
|
exc = self._engine.get_last_exception()
|
|
85
76
|
self._log_exception(exc)
|
|
86
|
-
self._logger.
|
|
87
|
-
"Healthcheck task failed.", tags=LogTags.RUNTIME
|
|
88
|
-
)
|
|
77
|
+
self._logger.info("Healthcheck finished", tags=LogTags.RUNTIME)
|
|
89
78
|
self._health_file.close()
|
|
90
|
-
self._logger.
|
|
79
|
+
self._logger.info(
|
|
91
80
|
"Healthcheck file removed: %s",
|
|
92
81
|
self._health_file,
|
|
93
82
|
tags=LogTags.RUNTIME,
|
|
94
83
|
)
|
|
95
|
-
|
|
84
|
+
break
|
|
96
85
|
sleep(self._HEALTHCHECK_INTERVAL)
|
|
97
86
|
|
|
98
87
|
def stop(self):
|
|
@@ -106,7 +95,7 @@ class HealthCheckProcessor:
|
|
|
106
95
|
self._logger.exception(exc, exc_info=(exc_type, exc, stack))
|
|
107
96
|
|
|
108
97
|
|
|
109
|
-
class SplightBaseComponent:
|
|
98
|
+
class SplightBaseComponent(ABC):
|
|
110
99
|
def __init__(
|
|
111
100
|
self,
|
|
112
101
|
component_id: Optional[str] = None,
|
|
@@ -121,9 +110,9 @@ class SplightBaseComponent:
|
|
|
121
110
|
instance_id=component_id,
|
|
122
111
|
)
|
|
123
112
|
self._execution_engine = ExecutionClient()
|
|
124
|
-
|
|
113
|
+
self._health_check = HealthCheckProcessor(self._execution_engine)
|
|
125
114
|
self._health_check_thread = Thread(
|
|
126
|
-
target=
|
|
115
|
+
target=self._health_check.start, args=(), daemon=False
|
|
127
116
|
)
|
|
128
117
|
# We can't add the healthcheck thread into the execution engine
|
|
129
118
|
# because that thread should stop if any of the registered threads
|
|
@@ -164,6 +153,8 @@ class SplightBaseComponent:
|
|
|
164
153
|
self._custom_types = self._get_custom_type_model(component_objects)
|
|
165
154
|
self._routines = self._get_routine_model(routines_objects)
|
|
166
155
|
|
|
156
|
+
self.start = self._wrap_start(self.start)
|
|
157
|
+
|
|
167
158
|
@property
|
|
168
159
|
def input(self) -> BaseModel:
|
|
169
160
|
return self._input
|
|
@@ -184,6 +175,36 @@ class SplightBaseComponent:
|
|
|
184
175
|
def execution_engine(self) -> ExecutionClient:
|
|
185
176
|
return self._execution_engine
|
|
186
177
|
|
|
178
|
+
def _register_exit(self):
|
|
179
|
+
if self._execution_engine.get_last_exception():
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
else:
|
|
182
|
+
sys.exit(0)
|
|
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
|
+
|
|
187
208
|
def _get_custom_type_model(
|
|
188
209
|
self, component_object: Dict[str, Type[ComponentObjectInstance]]
|
|
189
210
|
) -> BaseModel:
|
|
@@ -299,10 +320,10 @@ class SplightBaseComponent:
|
|
|
299
320
|
),
|
|
300
321
|
)
|
|
301
322
|
|
|
323
|
+
@abstractmethod
|
|
302
324
|
def start(self):
|
|
303
325
|
raise NotImplementedError()
|
|
304
326
|
|
|
305
327
|
def stop(self):
|
|
306
|
-
self._execution_engine.stop(self._health_check_thread)
|
|
307
328
|
self._execution_engine.terminate_all()
|
|
308
329
|
sys.exit(1)
|
|
@@ -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,
|
|
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.
|
|
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.
|
|
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.
|
|
176
|
-
self.
|
|
177
|
+
self._tasks_event.wait(timeout=next_event_time)
|
|
178
|
+
self._tasks_event.clear()
|
|
177
179
|
|
|
178
180
|
def stop(self) -> None:
|
|
179
|
-
self.
|
|
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.
|
|
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,31 +279,56 @@ 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
|
|
|
294
286
|
self._register_exit_functions()
|
|
295
287
|
super(ExecutionClient, self).__init__(*args, **kwargs)
|
|
296
288
|
logger.info("Execution client initialized.", tags=LogTags.RUNTIME)
|
|
289
|
+
self._exc = None
|
|
290
|
+
self._thread_exc = None
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def event(self) -> Event:
|
|
294
|
+
return self._event
|
|
297
295
|
|
|
298
296
|
def _register_exit_functions(self) -> None:
|
|
299
297
|
excepthook = sys.excepthook
|
|
298
|
+
thread_exchook = threading.excepthook
|
|
300
299
|
|
|
301
300
|
def wrap_excepthook(type, value, traceback):
|
|
301
|
+
self._exc = (type, value, traceback)
|
|
302
302
|
self.terminate_all()
|
|
303
303
|
excepthook(type, value, traceback)
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
def wrap_thread_excepthook(args):
|
|
306
|
+
self._thread_exc = (
|
|
307
|
+
args.exc_type,
|
|
308
|
+
args.exc_value,
|
|
309
|
+
args.exc_traceback,
|
|
310
|
+
)
|
|
311
|
+
self.terminate_all()
|
|
312
|
+
thread_exchook(args)
|
|
313
|
+
|
|
306
314
|
sys.excepthook = wrap_excepthook
|
|
315
|
+
threading.excepthook = wrap_thread_excepthook
|
|
307
316
|
|
|
308
317
|
def __del__(self) -> None:
|
|
309
318
|
self.terminate_all()
|
|
310
319
|
|
|
311
320
|
def terminate_all(self) -> None:
|
|
321
|
+
# Set the event to False to stop all threads
|
|
322
|
+
# This will also stop the scheduler
|
|
323
|
+
self._event.clear()
|
|
324
|
+
|
|
312
325
|
for p in self.processes:
|
|
313
326
|
p.terminate()
|
|
314
327
|
|
|
315
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()
|
|
316
332
|
logger.info("Executing new job.", tags=LogTags.RUNTIME)
|
|
317
333
|
if isinstance(job, Popen):
|
|
318
334
|
return self._start_process(job)
|
|
@@ -344,7 +360,7 @@ class ExecutionClient(AbstractClient):
|
|
|
344
360
|
logger.debug("Starting Task", tags=LogTags.RUNTIME)
|
|
345
361
|
if not getattr(self, "_scheduler", None):
|
|
346
362
|
# Instantiate and start Scheduler thread
|
|
347
|
-
self._scheduler = Scheduler()
|
|
363
|
+
self._scheduler = Scheduler(self._event)
|
|
348
364
|
self._start_thread(
|
|
349
365
|
Thread(target=self._scheduler.start, daemon=False)
|
|
350
366
|
)
|
|
@@ -356,19 +372,8 @@ class ExecutionClient(AbstractClient):
|
|
|
356
372
|
return self._scheduler.unschedule(job)
|
|
357
373
|
|
|
358
374
|
def is_alive(self) -> bool:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
# processing directly in the main thread so the healtcheck should
|
|
362
|
-
# say the component is healthy.
|
|
363
|
-
main_thread = threading.main_thread()
|
|
364
|
-
threads_status = [
|
|
365
|
-
p.is_alive() or p.exit_ok() for p in self.processes + self.threads
|
|
366
|
-
]
|
|
367
|
-
return (
|
|
368
|
-
main_thread.is_alive() or all(threads_status)
|
|
369
|
-
if threads_status
|
|
370
|
-
else main_thread.is_alive()
|
|
371
|
-
)
|
|
375
|
+
threads_status = [p.is_alive() for p in self.processes + self.threads]
|
|
376
|
+
return all(threads_status)
|
|
372
377
|
|
|
373
378
|
def healthcheck(self) -> Tuple[bool, ComponentStatus]:
|
|
374
379
|
"""Check if the component is alive and return the status.
|
|
@@ -393,9 +398,10 @@ class ExecutionClient(AbstractClient):
|
|
|
393
398
|
def get_last_exception(self) -> Optional[Exception]:
|
|
394
399
|
"""Get the last exception thrown in one of the threads.
|
|
395
400
|
It assumes that there is only one thread that crashed
|
|
396
|
-
|
|
401
|
+
It only works for the thread not the processes.
|
|
397
402
|
"""
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
if self._exc:
|
|
404
|
+
return self._exc[1]
|
|
405
|
+
if self._thread_exc:
|
|
406
|
+
return self._thread_exc[1]
|
|
401
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,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/__init__.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/abstract.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/classmap.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/exceptions.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/local_client.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/communication/remote_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/local_client.py
RENAMED
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/database/remote_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/client/datalake/local_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-4.0.0.dev5 → splight-lib-4.1.0.dev1}/splight_lib/communication/event_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|