splight-cli 4.6.0.dev1__tar.gz → 4.6.1__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 (73) hide show
  1. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/PKG-INFO +9 -4
  2. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/README.md +5 -0
  3. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/pyproject.toml +4 -4
  4. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/apply_exec.py +1 -1
  5. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/importer.py +2 -0
  6. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/models.py +11 -1
  7. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/plan_exec.py +23 -0
  8. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/replacer.py +67 -6
  9. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/solution.py +89 -10
  10. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/solution_checker.py +59 -5
  11. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/tests/constants.py +16 -8
  12. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/tests/test_plan.py +2 -2
  13. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/utils.py +4 -1
  14. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/template.py +1 -1
  15. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/__init__.py +0 -0
  16. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/cli.py +0 -0
  17. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/__init__.py +0 -0
  18. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/component.py +0 -0
  19. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/exceptions.py +0 -0
  20. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/loaders.py +0 -0
  21. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/.splightignore +0 -0
  22. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/Initialization +0 -0
  23. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/README.md +0 -0
  24. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/auto_readme.md +0 -0
  25. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/main.py +0 -0
  26. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/spec.json +0 -0
  27. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/templates/tests.py +0 -0
  28. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/tests/__init__.py +0 -0
  29. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/tests/test_component_manager.py +0 -0
  30. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/component/utils.py +0 -0
  31. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/config/__init__.py +0 -0
  32. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/constants.py +0 -0
  33. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/context/__init__.py +0 -0
  34. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/context/context.py +0 -0
  35. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/context/workspace.py +0 -0
  36. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/__init__.py +0 -0
  37. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/alert/__init__.py +0 -0
  38. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/asset/__init__.py +0 -0
  39. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/attribute/__init__.py +0 -0
  40. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/component/__init__.py +0 -0
  41. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/datalake/__init__.py +0 -0
  42. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/file/__init__.py +0 -0
  43. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/manager/__init__.py +0 -0
  44. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/manager/exceptions.py +0 -0
  45. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/manager/manager.py +0 -0
  46. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/manager/tests/test_datalake_manager.py +0 -0
  47. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/manager/tests/test_resource_manager.py +0 -0
  48. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/engine/secret/__init__.py +0 -0
  49. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/hub/__init__.py +0 -0
  50. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/hub/component/__init__.py +0 -0
  51. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/hub/component/exceptions.py +0 -0
  52. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/hub/component/hub_manager.py +0 -0
  53. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/hub/component/tests/test_hub_manager.py +0 -0
  54. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/settings.py +0 -0
  55. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/__init__.py +0 -0
  56. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/destroyer.py +0 -0
  57. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/exceptions.py +0 -0
  58. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/tests/test_apply.py +0 -0
  59. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/tests/test_destroy.py +0 -0
  60. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/solution/tests/test_import.py +0 -0
  61. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/tests/test_component/Initialization +0 -0
  62. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/tests/test_component/README.md +0 -0
  63. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/tests/test_component/main.py +0 -0
  64. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/tests/test_component/spec.json +0 -0
  65. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/tests/test_component/tests.py +0 -0
  66. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/__init__.py +0 -0
  67. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/input.py +0 -0
  68. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/json.py +0 -0
  69. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/loader.py +0 -0
  70. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/pprint.py +0 -0
  71. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/utils/yaml.py +0 -0
  72. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/version.py +0 -0
  73. {splight_cli-4.6.0.dev1 → splight_cli-4.6.1}/splight_cli/workspace/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-cli
3
- Version: 4.6.0.dev1
3
+ Version: 4.6.1
4
4
  Summary: Splight Command Line Interface
5
5
  Author: Splight Dev
6
6
  Author-email: dev@splight-ae.com
@@ -19,16 +19,21 @@ Requires-Dist: deepdiff (==6.3.1)
19
19
  Requires-Dist: mock (>=5.1.0,<6.0.0)
20
20
  Requires-Dist: packaging (==23.0)
21
21
  Requires-Dist: pathspec (==0.11.1)
22
- Requires-Dist: py7zr (==0.18.5)
23
- Requires-Dist: requests (==2.27.1)
22
+ Requires-Dist: py7zr (==0.20.8)
23
+ Requires-Dist: requests (==2.31.0)
24
24
  Requires-Dist: rich (==13.0.1)
25
25
  Requires-Dist: setuptools (==60.9.3)
26
- Requires-Dist: splight-lib (==4.4.0)
26
+ Requires-Dist: splight-lib (==4.6.3)
27
27
  Requires-Dist: typer (==0.7.0)
28
28
  Description-Content-Type: text/markdown
29
29
 
30
30
  # Splight CLI
31
31
 
32
+ ![snyk_code](https://github.com/splightplatform/splight-cli/blob/gh-pages/snyk_code.svg?raw=True)
33
+ ![snyk_dependencies](https://github.com/splightplatform/splight-cli/blob/gh-pages/snyk_dependencies.svg?raw=True)
34
+
35
+ ---
36
+
32
37
  ## Table of Content
33
38
 
34
39
  - [Splight CLI](#splight-cli)
@@ -1,5 +1,10 @@
1
1
  # Splight CLI
2
2
 
3
+ ![snyk_code](https://github.com/splightplatform/splight-cli/blob/gh-pages/snyk_code.svg?raw=True)
4
+ ![snyk_dependencies](https://github.com/splightplatform/splight-cli/blob/gh-pages/snyk_dependencies.svg?raw=True)
5
+
6
+ ---
7
+
3
8
  ## Table of Content
4
9
 
5
10
  - [Splight CLI](#splight-cli)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "splight-cli"
3
- version = "4.6.0.dev1"
3
+ version = "4.6.1"
4
4
  description = "Splight Command Line Interface"
5
5
  authors = ["Splight Dev <dev@splight-ae.com>"]
6
6
  readme = "README.md"
@@ -11,11 +11,11 @@ case-converter = "1.1.0"
11
11
  click = "8.0.4"
12
12
  click-default-group = "1.2.2"
13
13
  setuptools = "60.9.3"
14
- splight-lib = "4.4.0"
15
- py7zr = "0.18.5"
14
+ splight-lib = "4.6.3"
15
+ py7zr = "0.20.8"
16
16
  typer = "0.7.0"
17
17
  colorama = "0.4.4"
18
- requests = "2.27.1"
18
+ requests = "2.31.0"
19
19
  PyYAML = "6.0.1"
20
20
  rich = "13.0.1"
21
21
  pathspec = "0.11.1"
@@ -115,7 +115,7 @@ class ApplyExecutor:
115
115
  )
116
116
  if diff:
117
117
  bprint(
118
- f"\nThe remote {model_name} with id {instance_id} has the "
118
+ f"\nThe remote {model_name} with id {instance_id} has the"
119
119
  " following differences with the local item:"
120
120
  )
121
121
  rprint(diff)
@@ -3,6 +3,7 @@ from uuid import UUID
3
3
 
4
4
  from rich.console import Console
5
5
  from splight_lib.models import (
6
+ Alert,
6
7
  Asset,
7
8
  Component,
8
9
  Function,
@@ -31,6 +32,7 @@ class ImporterExecutor:
31
32
  "secret": Secret,
32
33
  "component": Component,
33
34
  "function": Function,
35
+ "alert": Alert,
34
36
  }
35
37
 
36
38
  def import_element(self, element: ElementType, id: UUID) -> ImportResult:
@@ -2,7 +2,14 @@ from enum import Enum
2
2
  from typing import List, Optional
3
3
 
4
4
  from pydantic import BaseModel, Field
5
- from splight_lib.models import Asset, File, Function, RoutineObject, Secret
5
+ from splight_lib.models import (
6
+ Alert,
7
+ Asset,
8
+ File,
9
+ Function,
10
+ RoutineObject,
11
+ Secret,
12
+ )
6
13
  from splight_lib.models.component import Component as LibComponent
7
14
 
8
15
 
@@ -11,6 +18,7 @@ class ElementType(str, Enum):
11
18
  secret = "secret"
12
19
  component = "component"
13
20
  function = "function"
21
+ alert = "alert"
14
22
 
15
23
 
16
24
  class Component(LibComponent):
@@ -23,12 +31,14 @@ class PlanSolution(BaseModel):
23
31
  files: List[File]
24
32
  components: List[Component]
25
33
  functions: List[Function]
34
+ alerts: List[Alert]
26
35
  imported_assets: Optional[List[Asset]] = Field(default_factory=list)
27
36
  imported_secrets: Optional[List[Secret]] = Field(default_factory=list)
28
37
  imported_components: Optional[List[Component]] = Field(
29
38
  default_factory=list
30
39
  )
31
40
  imported_functions: Optional[List[Function]] = Field(default_factory=list)
41
+ imported_alerts: Optional[List[Alert]] = Field(default_factory=list)
32
42
 
33
43
 
34
44
  class StateSolution(PlanSolution):
@@ -30,6 +30,29 @@ class PlanExecutor:
30
30
  "If the plan is applied the following Asset will be deleted:"
31
31
  )
32
32
  bprint(asset)
33
+ for secret in check_results.secrets_to_delete:
34
+ bprint(
35
+ "If the plan is applied the following Secret will be "
36
+ "deleted:"
37
+ )
38
+ bprint(secret)
39
+ for file in check_results.files_to_delete:
40
+ bprint(
41
+ "If the plan is applied the following File will be " "deleted:"
42
+ )
43
+ bprint(file)
44
+ for function in check_results.functions_to_delete:
45
+ bprint(
46
+ "If the plan is applied the following Function will be "
47
+ "deleted:"
48
+ )
49
+ bprint(function)
50
+ for alert in check_results.alerts_to_delete:
51
+ bprint(
52
+ "If the plan is applied the following alert will be "
53
+ "deleted:"
54
+ )
55
+ bprint(alert)
33
56
  for component in check_results.components_to_delete:
34
57
  bprint(
35
58
  "If the plan is applied the following Component will be "
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from typing import Callable, Dict, List, Optional, Union
3
3
 
4
+ from splight_lib.models import AlertItem, QueryFilter
4
5
  from splight_lib.models.component import (
5
6
  InputDataAddress,
6
7
  InputParameter,
@@ -36,6 +37,10 @@ class Replacer:
36
37
  file_ref = get_ref_str("file", file.name)
37
38
  self._reference_map[file_ref] = file.id
38
39
 
40
+ for secret in self._state.secrets:
41
+ secret_ref = get_ref_str("secret", secret.name)
42
+ self._reference_map[secret_ref] = f"${{{{SECRET.{secret.name}}}}}"
43
+
39
44
  def replace_references(self):
40
45
  """Replaces any references in the inputs, outputs and routines of every
41
46
  component.
@@ -66,6 +71,14 @@ class Replacer:
66
71
  target_attribute=state_functions[i].target_attribute
67
72
  )
68
73
 
74
+ state_alerts = self._state.alerts
75
+ for i in range(len(state_alerts)):
76
+ alert_items = state_alerts[i].alert_items
77
+ for j in range(len(alert_items)):
78
+ self._replace_alert_ref(alert_item=alert_items[j])
79
+
80
+ self._replace_alert_ref(assets=state_alerts[i].assets)
81
+
69
82
  def _replace_io_ref(
70
83
  self,
71
84
  io_elem: Union[InputDataAddress, InputParameter, Output],
@@ -156,6 +169,54 @@ class Replacer:
156
169
  value_ref = target_attribute["id"]
157
170
  target_attribute["id"] = self._parse_input_output(value_ref)
158
171
 
172
+ def _replace_alert_ref(
173
+ self, alert_item: AlertItem = None, assets: QueryFilter = None
174
+ ):
175
+ """Replaces references in the alert_item and assets.
176
+
177
+ Parameters
178
+ ----------
179
+ alert_item : Optional[AlertItem]
180
+ The alert item to be processed. Default None.
181
+ assets : Optional[List[QueryFilter]]
182
+ List of assets to be processed. Default None.
183
+ """
184
+ if alert_item:
185
+ asset = alert_item.query_filter_asset
186
+ attribute = alert_item.query_filter_attribute
187
+ query_plain = alert_item.query_plain
188
+
189
+ if asset:
190
+ value_ref = asset["id"]
191
+ asset["id"] = self._parse_input_output(value_ref)
192
+ alert_item.query_filter_asset = asset
193
+
194
+ if attribute:
195
+ value_ref = attribute["id"]
196
+ attribute["id"] = self._parse_input_output(value_ref)
197
+ alert_item.query_filter_attribute = attribute
198
+
199
+ if query_plain:
200
+ query_dict = json.loads(query_plain)
201
+
202
+ # NOTE: sadly this depends on the query.
203
+ # I'd rather do this than black magic to get the placeholders.
204
+ asset_ref = query_dict[0]["$match"]["asset"]
205
+ attribute_ref = query_dict[0]["$match"]["attribute"]
206
+
207
+ query_dict[0]["$match"]["asset"] = self._parse_input_output(
208
+ asset_ref
209
+ )
210
+ query_dict[0]["$match"][
211
+ "attribute"
212
+ ] = self._parse_input_output(attribute_ref)
213
+ alert_item.query_plain = json.dumps(query_dict)
214
+
215
+ if assets:
216
+ for asset in assets:
217
+ value_ref = asset["id"]
218
+ asset["id"] = self._parse_input_output(value_ref)
219
+
159
220
  def _get_new_value(
160
221
  self,
161
222
  elem: Union[InputDataAddress, InputParameter, Output],
@@ -273,6 +334,12 @@ class Replacer:
273
334
  elif attr_ref not in self._reference_map:
274
335
  missing_ref = attr_ref
275
336
 
337
+ if missing_ref is not None:
338
+ raise UndefinedID(
339
+ f"The reference '{missing_ref}' "
340
+ f"used in the routine named '{routine_name}' of the "
341
+ f"component '{component_name}' is not a valid reference."
342
+ )
276
343
  if self._attr_to_asset_map[attr_ref] != asset_ref:
277
344
  raise UndefinedID(
278
345
  f"The attribute '{attr_ref}' is not an attribute of "
@@ -280,12 +347,6 @@ class Replacer:
280
347
  f"'{routine_name}' of the component '{component_name}'."
281
348
  )
282
349
 
283
- if missing_ref is not None:
284
- raise UndefinedID(
285
- f"The reference '{missing_ref}' "
286
- f"used in the routine named '{routine_name}' of the "
287
- f"component '{component_name}' is not a valid reference."
288
- )
289
350
  return {
290
351
  "asset": self._reference_map[asset_ref],
291
352
  "attribute": self._reference_map[attr_ref],
@@ -6,6 +6,7 @@ import typer
6
6
  from pydantic import ValidationError
7
7
  from rich.console import Console
8
8
  from splight_lib.models import (
9
+ Alert,
9
10
  Asset,
10
11
  Component,
11
12
  File,
@@ -62,6 +63,8 @@ class SolutionManager:
62
63
  r"root\['config'\]\[\d+\]\['description'\]",
63
64
  r"root\['input'\]\[\d+\]\['description'\]",
64
65
  r"root\['output'\]\[\d+\]\['description'\]",
66
+ # NOTE: API returns the DataAddress type but we don't use it
67
+ r"root\['(?:input|output)'\]\[\d+\]\['value'\]\['type'\]",
65
68
  ],
66
69
  File.__name__: [
67
70
  r"root\['metadata'\]",
@@ -83,13 +86,14 @@ class SolutionManager:
83
86
  console.print("\nStarting apply...", style=PRINT_STYLE)
84
87
  check_result = self._solution_checker.check()
85
88
  self._plan, self._state = check_result.plan, check_result.state
86
- self._delete_assets_and_components(check_result)
89
+ self._delete_objects(check_result)
87
90
  self._apply_assets_state()
88
91
  self._apply_secrets_state()
89
92
  self._apply_files_state()
90
93
  self._replacer.build_reference_map()
91
94
  self._replacer.replace_references()
92
95
  self._apply_functions_state()
96
+ self._apply_alerts_state()
93
97
  self._apply_components_state()
94
98
 
95
99
  def plan(self):
@@ -99,10 +103,12 @@ class SolutionManager:
99
103
  self._plan_exec.plan_elements_to_delete(check_result)
100
104
  self._plan_assets_state()
101
105
  self._plan_secrets_state()
106
+ # TODO: buscar los files en una carpeta fija
102
107
  self._plan_files_state()
103
108
  self._replacer.build_reference_map()
104
109
  self._replacer.replace_references()
105
110
  self._plan_functions_state()
111
+ self._plan_alerts_state()
106
112
  self._plan_components_state()
107
113
 
108
114
  def import_element(self, element: ElementType, id: UUID):
@@ -155,12 +161,20 @@ class SolutionManager:
155
161
 
156
162
  state_functions = self._state.functions
157
163
  for idx in range(len(state_functions) - 1, -1, -1):
158
- function_to_detele = state_functions[idx]
159
- destroyed = self._destroyer.destroy(Function, function_to_detele)
164
+ function_to_delete = state_functions[idx]
165
+ destroyed = self._destroyer.destroy(Function, function_to_delete)
160
166
  if destroyed:
161
167
  state_functions.pop(idx)
162
168
  save_yaml(self._state_path, self._state)
163
169
 
170
+ state_alerts = self._state.alerts
171
+ for idx in range(len(state_alerts) - 1, -1, -1):
172
+ alert_to_delete = state_alerts[idx]
173
+ destroyed = self._destroyer.destroy(Alert, alert_to_delete)
174
+ if destroyed:
175
+ state_alerts.pop(idx)
176
+ save_yaml(self._state_path, self._state)
177
+
164
178
  state_components = self._state.components
165
179
  for idx in range(len(state_components) - 1, -1, -1):
166
180
  component_to_delete = state_components[idx]
@@ -176,8 +190,16 @@ class SolutionManager:
176
190
  return StateSolution.model_validate(
177
191
  load_yaml(self._state_path)
178
192
  )
179
- except ValidationError:
180
- pass
193
+ except ValidationError as exc:
194
+ raise typer.Abort(
195
+ f"Validation error while opening '{self._state_path}': {exc}"
196
+ )
197
+ except FileNotFoundError as exc:
198
+ raise typer.Abort(f"File '{self._state_path}' not found.")
199
+ except:
200
+ raise typer.Abort(
201
+ f"Unkown error while opening file '{self._state_path}'"
202
+ )
181
203
  else:
182
204
  self._state_path = self._plan_path.parent / DEFAULT_STATE_PATH
183
205
 
@@ -232,7 +254,12 @@ class SolutionManager:
232
254
  for state_function in function_list:
233
255
  self._plan_exec.plan_elem_state(Function, state_function)
234
256
 
235
- def _delete_assets_and_components(self, check_result: CheckResult):
257
+ def _plan_alerts_state(self):
258
+ alert_list = self._state.alerts + self._state.imported_alerts
259
+ for state_alert in alert_list:
260
+ self._plan_exec.plan_elem_state(Alert, state_alert)
261
+
262
+ def _delete_objects(self, check_result: CheckResult):
236
263
  """Deletes assets and/or components that have been removed from the
237
264
  plan.
238
265
 
@@ -245,6 +272,22 @@ class SolutionManager:
245
272
  self._apply_exec.delete(Asset, asset)
246
273
  save_yaml(self._state_path, self._state)
247
274
 
275
+ for secret in check_result.secrets_to_delete:
276
+ self._apply_exec.delete(Secret, secret)
277
+ save_yaml(self._state_path, self._state)
278
+
279
+ for file in check_result.files_to_delete:
280
+ self._apply_exec.delete(File, file)
281
+ save_yaml(self._state_path, self._state)
282
+
283
+ for function in check_result.functions_to_delete:
284
+ self._apply_exec.delete(Function, function)
285
+ save_yaml(self._state_path, self._state)
286
+
287
+ for alert in check_result.alerts_to_delete:
288
+ self._apply_exec.delete(Alert, alert)
289
+ save_yaml(self._state_path, self._state)
290
+
248
291
  for component in check_result.components_to_delete:
249
292
  self._apply_exec.delete(Component, component)
250
293
  save_yaml(self._state_path, self._state)
@@ -275,9 +318,7 @@ class SolutionManager:
275
318
 
276
319
  def _apply_secrets_state(self):
277
320
  """Applies secrets states to the engine."""
278
- secrets_list = (
279
- self._state.secrets
280
- ) # TODO: a chequear + self._state.imported_secrets
321
+ secrets_list = self._state.secrets
281
322
  for i in range(len(secrets_list)):
282
323
  result = self._apply_exec.apply(
283
324
  model=Secret, local_instance=secrets_list[i]
@@ -362,7 +403,7 @@ class SolutionManager:
362
403
  return routine_list
363
404
 
364
405
  def _apply_functions_state(self):
365
- """Applies RoutineObject states to the engine."""
406
+ """Applies Function states to the engine."""
366
407
  functions_list = self._state.functions
367
408
  for i in range(len(functions_list)):
368
409
  result = self._apply_exec.apply(
@@ -375,6 +416,44 @@ class SolutionManager:
375
416
  )
376
417
  save_yaml(self._state_path, self._state)
377
418
 
419
+ imported_functions_list = self._state.imported_functions
420
+ for i in range(len(imported_functions_list)):
421
+ result = self._apply_exec.apply(
422
+ model=Function,
423
+ local_instance=imported_functions_list[i],
424
+ not_found_is_exception=True,
425
+ )
426
+ if result.update:
427
+ imported_functions_list[i] = Function.model_validate(
428
+ result.updated_dict
429
+ )
430
+ save_yaml(self._state_path, self._state)
431
+
432
+ def _apply_alerts_state(self):
433
+ """Applies Alert states to the engine."""
434
+ alerts_list = self._state.alerts
435
+ for i in range(len(alerts_list)):
436
+ result = self._apply_exec.apply(
437
+ model=Alert,
438
+ local_instance=alerts_list[i],
439
+ )
440
+ if result.update:
441
+ alerts_list[i] = Alert.model_validate(result.updated_dict)
442
+ save_yaml(self._state_path, self._state)
443
+
444
+ imported_alerts_list = self._state.imported_alerts
445
+ for i in range(len(imported_alerts_list)):
446
+ result = self._apply_exec.apply(
447
+ model=Alert,
448
+ local_instance=imported_alerts_list[i],
449
+ not_found_is_exception=True,
450
+ )
451
+ if result.update:
452
+ imported_alerts_list[i] = Alert.model_validate(
453
+ result.updated_dict
454
+ )
455
+ save_yaml(self._state_path, self._state)
456
+
378
457
  def _apply_files_state(self):
379
458
  """Applies Files states to the engine."""
380
459
  files_list = self._state.files
@@ -3,6 +3,8 @@ from typing import Callable, List, Type
3
3
 
4
4
  from rich.console import Console
5
5
  from splight_lib.models import (
6
+ Alert,
7
+ AlertItem,
6
8
  Asset,
7
9
  Attribute,
8
10
  File,
@@ -11,6 +13,8 @@ from splight_lib.models import (
11
13
  RoutineObject,
12
14
  Secret,
13
15
  )
16
+ from splight_lib.models.alert import AlertThreshold
17
+ from splight_lib.models.asset import GeometryCollection
14
18
 
15
19
  from splight_cli.solution.exceptions import ElemnentAlreadyDefined
16
20
  from splight_cli.solution.models import Component
@@ -23,7 +27,8 @@ CheckResult = namedtuple(
23
27
  "secrets_to_delete",
24
28
  "files_to_delete",
25
29
  "components_to_delete",
26
- "functions_to_detele",
30
+ "functions_to_delete",
31
+ "alerts_to_delete",
27
32
  "plan",
28
33
  "state",
29
34
  ),
@@ -87,6 +92,18 @@ class SolutionChecker:
87
92
  Function.__name__,
88
93
  self._update_function,
89
94
  )
95
+ alerts_to_delete = self._check_elements(
96
+ self._plan.alerts,
97
+ self._state.alerts,
98
+ Alert.__name__,
99
+ self._update_alert,
100
+ )
101
+ alerts_to_delete += self._check_elements(
102
+ self._plan.imported_alerts,
103
+ self._state.imported_alerts,
104
+ Alert.__name__,
105
+ self._update_alert,
106
+ )
90
107
  components_to_delete = self._check_elements(
91
108
  self._plan.components,
92
109
  self._state.components,
@@ -105,7 +122,8 @@ class SolutionChecker:
105
122
  secrets_to_delete=secrets_to_delete,
106
123
  files_to_delete=files_to_delete,
107
124
  components_to_delete=components_to_delete,
108
- functions_to_detele=functions_to_delete,
125
+ functions_to_delete=functions_to_delete,
126
+ alerts_to_delete=alerts_to_delete,
109
127
  plan=self._plan,
110
128
  state=self._state,
111
129
  )
@@ -186,7 +204,9 @@ class SolutionChecker:
186
204
  Updated asset.
187
205
  """
188
206
  plan_asset_dict = plan_asset.model_dump(
189
- exclude={"attributes"}, exclude_none=True, exclude_unset=True
207
+ exclude={"attributes", "geomtry"},
208
+ exclude_none=True,
209
+ exclude_unset=True,
190
210
  )
191
211
  state_asset = state_asset.model_copy(update=plan_asset_dict)
192
212
  self._check_elements(
@@ -195,6 +215,8 @@ class SolutionChecker:
195
215
  Attribute.__name__,
196
216
  self._update_attribute,
197
217
  )
218
+ if geometry := plan_asset_dict.get("geometry", {}):
219
+ state_asset.geometry = GeometryCollection.model_validate(geometry)
198
220
  return state_asset
199
221
 
200
222
  def _update_attribute(
@@ -227,9 +249,9 @@ class SolutionChecker:
227
249
 
228
250
  Parameters
229
251
  ----------
230
- plan_secret : secret
252
+ plan_secret : Secret
231
253
  Plan secret.
232
- state_secret : secret
254
+ state_secret : Secret
233
255
  State secret to be updated based on the plan secret.
234
256
 
235
257
  Returns
@@ -353,3 +375,35 @@ class SolutionChecker:
353
375
  exclude_unset=True,
354
376
  )
355
377
  return state_function_item.model_copy(update=plan_function_item_dict)
378
+
379
+ def _update_alert(self, plan_alert: Alert, state_alert: Alert):
380
+ plan_alert_dict = plan_alert.model_dump(
381
+ exclude_none=True, exclude_unset=True, exclude={"alert_items"}
382
+ )
383
+ state_alert = state_alert.model_copy(update=plan_alert_dict)
384
+
385
+ state_alert.stmt_thresholds = []
386
+ for threshold in plan_alert_dict["stmt_thresholds"]:
387
+ state_alert.stmt_thresholds.append(
388
+ AlertThreshold.model_validate(threshold)
389
+ )
390
+
391
+ self._check_elements(
392
+ plan_alert.alert_items,
393
+ state_alert.alert_items,
394
+ AlertItem.__name__,
395
+ self._update_alert_item,
396
+ accesor="ref_id",
397
+ )
398
+ return state_alert
399
+
400
+ def _update_alert_item(
401
+ self,
402
+ plan_alert_item: AlertItem,
403
+ state_alert_item: AlertItem,
404
+ ):
405
+ plan_alert_item_dict = plan_alert_item.model_dump(
406
+ exclude_none=True,
407
+ exclude_unset=True,
408
+ )
409
+ return state_alert_item.model_copy(update=plan_alert_item_dict)
@@ -4,6 +4,10 @@ from splight_cli.solution.solution import StateSolution
4
4
  from splight_cli.solution.utils import to_dict
5
5
 
6
6
  TEST_PLAN = {
7
+ "secrets": [],
8
+ "files": [],
9
+ "functions": [],
10
+ "alerts": [],
7
11
  "assets": [
8
12
  {
9
13
  "attributes": [
@@ -171,8 +175,8 @@ TEST_PLAN = {
171
175
  "sensitive": False,
172
176
  "type": "DataAddress",
173
177
  "value": {
174
- "asset": "local.{{test_asset}}",
175
- "attribute": "attr_1",
178
+ "asset": f"asset.{{{{test_asset}}}}",
179
+ "attribute": f"asset.{{{{test_asset}}}}.attribute.{{{{attr_1}}}}",
176
180
  },
177
181
  "value_type": "Number",
178
182
  },
@@ -199,12 +203,12 @@ TEST_PLAN = {
199
203
  "type": "DataAddress",
200
204
  "value": [
201
205
  {
202
- "asset": "local.{{test_asset}}",
203
- "attribute": "attr_2",
206
+ "asset": f"asset.{{{{test_asset}}}}",
207
+ "attribute": f"asset.{{{{test_asset}}}}.attribute.{{{{attr_2}}}}",
204
208
  },
205
209
  {
206
- "asset": "local.{{test_asset}}",
207
- "attribute": "attr_3",
210
+ "asset": f"asset.{{{{test_asset}}}}",
211
+ "attribute": f"asset.{{{{test_asset}}}}.attribute.{{{{attr_3}}}}",
208
212
  },
209
213
  ],
210
214
  "value_type": "Number",
@@ -222,8 +226,8 @@ TEST_PLAN = {
222
226
  "sensitive": False,
223
227
  "type": "DataAddress",
224
228
  "value": {
225
- "asset": "local.{{test_asset}}",
226
- "attribute": "attr_4",
229
+ "asset": f"asset.{{{{test_asset}}}}",
230
+ "attribute": f"asset.{{{{test_asset}}}}.attribute.{{{{attr_4}}}}",
227
231
  },
228
232
  "value_type": "Number",
229
233
  },
@@ -275,6 +279,10 @@ TEST_PLAN = {
275
279
  "imported_components": [],
276
280
  }
277
281
  TEST_STATE_FILLED = {
282
+ "secrets": [],
283
+ "files": [],
284
+ "functions": [],
285
+ "alerts": [],
278
286
  "assets": [
279
287
  {
280
288
  "attributes": [
@@ -22,8 +22,8 @@ def test_plan_print(load_yaml_mock, save_yaml_mock):
22
22
  def test_plan_raise_undefined_asset_name(load_yaml_mock, save_yaml_mock):
23
23
  plan = get_plan()
24
24
  plan["components"][0]["routines"][0]["output"][0]["value"] = {
25
- "asset": "local.{{test_asset}}",
26
- "attribute": "attr_5",
25
+ "asset": f"asset.{{{{test_asset}}}}",
26
+ "attribute": f"asset.{{{{test_asset}}}}.attribute.{{{{attr_5}}}}",
27
27
  }
28
28
  load_yaml_mock.side_effect = [plan, plan]
29
29
  solution_manager = SolutionManager("./dummy_path", "./dummy_path")
@@ -10,6 +10,7 @@ from pydantic import BaseModel
10
10
  from rich.console import Console
11
11
  from rich.style import Style
12
12
  from splight_lib.models import (
13
+ Alert,
13
14
  Asset,
14
15
  Component,
15
16
  File,
@@ -20,7 +21,9 @@ from splight_lib.models import (
20
21
 
21
22
  console = Console()
22
23
 
23
- SplightTypes = Union[Asset, Secret, Component, RoutineObject, File, Function]
24
+ SplightTypes = Union[
25
+ Asset, Alert, Secret, Component, RoutineObject, File, Function
26
+ ]
24
27
 
25
28
  MatchResult = namedtuple(
26
29
  "MatchResult", ["is_id", "type", "asset", "attribute"]
@@ -15,4 +15,4 @@ def get_template(name) -> Template:
15
15
  raise MissingTemplate(f"Unable to find template {template_path}")
16
16
  with open(template_path, "r+") as f:
17
17
  content = f.read()
18
- return Template(content)
18
+ return Template(content, autoescape=True)