splight-lib 5.7.1__tar.gz → 5.8.0.dev0__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-5.7.1 → splight_lib-5.8.0.dev0}/PKG-INFO +1 -1
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/pyproject.toml +1 -1
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/database/__init__.py +0 -6
- splight_lib-5.8.0.dev0/splight_lib/client/database/builder.py +11 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/database/classmap.py +2 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/database/remote_client.py +31 -4
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/__init__.py +0 -2
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/builder.py +0 -2
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/__init__.py +2 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/base.py +8 -5
- splight_lib-5.8.0.dev0/splight_lib/models/exceptions.py +66 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/hub.py +1 -6
- splight_lib-5.8.0.dev0/splight_lib/models/hub_solution.py +107 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/pipeline.py +0 -1
- splight_lib-5.8.0.dev0/splight_lib/models/solution.py +161 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/settings.py +2 -16
- splight_lib-5.8.0.dev0/splight_lib/solution/__init__.py +3 -0
- splight_lib-5.8.0.dev0/splight_lib/solution/exceptions.py +8 -0
- splight_lib-5.8.0.dev0/splight_lib/solution/solution.py +23 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/tests/test_api_contracts.py +1 -15
- splight_lib-5.7.1/splight_lib/client/database/builder.py +0 -17
- splight_lib-5.7.1/splight_lib/client/database/local_client.py +0 -187
- splight_lib-5.7.1/splight_lib/client/datalake/local_client.py +0 -119
- splight_lib-5.7.1/splight_lib/models/exceptions.py +0 -38
- splight_lib-5.7.1/splight_lib/models/hub_solution.py +0 -49
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/LICENSE.txt +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/README.md +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/abstract/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/abstract/client.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/auth/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/auth/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/auth/mac_auth.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/auth/token.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/database/abstract.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/abstract.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/buffer.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/remote_client.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/datalake/schemas.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/file_handler.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/filter.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/hub/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/hub/abstract.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/hub/client.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/tests/test_database.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/client/tests/test_datalake.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/abstract.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/spec.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/tests/test_abstract.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/component/tests/test_spec.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/conftest.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/constants.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/encryption.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/engine.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/scheduling.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/task.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/tests/test_execution.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/tests/test_scheduling.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/execution/trigger.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/_internal.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/component.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/constants.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/logging.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/logging/tests/test_logging.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/alert.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/asset.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/attribute.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/component.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/dashboard.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/data_address.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/file.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/function.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/generic.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/metadata.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/native.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/secret.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/tests/models.json +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/tests/test_component_object_instance.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/tests/test_database_model.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/tests/test_metadata.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/models/tests/test_models.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/restclient/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/restclient/client.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/restclient/exceptions.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/restclient/tests/test_restclient.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/restclient/types.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/stringcase.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/testing/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/tests/FakeProc.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/tests/asset_geometries.json +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/tests/test_encryption.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/utils/__init__.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/utils/custom_model.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/utils/hub.py +0 -0
- {splight_lib-5.7.1 → splight_lib-5.8.0.dev0}/splight_lib/version.py +0 -0
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
from splight_lib.client.database.builder import DatabaseClientBuilder
|
|
2
|
-
from splight_lib.client.database.local_client import (
|
|
3
|
-
LOCAL_DB_FILE,
|
|
4
|
-
LocalDatabaseClient,
|
|
5
|
-
)
|
|
6
2
|
from splight_lib.client.database.remote_client import RemoteDatabaseClient
|
|
7
3
|
|
|
8
4
|
__all__ = [
|
|
9
|
-
LocalDatabaseClient,
|
|
10
5
|
DatabaseClientBuilder,
|
|
11
6
|
RemoteDatabaseClient,
|
|
12
|
-
LOCAL_DB_FILE,
|
|
13
7
|
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from splight_lib.client.database.abstract import AbstractDatabaseClient
|
|
4
|
+
from splight_lib.client.database.remote_client import RemoteDatabaseClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatabaseClientBuilder:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def build(parameters: Dict[str, Any] = {}) -> AbstractDatabaseClient:
|
|
10
|
+
db_client = RemoteDatabaseClient(**parameters)
|
|
11
|
+
return db_client
|
|
@@ -20,8 +20,10 @@ MODEL_NAME_MAP = {
|
|
|
20
20
|
"routineobject": f"{ENGINE_PREFIX}/component/routines/",
|
|
21
21
|
"secret": f"{ENGINE_PREFIX}/secrets/",
|
|
22
22
|
"setpoint": f"{ENGINE_PREFIX}/setpoints/",
|
|
23
|
+
"solution": f"{ENGINE_PREFIX}/solution/solutions/",
|
|
23
24
|
"tab": f"{ENGINE_PREFIX}/dashboard/tabs/",
|
|
24
25
|
"hubsolution": f"{HUB_PREFIX}/solution/solutions/",
|
|
26
|
+
"hubsolutionversion": f"{HUB_PREFIX}/solution/versions/",
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
CUSTOM_PATHS_MAP = {
|
|
@@ -192,7 +192,7 @@ class RemoteDatabaseClient(AbstractDatabaseClient, AbstractRemoteClient):
|
|
|
192
192
|
self,
|
|
193
193
|
resource_name: str,
|
|
194
194
|
instance: Dict,
|
|
195
|
-
|
|
195
|
+
type_: Optional[str] = None,
|
|
196
196
|
**kwargs,
|
|
197
197
|
) -> NamedTemporaryFile:
|
|
198
198
|
"""Returns the number of resources in the database for a given model
|
|
@@ -211,12 +211,13 @@ class RemoteDatabaseClient(AbstractDatabaseClient, AbstractRemoteClient):
|
|
|
211
211
|
------
|
|
212
212
|
InvalidModelName thrown when the model name is not correct.
|
|
213
213
|
"""
|
|
214
|
-
if resource_name.lower()
|
|
214
|
+
if resource_name.lower() not in ["file", "hubsolutionversion"]:
|
|
215
215
|
raise InvalidModel("Only files can be downloaded.")
|
|
216
216
|
api_path = self._get_api_path(resource_name)
|
|
217
217
|
resource_id = instance.get("id")
|
|
218
218
|
url = self._base_url / api_path / f"{resource_id}/download_url/"
|
|
219
|
-
|
|
219
|
+
params = {"type": type_} if type_ else {}
|
|
220
|
+
response = self._restclient.get(url, params=params)
|
|
220
221
|
if response.is_error:
|
|
221
222
|
raise RequestError(response.status_code, response.text)
|
|
222
223
|
download_url = response.json().get("url")
|
|
@@ -354,7 +355,33 @@ class RemoteDatabaseClient(AbstractDatabaseClient, AbstractRemoteClient):
|
|
|
354
355
|
upload_url,
|
|
355
356
|
files=file,
|
|
356
357
|
)
|
|
357
|
-
if response.
|
|
358
|
+
if response.ok:
|
|
359
|
+
raise RequestError(response.status_code, response.text)
|
|
360
|
+
logger.debug(
|
|
361
|
+
"File uploaded succesfully %s.", resource_id, tags=LogTags.DATABASE
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def upload(
|
|
365
|
+
self,
|
|
366
|
+
resource_name: str,
|
|
367
|
+
instance: Dict,
|
|
368
|
+
file_path: str,
|
|
369
|
+
type_: Optional[str] = None,
|
|
370
|
+
):
|
|
371
|
+
api_path = self._get_api_path(resource_name)
|
|
372
|
+
resource_id = instance.get("id")
|
|
373
|
+
params = {"type": type_} if type_ else {}
|
|
374
|
+
url = self._base_url / api_path / f"{resource_id}/upload_url/"
|
|
375
|
+
response = self._restclient.get(url, params=params)
|
|
376
|
+
if response.is_error:
|
|
377
|
+
raise RequestError(response.status_code, response.text)
|
|
378
|
+
upload_url = response.json().get("url")
|
|
379
|
+
with open(file_path, "rb") as fid:
|
|
380
|
+
response = requests.put(
|
|
381
|
+
upload_url,
|
|
382
|
+
data=fid,
|
|
383
|
+
)
|
|
384
|
+
if not response.ok:
|
|
358
385
|
raise RequestError(response.status_code, response.text)
|
|
359
386
|
logger.debug(
|
|
360
387
|
"File uploaded succesfully %s.", resource_id, tags=LogTags.DATABASE
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from splight_lib.client.datalake.builder import DatalakeClientBuilder
|
|
2
|
-
from splight_lib.client.datalake.local_client import LocalDatalakeClient
|
|
3
2
|
from splight_lib.client.datalake.remote_client import (
|
|
4
3
|
BufferedAsyncRemoteDatalakeClient,
|
|
5
4
|
BufferedSyncRemoteDataClient,
|
|
@@ -11,5 +10,4 @@ __all__ = [
|
|
|
11
10
|
SyncRemoteDatalakeClient,
|
|
12
11
|
BufferedAsyncRemoteDatalakeClient,
|
|
13
12
|
BufferedSyncRemoteDataClient,
|
|
14
|
-
LocalDatalakeClient,
|
|
15
13
|
]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from typing import Any, Dict
|
|
2
2
|
|
|
3
3
|
from splight_lib.client.datalake.abstract import AbstractDatalakeClient
|
|
4
|
-
from splight_lib.client.datalake.local_client import LocalDatalakeClient
|
|
5
4
|
from splight_lib.client.datalake.remote_client import (
|
|
6
5
|
BufferedAsyncRemoteDatalakeClient,
|
|
7
6
|
BufferedSyncRemoteDataClient,
|
|
@@ -13,7 +12,6 @@ DL_CLIENT_TYPE_MAP = {
|
|
|
13
12
|
DatalakeClientType.BUFFERED_ASYNC: BufferedAsyncRemoteDatalakeClient,
|
|
14
13
|
DatalakeClientType.BUFFERED_SYNC: BufferedSyncRemoteDataClient,
|
|
15
14
|
DatalakeClientType.SYNC: SyncRemoteDatalakeClient,
|
|
16
|
-
DatalakeClientType.LOCAL: LocalDatalakeClient,
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
|
|
@@ -24,6 +24,7 @@ from splight_lib.models.hub_solution import HubSolution
|
|
|
24
24
|
from splight_lib.models.metadata import Metadata
|
|
25
25
|
from splight_lib.models.native import Boolean, Number, String
|
|
26
26
|
from splight_lib.models.secret import Secret
|
|
27
|
+
from splight_lib.models.solution import Solution
|
|
27
28
|
|
|
28
29
|
__all__ = [
|
|
29
30
|
AdvancedFilter,
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
RoutineEvaluation,
|
|
52
53
|
String,
|
|
53
54
|
Secret,
|
|
55
|
+
Solution,
|
|
54
56
|
RoutineObject,
|
|
55
57
|
RoutineObjectInstance,
|
|
56
58
|
Tab,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
+
from enum import auto
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import ClassVar, Dict, List, Optional, TypeVar
|
|
5
6
|
|
|
6
7
|
import pandas as pd
|
|
7
8
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
9
|
+
from strenum import LowercaseStrEnum
|
|
8
10
|
|
|
9
11
|
from splight_lib.client.database import DatabaseClientBuilder
|
|
10
12
|
from splight_lib.client.database.abstract import AbstractDatabaseClient
|
|
@@ -23,6 +25,11 @@ def datalake_model_serializer(data: Dict, default=str, **dumps_kwargs):
|
|
|
23
25
|
FilePath = TypeVar("FilePath", str, Path)
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
class PrivacyPolicy(LowercaseStrEnum):
|
|
29
|
+
PUBLIC = auto()
|
|
30
|
+
PRIVATE = auto()
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
class SplightDatabaseBaseModel(BaseModel):
|
|
27
34
|
_db_client: AbstractDatabaseClient = PrivateAttr()
|
|
28
35
|
|
|
@@ -41,8 +48,7 @@ class SplightDatabaseBaseModel(BaseModel):
|
|
|
41
48
|
self.model_dump(exclude_none=True, mode="json"),
|
|
42
49
|
files=files_dict,
|
|
43
50
|
)
|
|
44
|
-
|
|
45
|
-
setattr(self, field, saved.get(field))
|
|
51
|
+
return self.model_validate(saved)
|
|
46
52
|
|
|
47
53
|
def delete(self):
|
|
48
54
|
self._db_client.delete(
|
|
@@ -67,9 +73,7 @@ class SplightDatabaseBaseModel(BaseModel):
|
|
|
67
73
|
@staticmethod
|
|
68
74
|
def __get_database_client() -> AbstractDatabaseClient:
|
|
69
75
|
db_client = DatabaseClientBuilder.build(
|
|
70
|
-
local=settings.LOCAL_ENVIRONMENT,
|
|
71
76
|
parameters={
|
|
72
|
-
"path": settings.CURRENT_DIR,
|
|
73
77
|
"base_url": settings.SPLIGHT_PLATFORM_API_HOST,
|
|
74
78
|
"access_id": settings.SPLIGHT_ACCESS_ID,
|
|
75
79
|
"secret_key": settings.SPLIGHT_SECRET_KEY,
|
|
@@ -182,7 +186,6 @@ class SplightDatalakeBaseModel(BaseModel):
|
|
|
182
186
|
db_client = DatalakeClientBuilder.build(
|
|
183
187
|
dl_client_type=settings.DL_CLIENT_TYPE,
|
|
184
188
|
parameters={
|
|
185
|
-
"path": settings.CURRENT_DIR,
|
|
186
189
|
"base_url": settings.SPLIGHT_PLATFORM_API_HOST,
|
|
187
190
|
"access_id": settings.SPLIGHT_ACCESS_ID,
|
|
188
191
|
"secret_key": settings.SPLIGHT_SECRET_KEY,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
class MethodNotAllowed(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidObjectInstance(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecretNotFound(Exception):
|
|
10
|
+
def __init__(self, name: str):
|
|
11
|
+
msg = f"Secret {name} not found in database"
|
|
12
|
+
super().__init__(msg)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SecretDecryptionError(Exception):
|
|
16
|
+
def __init__(self, name: str):
|
|
17
|
+
msg = f"Unable to decrypt secret {name}"
|
|
18
|
+
super().__init__(msg)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InvalidFunctionConfiguration(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MissingFunctionItemExpression(Exception):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InvalidAlertConfiguration(Exception):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MissingAlertItemExpression(Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ForbiddenOperation(Exception):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InvalidConfigType(Exception):
|
|
42
|
+
def __init__(self, name: str, type_: str):
|
|
43
|
+
msg = (
|
|
44
|
+
f"Config parameter {name} has an invalid type {type_}. The only "
|
|
45
|
+
"valid type is 'Asset'"
|
|
46
|
+
)
|
|
47
|
+
super().__init__(msg)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InvalidResourceType(Exception):
|
|
51
|
+
def __init__(self, name: str, type_: str):
|
|
52
|
+
msg = (
|
|
53
|
+
f"Resource {name} has an invalid type {type_}. The only "
|
|
54
|
+
"valid type is 'Asset'"
|
|
55
|
+
)
|
|
56
|
+
super().__init__(msg)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MissingAsset(Exception):
|
|
60
|
+
def __init__(self, name: str):
|
|
61
|
+
msg = f"Resource {name} is missing the asset value"
|
|
62
|
+
super().__init__(msg)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InvalidArgument(Exception):
|
|
66
|
+
pass
|
|
@@ -64,16 +64,10 @@ class HubComponent(BaseModel):
|
|
|
64
64
|
)
|
|
65
65
|
privacy_policy: Optional[str] = None
|
|
66
66
|
component_type: ComponentType = ComponentType.CONNECTOR
|
|
67
|
-
tenant: Optional[str] = None
|
|
68
67
|
readme: Optional[str] = None
|
|
69
|
-
picture: Optional[str] = None
|
|
70
68
|
file: Optional[str] = None
|
|
71
69
|
verification: Optional[HubComponentVerificationEnum] = None
|
|
72
|
-
created_at: Optional[str] = None
|
|
73
|
-
last_modified: Optional[str] = None
|
|
74
70
|
tags: List[str] = []
|
|
75
|
-
min_component_capacity: Optional[str] = None
|
|
76
|
-
usage_count: int = 0
|
|
77
71
|
|
|
78
72
|
custom_types: List[CustomType] = []
|
|
79
73
|
input: List[InputParameter] = []
|
|
@@ -197,6 +191,7 @@ class HubComponent(BaseModel):
|
|
|
197
191
|
spec.setdefault("component_type", ComponentType.CONNECTOR.value)
|
|
198
192
|
data_cls = cls.model_validate(spec)
|
|
199
193
|
|
|
194
|
+
# TODO: Check if this is needed
|
|
200
195
|
data = data_cls.model_dump(exclude_none=True)
|
|
201
196
|
|
|
202
197
|
to_json = [
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from glob import glob
|
|
3
|
+
from tempfile import NamedTemporaryFile
|
|
4
|
+
from typing import List, Literal, Optional
|
|
5
|
+
|
|
6
|
+
import py7zr
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from splight_lib.constants import DESCRIPTION_MAX_LENGTH
|
|
10
|
+
from splight_lib.models.base import (
|
|
11
|
+
FilePath,
|
|
12
|
+
PrivacyPolicy,
|
|
13
|
+
SplightDatabaseBaseModel,
|
|
14
|
+
)
|
|
15
|
+
from splight_lib.models.component import InputParameter
|
|
16
|
+
from splight_lib.utils.hub import (
|
|
17
|
+
COMPRESSION_TYPE,
|
|
18
|
+
README_FILE_1,
|
|
19
|
+
get_ignore_pathspec,
|
|
20
|
+
get_spec,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InputAsset(InputParameter):
|
|
25
|
+
type: Literal["Asset"] = "Asset"
|
|
26
|
+
kind: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HubSolution(SplightDatabaseBaseModel):
|
|
30
|
+
id: Optional[str] = None
|
|
31
|
+
name: str
|
|
32
|
+
version: str
|
|
33
|
+
description: Optional[str] = Field(
|
|
34
|
+
default=None, max_length=DESCRIPTION_MAX_LENGTH
|
|
35
|
+
)
|
|
36
|
+
tags: Optional[List[str]] = Field(default=[])
|
|
37
|
+
privacy_policy: PrivacyPolicy = PrivacyPolicy.PUBLIC
|
|
38
|
+
|
|
39
|
+
config: List[InputParameter] = []
|
|
40
|
+
resources: List[InputAsset] = []
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def upload(cls, path: FilePath):
|
|
44
|
+
db_client = cls._SplightDatabaseBaseModel__get_database_client()
|
|
45
|
+
spec = get_spec(path)
|
|
46
|
+
name = spec.get("name")
|
|
47
|
+
version = spec.get("version")
|
|
48
|
+
|
|
49
|
+
raw_hub_solution = db_client.get(
|
|
50
|
+
resource_name=cls.__name__,
|
|
51
|
+
filters={"name": name, "version": version},
|
|
52
|
+
)
|
|
53
|
+
solution = cls.model_validate(spec)
|
|
54
|
+
if raw_hub_solution:
|
|
55
|
+
old_hub_solution = cls.model_validate(raw_hub_solution[0])
|
|
56
|
+
solution.id = old_hub_solution.id
|
|
57
|
+
|
|
58
|
+
solution.save()
|
|
59
|
+
|
|
60
|
+
file_name = f"{name}-{version}.{COMPRESSION_TYPE}"
|
|
61
|
+
ignore_pathspec = get_ignore_pathspec(path)
|
|
62
|
+
versioned_path = f"{name}-{version}"
|
|
63
|
+
main_file = os.path.join(path, "__main__.py")
|
|
64
|
+
readme_path = os.path.join(path, README_FILE_1)
|
|
65
|
+
if not os.path.exists(main_file):
|
|
66
|
+
raise FileNotFoundError(f"__main__.py file not found: {main_file}")
|
|
67
|
+
if not os.path.exists(readme_path):
|
|
68
|
+
raise FileNotFoundError(f"README.md file not found: {readme_path}")
|
|
69
|
+
with py7zr.SevenZipFile(file_name, "w") as archive:
|
|
70
|
+
all_files = glob(f"{path}/**", recursive=True)
|
|
71
|
+
for filepath in all_files:
|
|
72
|
+
if ignore_pathspec and ignore_pathspec.match_file(filepath):
|
|
73
|
+
continue
|
|
74
|
+
if os.path.isdir(filepath):
|
|
75
|
+
continue
|
|
76
|
+
rel_filename = os.path.relpath(filepath, path)
|
|
77
|
+
new_filepath = os.path.join(versioned_path, rel_filename)
|
|
78
|
+
archive.write(filepath, new_filepath)
|
|
79
|
+
|
|
80
|
+
data = solution.model_dump()
|
|
81
|
+
try:
|
|
82
|
+
db_client.upload(
|
|
83
|
+
"hubsolutionversion",
|
|
84
|
+
instance=data,
|
|
85
|
+
file_path=file_name,
|
|
86
|
+
type_="source",
|
|
87
|
+
)
|
|
88
|
+
db_client.upload(
|
|
89
|
+
"hubsolutionversion",
|
|
90
|
+
instance=data,
|
|
91
|
+
file_path=readme_path,
|
|
92
|
+
type_="readme",
|
|
93
|
+
)
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
raise exc
|
|
96
|
+
finally:
|
|
97
|
+
if os.path.exists(file_name):
|
|
98
|
+
os.remove(file_name)
|
|
99
|
+
return solution
|
|
100
|
+
|
|
101
|
+
def download(self) -> NamedTemporaryFile:
|
|
102
|
+
db_client = self._SplightDatabaseBaseModel__get_database_client()
|
|
103
|
+
return db_client.download(
|
|
104
|
+
resource_name="hubsolutionversion",
|
|
105
|
+
instance=self.model_dump(),
|
|
106
|
+
type_="source",
|
|
107
|
+
)
|
|
@@ -14,7 +14,6 @@ def get_datalake_client() -> AbstractDatalakeClient:
|
|
|
14
14
|
client = DatalakeClientBuilder.build(
|
|
15
15
|
dl_client_type=settings.DL_CLIENT_TYPE,
|
|
16
16
|
parameters={
|
|
17
|
-
"path": settings.CURRENT_DIR,
|
|
18
17
|
"base_url": settings.SPLIGHT_PLATFORM_API_HOST,
|
|
19
18
|
"access_id": settings.SPLIGHT_ACCESS_ID,
|
|
20
19
|
"secret_key": settings.SPLIGHT_SECRET_KEY,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from typing import NamedTuple, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, computed_field
|
|
5
|
+
|
|
6
|
+
from splight_lib.models.asset import Asset
|
|
7
|
+
from splight_lib.models.base import SplightDatabaseBaseModel
|
|
8
|
+
from splight_lib.models.exceptions import (
|
|
9
|
+
InvalidArgument,
|
|
10
|
+
InvalidConfigType,
|
|
11
|
+
InvalidResourceType,
|
|
12
|
+
MissingAsset,
|
|
13
|
+
)
|
|
14
|
+
from splight_lib.models.hub_solution import HubSolution
|
|
15
|
+
|
|
16
|
+
CAST_TO = {
|
|
17
|
+
"int": int,
|
|
18
|
+
"float": float,
|
|
19
|
+
"bool": bool,
|
|
20
|
+
"str": str,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_model_class(config: list[dict], name: str) -> NamedTuple:
|
|
25
|
+
config_class = namedtuple(name, [x.name for x in config])
|
|
26
|
+
return config_class
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_solution_config(
|
|
30
|
+
config: list[dict], config_class: NamedTuple
|
|
31
|
+
) -> NamedTuple:
|
|
32
|
+
config_dict = {}
|
|
33
|
+
for item in config:
|
|
34
|
+
if item["type"] not in CAST_TO:
|
|
35
|
+
raise InvalidConfigType(item["name"], item["type"])
|
|
36
|
+
if item["multiple"]:
|
|
37
|
+
value = [CAST_TO[item["type"]](x) for x in item["value"]]
|
|
38
|
+
else:
|
|
39
|
+
value = CAST_TO[item["type"]](item["value"])
|
|
40
|
+
config_dict.update({item["name"]: value})
|
|
41
|
+
config = config_class(**config_dict)
|
|
42
|
+
return config
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_solution_resources(
|
|
46
|
+
resources: list[dict], model_class: NamedTuple
|
|
47
|
+
) -> namedtuple:
|
|
48
|
+
resources_dict = {}
|
|
49
|
+
for resource in resources:
|
|
50
|
+
if type_ := resource["type"] != "Asset":
|
|
51
|
+
raise InvalidResourceType(resource["name"], type_)
|
|
52
|
+
if not resource["value"]:
|
|
53
|
+
raise MissingAsset(resource["name"])
|
|
54
|
+
|
|
55
|
+
if resource["multiple"]:
|
|
56
|
+
value = [Asset.retrieve(x) for x in resource["value"]]
|
|
57
|
+
else:
|
|
58
|
+
value = Asset.retrieve(resource["value"])
|
|
59
|
+
resources_dict.update({resource["name"]: value})
|
|
60
|
+
resources = model_class(**resources_dict)
|
|
61
|
+
return resources
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# class Solution(BaseModel):
|
|
65
|
+
# id: str
|
|
66
|
+
# name: str
|
|
67
|
+
# description: str
|
|
68
|
+
# hub_solution: HubSolution
|
|
69
|
+
#
|
|
70
|
+
# @classmethod
|
|
71
|
+
# def list(cls) -> list[Self]:
|
|
72
|
+
# return [solution.parse() for solution in RawSolution.list()]
|
|
73
|
+
#
|
|
74
|
+
# @classmethod
|
|
75
|
+
# def retrieve(cls, solution_id: str) -> Self:
|
|
76
|
+
# return RawSolution.retrieve(solution_id).parse()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Solution(SplightDatabaseBaseModel):
|
|
80
|
+
id: Optional[str] = None
|
|
81
|
+
name: str
|
|
82
|
+
description: str
|
|
83
|
+
hub_solution: HubSolution
|
|
84
|
+
raw_config: list[dict] = Field(alias="config")
|
|
85
|
+
raw_resources: list[dict] = Field(alias="resources")
|
|
86
|
+
|
|
87
|
+
def model_dump(self, *args, **kwargs):
|
|
88
|
+
kwargs.update({"by_alias": True})
|
|
89
|
+
return super().model_dump(*args, **kwargs)
|
|
90
|
+
|
|
91
|
+
def model_dump_json(self, *args, **kwargs):
|
|
92
|
+
kwargs.update({"by_alias": True})
|
|
93
|
+
return super().model_dump_json(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
@computed_field(alias="parsed_config")
|
|
96
|
+
@property
|
|
97
|
+
def config(self) -> NamedTuple:
|
|
98
|
+
model_class = get_model_class(self.hub_solution.config, "Config")
|
|
99
|
+
config = load_solution_config(self.raw_config, model_class)
|
|
100
|
+
return config
|
|
101
|
+
|
|
102
|
+
@computed_field(alias="parsed_resources")
|
|
103
|
+
@property
|
|
104
|
+
def resources(self) -> NamedTuple:
|
|
105
|
+
model_class = get_model_class(self.hub_solution.resources, "Resources")
|
|
106
|
+
resources = load_solution_resources(self.raw_resources, model_class)
|
|
107
|
+
return resources
|
|
108
|
+
|
|
109
|
+
def update_config(self, **kwargs: dict):
|
|
110
|
+
valid_params = [x["name"] for x in self.raw_config]
|
|
111
|
+
for key, value in kwargs.items():
|
|
112
|
+
if key not in valid_params:
|
|
113
|
+
raise InvalidArgument(
|
|
114
|
+
(
|
|
115
|
+
f"Got invalid parameter {key}. Valid config parameter "
|
|
116
|
+
f"are {valid_params}"
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
for item in self.raw_config:
|
|
120
|
+
if item["name"] == key:
|
|
121
|
+
item["value"] = value
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
def update_resources(self, **kwargs: dict):
|
|
125
|
+
valid_params = [x["name"] for x in self.raw_resources]
|
|
126
|
+
for key, value in kwargs.items():
|
|
127
|
+
if key not in valid_params:
|
|
128
|
+
raise InvalidArgument(
|
|
129
|
+
(
|
|
130
|
+
f"Got invalid parameter {key}. Valid resource "
|
|
131
|
+
f"parameter are {valid_params}"
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
for item in self.raw_resources:
|
|
135
|
+
if item["name"] == key:
|
|
136
|
+
item["value"] = value
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
# def parse(self) -> Solution:
|
|
140
|
+
# config = load_solution_config(self.config)
|
|
141
|
+
# config_type = NamedTuple(
|
|
142
|
+
# "Config", [(x["name"], Any) for x in self.config]
|
|
143
|
+
# )
|
|
144
|
+
# resources = load_solution_resources(self.resources)
|
|
145
|
+
# resouces_type = NamedTuple(
|
|
146
|
+
# "Resources", [(x["name"], Any) for x in self.resources]
|
|
147
|
+
# )
|
|
148
|
+
# model = create_model(
|
|
149
|
+
# "Solution",
|
|
150
|
+
# __base__=Solution,
|
|
151
|
+
# config=(config_type, ...),
|
|
152
|
+
# resources=(resouces_type, ...),
|
|
153
|
+
# )
|
|
154
|
+
# return model(
|
|
155
|
+
# id=self.id,
|
|
156
|
+
# name=self.name,
|
|
157
|
+
# description=self.description,
|
|
158
|
+
# hub_solution=self.hub_solution,
|
|
159
|
+
# config=config,
|
|
160
|
+
# resources=resources,
|
|
161
|
+
# )
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Any, Dict,
|
|
3
|
+
from typing import Any, Dict, Tuple, Type
|
|
4
4
|
|
|
5
5
|
import yaml
|
|
6
|
-
from pydantic import ConfigDict
|
|
6
|
+
from pydantic import ConfigDict
|
|
7
7
|
from pydantic.fields import FieldInfo
|
|
8
8
|
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
|
|
9
9
|
|
|
@@ -65,11 +65,6 @@ class SplightSettings(BaseSettings, Singleton):
|
|
|
65
65
|
SPLIGHT_SECRET_KEY: str = ""
|
|
66
66
|
SPLIGHT_PLATFORM_API_HOST: str = "https://api.splight-ai.com"
|
|
67
67
|
|
|
68
|
-
# Parameters for local environment
|
|
69
|
-
# TODO: to deprecate this and its effects
|
|
70
|
-
LOCAL_ENVIRONMENT: bool = False
|
|
71
|
-
CURRENT_DIR: Optional[str] = None
|
|
72
|
-
|
|
73
68
|
# Parameters for the datalake client
|
|
74
69
|
# Review if is better to use another class for only the DL Client settings
|
|
75
70
|
DL_CLIENT_TYPE: DatalakeClientType = DatalakeClientType.BUFFERED_ASYNC
|
|
@@ -78,15 +73,6 @@ class SplightSettings(BaseSettings, Singleton):
|
|
|
78
73
|
|
|
79
74
|
model_config = ConfigDict(extra="ignore")
|
|
80
75
|
|
|
81
|
-
@model_validator(mode="after")
|
|
82
|
-
@classmethod
|
|
83
|
-
def validate_local_environment(
|
|
84
|
-
cls, instance: "SplightSettings"
|
|
85
|
-
) -> "SplightSettings":
|
|
86
|
-
if instance.LOCAL_ENVIRONMENT:
|
|
87
|
-
instance.CURRENT_DIR = os.getcwd()
|
|
88
|
-
return instance
|
|
89
|
-
|
|
90
76
|
def configure(self, **params: Dict):
|
|
91
77
|
self.model_validate(params)
|
|
92
78
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from splight_lib.models import Solution
|
|
5
|
+
from splight_lib.solution.exceptions import (
|
|
6
|
+
MissingInstanceEnvVar,
|
|
7
|
+
MissingInstanceId,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
ENV_VAR = "INSTANCE_AS_STR"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SolutionLoader:
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_env(cls) -> Solution:
|
|
16
|
+
if not (str_instance := os.environ.get(ENV_VAR)):
|
|
17
|
+
raise MissingInstanceEnvVar(ENV_VAR)
|
|
18
|
+
str_instance = os.environ.get("INSTANCE_AS_STR", "")
|
|
19
|
+
instance = json.loads(str_instance)
|
|
20
|
+
if not (instance_id := instance.get("id")):
|
|
21
|
+
raise MissingInstanceId()
|
|
22
|
+
solution = Solution.retrieve(instance_id)
|
|
23
|
+
return solution
|