splight-lib 5.7.2__tar.gz → 5.8.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 (100) hide show
  1. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/PKG-INFO +1 -1
  2. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/pyproject.toml +1 -1
  3. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/database/classmap.py +2 -0
  4. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/database/remote_client.py +31 -4
  5. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/__init__.py +2 -0
  6. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/asset.py +1 -1
  7. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/base.py +8 -2
  8. splight_lib-5.8.0.dev1/splight_lib/models/exceptions.py +66 -0
  9. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/hub.py +1 -6
  10. splight_lib-5.8.0.dev1/splight_lib/models/hub_solution.py +107 -0
  11. splight_lib-5.8.0.dev1/splight_lib/models/solution.py +122 -0
  12. splight_lib-5.8.0.dev1/splight_lib/solution/__init__.py +3 -0
  13. splight_lib-5.8.0.dev1/splight_lib/solution/exceptions.py +8 -0
  14. splight_lib-5.8.0.dev1/splight_lib/solution/solution.py +23 -0
  15. splight_lib-5.7.2/splight_lib/models/exceptions.py +0 -38
  16. splight_lib-5.7.2/splight_lib/models/hub_solution.py +0 -49
  17. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/LICENSE.txt +0 -0
  18. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/README.md +0 -0
  19. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/__init__.py +0 -0
  20. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/abstract/__init__.py +0 -0
  21. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/abstract/client.py +0 -0
  22. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/auth/__init__.py +0 -0
  23. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/auth/exceptions.py +0 -0
  24. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/auth/mac_auth.py +0 -0
  25. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/auth/token.py +0 -0
  26. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/__init__.py +0 -0
  27. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/database/__init__.py +0 -0
  28. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/database/abstract.py +0 -0
  29. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/database/builder.py +0 -0
  30. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/__init__.py +0 -0
  31. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/abstract.py +0 -0
  32. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/buffer.py +0 -0
  33. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/builder.py +0 -0
  34. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/exceptions.py +0 -0
  35. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/remote_client.py +0 -0
  36. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/datalake/schemas.py +0 -0
  37. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/exceptions.py +0 -0
  38. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/file_handler.py +0 -0
  39. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/filter.py +0 -0
  40. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/hub/__init__.py +0 -0
  41. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/hub/abstract.py +0 -0
  42. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/hub/client.py +0 -0
  43. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/tests/test_database.py +0 -0
  44. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/client/tests/test_datalake.py +0 -0
  45. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/__init__.py +0 -0
  46. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/abstract.py +0 -0
  47. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/exceptions.py +0 -0
  48. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/spec.py +0 -0
  49. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/tests/test_abstract.py +0 -0
  50. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/component/tests/test_spec.py +0 -0
  51. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/conftest.py +0 -0
  52. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/constants.py +0 -0
  53. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/encryption.py +0 -0
  54. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/__init__.py +0 -0
  55. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/engine.py +0 -0
  56. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/exceptions.py +0 -0
  57. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/scheduling.py +0 -0
  58. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/task.py +0 -0
  59. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/tests/test_execution.py +0 -0
  60. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/tests/test_scheduling.py +0 -0
  61. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/execution/trigger.py +0 -0
  62. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/__init__.py +0 -0
  63. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/_internal.py +0 -0
  64. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/component.py +0 -0
  65. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/constants.py +0 -0
  66. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/logging.py +0 -0
  67. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/logging/tests/test_logging.py +0 -0
  68. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/alert.py +0 -0
  69. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/attribute.py +0 -0
  70. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/component.py +0 -0
  71. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/dashboard.py +0 -0
  72. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/data_address.py +0 -0
  73. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/file.py +0 -0
  74. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/function.py +0 -0
  75. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/generic.py +0 -0
  76. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/metadata.py +0 -0
  77. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/native.py +0 -0
  78. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/pipeline.py +0 -0
  79. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/secret.py +0 -0
  80. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/tests/models.json +0 -0
  81. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/tests/test_component_object_instance.py +0 -0
  82. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/tests/test_database_model.py +0 -0
  83. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/tests/test_metadata.py +0 -0
  84. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/models/tests/test_models.py +0 -0
  85. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/restclient/__init__.py +0 -0
  86. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/restclient/client.py +0 -0
  87. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/restclient/exceptions.py +0 -0
  88. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/restclient/tests/test_restclient.py +0 -0
  89. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/restclient/types.py +0 -0
  90. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/settings.py +0 -0
  91. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/stringcase.py +0 -0
  92. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/testing/__init__.py +0 -0
  93. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/tests/FakeProc.py +0 -0
  94. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/tests/asset_geometries.json +0 -0
  95. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/tests/test_api_contracts.py +0 -0
  96. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/tests/test_encryption.py +0 -0
  97. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/utils/__init__.py +0 -0
  98. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/utils/custom_model.py +0 -0
  99. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/utils/hub.py +0 -0
  100. {splight_lib-5.7.2 → splight_lib-5.8.0.dev1}/splight_lib/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-lib
3
- Version: 5.7.2
3
+ Version: 5.8.0.dev1
4
4
  Summary: Splight Library
5
5
  Author: Splight Dev
6
6
  Author-email: dev@splight-ae.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "splight-lib"
3
- version = "5.7.2"
3
+ version = "5.8.0.dev1"
4
4
  description = "Splight Library"
5
5
  authors = ["Splight Dev <dev@splight-ae.com>"]
6
6
  readme = "README.md"
@@ -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
- decrypt: bool = True,
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() != "file":
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
- response = self._restclient.get(url)
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.is_error:
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
@@ -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,
@@ -22,7 +22,7 @@ class AssetRelationship(BaseModel):
22
22
  id: str
23
23
  name: str
24
24
  description: Optional[str] = None
25
- related_asset_kind: Optional[str] = None
25
+ related_asset_kind: Optional[AssetRepr] = None
26
26
  asset: AssetRepr
27
27
  related_asset: AssetRepr
28
28
 
@@ -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
- for field in self.model_fields:
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(
@@ -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
+ )
@@ -0,0 +1,122 @@
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(SplightDatabaseBaseModel):
65
+ id: Optional[str] = None
66
+ name: str
67
+ description: Optional[str] = None
68
+ hub_solution: HubSolution
69
+ raw_config: list[dict] = Field(alias="config")
70
+ raw_resources: list[dict] = Field(alias="resources")
71
+
72
+ def model_dump(self, *args, **kwargs):
73
+ kwargs.update({"by_alias": True})
74
+ return super().model_dump(*args, **kwargs)
75
+
76
+ def model_dump_json(self, *args, **kwargs):
77
+ kwargs.update({"by_alias": True})
78
+ return super().model_dump_json(*args, **kwargs)
79
+
80
+ @computed_field(alias="parsed_config")
81
+ @property
82
+ def config(self) -> NamedTuple:
83
+ model_class = get_model_class(self.hub_solution.config, "Config")
84
+ config = load_solution_config(self.raw_config, model_class)
85
+ return config
86
+
87
+ @computed_field(alias="parsed_resources")
88
+ @property
89
+ def resources(self) -> NamedTuple:
90
+ model_class = get_model_class(self.hub_solution.resources, "Resources")
91
+ resources = load_solution_resources(self.raw_resources, model_class)
92
+ return resources
93
+
94
+ def update_config(self, **kwargs: dict):
95
+ valid_params = [x["name"] for x in self.raw_config]
96
+ for key, value in kwargs.items():
97
+ if key not in valid_params:
98
+ raise InvalidArgument(
99
+ (
100
+ f"Got invalid parameter {key}. Valid config parameter "
101
+ f"are {valid_params}"
102
+ )
103
+ )
104
+ for item in self.raw_config:
105
+ if item["name"] == key:
106
+ item["value"] = value
107
+ break
108
+
109
+ def update_resources(self, **kwargs: dict):
110
+ valid_params = [x["name"] for x in self.raw_resources]
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 resource "
116
+ f"parameter are {valid_params}"
117
+ )
118
+ )
119
+ for item in self.raw_resources:
120
+ if item["name"] == key:
121
+ item["value"] = value
122
+ break
@@ -0,0 +1,3 @@
1
+ from splight_lib.solution.solution import SolutionLoader
2
+
3
+ __all__ = [SolutionLoader]
@@ -0,0 +1,8 @@
1
+ class MissingInstanceEnvVar(Exception):
2
+ def __init__(self, env_var: str):
3
+ super().__init__(f"Missing environment variable: {env_var}")
4
+
5
+
6
+ class MissingInstanceId(Exception):
7
+ def __init__(self):
8
+ super().__init__("Missing instance id")
@@ -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
@@ -1,38 +0,0 @@
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
@@ -1,49 +0,0 @@
1
- from enum import auto
2
- from typing import Dict, List, Optional
3
-
4
- from pydantic import Field
5
- from strenum import LowercaseStrEnum
6
-
7
- from splight_lib.constants import DESCRIPTION_MAX_LENGTH
8
- from splight_lib.models.base import FilePath, SplightDatabaseBaseModel
9
-
10
-
11
- class PrivacyPolicy(LowercaseStrEnum):
12
- PUBLIC = auto()
13
- PRIVATE = auto()
14
-
15
-
16
- class HubSolution(SplightDatabaseBaseModel):
17
- id: Optional[str] = None
18
- name: str
19
- version: str
20
- description: Optional[str] = Field(
21
- default=None, max_length=DESCRIPTION_MAX_LENGTH
22
- )
23
- tags: Optional[List[str]] = Field(default=None)
24
- privacy_policy: PrivacyPolicy = PrivacyPolicy.PUBLIC
25
-
26
- main_file: Optional[FilePath] = Field(default=None, exclude=True)
27
- variables_file: Optional[FilePath] = Field(default=None, exclude=True)
28
- values_file: Optional[FilePath] = Field(default=None, exclude=True)
29
- readme_file: Optional[FilePath] = Field(default=None, exclude=True)
30
-
31
- def save(self) -> None:
32
- if not all(
33
- [
34
- self.main_file,
35
- self.variables_file,
36
- self.values_file,
37
- self.readme_file,
38
- ]
39
- ):
40
- raise ValueError("Missing one of the required files")
41
- return super().save()
42
-
43
- def _get_model_files_dict(self) -> Optional[Dict]:
44
- return {
45
- "main_file": self.main_file,
46
- "variables_file": self.variables_file,
47
- "values_file": self.values_file,
48
- "readme_file": self.readme_file,
49
- }
File without changes