splight-cli 4.9.0__tar.gz → 4.11.0__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 (93) hide show
  1. {splight_cli-4.9.0 → splight_cli-4.11.0}/PKG-INFO +13 -10
  2. {splight_cli-4.9.0 → splight_cli-4.11.0}/README.md +11 -8
  3. {splight_cli-4.9.0 → splight_cli-4.11.0}/pyproject.toml +3 -3
  4. splight_cli-4.11.0/splight_cli/solution/__init__.py +88 -0
  5. splight_cli-4.11.0/splight_cli/solution/dict.py +50 -0
  6. splight_cli-4.11.0/splight_cli/solution/parser.py +103 -0
  7. splight_cli-4.11.0/splight_cli/solution/resource/__init__.py +5 -0
  8. splight_cli-4.11.0/splight_cli/solution/resource/diff.py +39 -0
  9. splight_cli-4.11.0/splight_cli/solution/resource/logger.py +36 -0
  10. splight_cli-4.11.0/splight_cli/solution/resource/manager.py +237 -0
  11. splight_cli-4.11.0/splight_cli/solution/resource/models/__init__.py +25 -0
  12. splight_cli-4.11.0/splight_cli/solution/resource/models/alert.py +14 -0
  13. splight_cli-4.11.0/splight_cli/solution/resource/models/asset.py +8 -0
  14. splight_cli-4.11.0/splight_cli/solution/resource/models/attribute.py +8 -0
  15. splight_cli-4.11.0/splight_cli/solution/resource/models/base.py +77 -0
  16. splight_cli-4.11.0/splight_cli/solution/resource/models/component.py +12 -0
  17. splight_cli-4.11.0/splight_cli/solution/resource/models/component_object.py +8 -0
  18. splight_cli-4.11.0/splight_cli/solution/resource/models/file.py +8 -0
  19. splight_cli-4.11.0/splight_cli/solution/resource/models/function.py +14 -0
  20. splight_cli-4.11.0/splight_cli/solution/resource/models/metadata.py +8 -0
  21. splight_cli-4.11.0/splight_cli/solution/resource/models/routine.py +9 -0
  22. splight_cli-4.11.0/splight_cli/solution/resource/models/secret.py +38 -0
  23. splight_cli-4.11.0/splight_cli/solution/state.py +56 -0
  24. splight_cli-4.9.0/splight_cli/solution/__init__.py +0 -103
  25. splight_cli-4.9.0/splight_cli/solution/apply_exec.py +0 -180
  26. splight_cli-4.9.0/splight_cli/solution/destroyer.py +0 -42
  27. splight_cli-4.9.0/splight_cli/solution/exceptions.py +0 -6
  28. splight_cli-4.9.0/splight_cli/solution/importer.py +0 -87
  29. splight_cli-4.9.0/splight_cli/solution/models.py +0 -45
  30. splight_cli-4.9.0/splight_cli/solution/plan_exec.py +0 -123
  31. splight_cli-4.9.0/splight_cli/solution/replacer.py +0 -357
  32. splight_cli-4.9.0/splight_cli/solution/solution.py +0 -466
  33. splight_cli-4.9.0/splight_cli/solution/solution_checker.py +0 -409
  34. splight_cli-4.9.0/splight_cli/solution/tests/constants.py +0 -559
  35. splight_cli-4.9.0/splight_cli/solution/tests/test_apply.py +0 -244
  36. splight_cli-4.9.0/splight_cli/solution/tests/test_destroy.py +0 -107
  37. splight_cli-4.9.0/splight_cli/solution/tests/test_import.py +0 -154
  38. splight_cli-4.9.0/splight_cli/solution/tests/test_plan.py +0 -52
  39. splight_cli-4.9.0/splight_cli/solution/utils.py +0 -80
  40. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/__init__.py +0 -0
  41. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/cli.py +0 -0
  42. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/__init__.py +0 -0
  43. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/component.py +0 -0
  44. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/exceptions.py +0 -0
  45. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/loaders.py +0 -0
  46. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/.splightignore +0 -0
  47. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/Initialization +0 -0
  48. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/README.md +0 -0
  49. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/auto_readme.md +0 -0
  50. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/main.py +0 -0
  51. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/spec.json +0 -0
  52. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/templates/tests.py +0 -0
  53. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/tests/__init__.py +0 -0
  54. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/tests/test_component_manager.py +0 -0
  55. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/component/utils.py +0 -0
  56. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/config/__init__.py +0 -0
  57. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/constants.py +0 -0
  58. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/__init__.py +0 -0
  59. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/context.py +0 -0
  60. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/context/workspace.py +0 -0
  61. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/__init__.py +0 -0
  62. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/alert/__init__.py +0 -0
  63. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/asset/__init__.py +0 -0
  64. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/attribute/__init__.py +0 -0
  65. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/component/__init__.py +0 -0
  66. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/datalake/__init__.py +0 -0
  67. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/file/__init__.py +0 -0
  68. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/__init__.py +0 -0
  69. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/exceptions.py +0 -0
  70. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/manager.py +0 -0
  71. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/tests/test_datalake_manager.py +0 -0
  72. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/manager/tests/test_resource_manager.py +0 -0
  73. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/engine/secret/__init__.py +0 -0
  74. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/__init__.py +0 -0
  75. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/__init__.py +0 -0
  76. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/exceptions.py +0 -0
  77. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/hub_manager.py +0 -0
  78. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/hub/component/tests/test_hub_manager.py +0 -0
  79. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/settings.py +0 -0
  80. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/Initialization +0 -0
  81. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/README.md +0 -0
  82. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/main.py +0 -0
  83. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/spec.json +0 -0
  84. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/tests/test_component/tests.py +0 -0
  85. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/__init__.py +0 -0
  86. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/input.py +0 -0
  87. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/json.py +0 -0
  88. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/loader.py +0 -0
  89. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/pprint.py +0 -0
  90. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/template.py +0 -0
  91. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/utils/yaml.py +0 -0
  92. {splight_cli-4.9.0 → splight_cli-4.11.0}/splight_cli/version.py +0 -0
  93. {splight_cli-4.9.0 → splight_cli-4.11.0}/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.9.0
3
+ Version: 4.11.0
4
4
  Summary: Splight Command Line Interface
5
5
  Author: Splight Dev
6
6
  Author-email: dev@splight-ae.com
@@ -15,8 +15,6 @@ Requires-Dist: case-converter (==1.1.0)
15
15
  Requires-Dist: click (==8.0.4)
16
16
  Requires-Dist: click-default-group (==1.2.2)
17
17
  Requires-Dist: colorama (==0.4.4)
18
- Requires-Dist: deepdiff (==6.3.1)
19
- Requires-Dist: mock (>=5.1.0,<6.0.0)
20
18
  Requires-Dist: packaging (==23.0)
21
19
  Requires-Dist: pathspec (==0.11.1)
22
20
  Requires-Dist: py7zr (==0.20.8)
@@ -24,14 +22,12 @@ Requires-Dist: requests (==2.31.0)
24
22
  Requires-Dist: rich (==13.0.1)
25
23
  Requires-Dist: setuptools (==60.9.3)
26
24
  Requires-Dist: splight-lib (>=5.2.2,<6.0.0)
25
+ Requires-Dist: toposort (>=1.10,<2.0)
27
26
  Requires-Dist: typer (==0.9.0)
28
27
  Description-Content-Type: text/markdown
29
28
 
30
29
  # Splight CLI
31
30
 
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
31
  ---
36
32
 
37
33
  ## Table of Content
@@ -76,6 +72,7 @@ pip install splight-cli
76
72
  ```
77
73
 
78
74
  Once you have installed `splight-cli` you can check the installation with
75
+
79
76
  ```bash
80
77
  splight --version
81
78
  ```
@@ -251,12 +248,15 @@ You can also retrieve one of the parameters using the `get` command:
251
248
  ```bash
252
249
  splight configure get <parameter>
253
250
  ```
251
+
254
252
  for example for reading the `SPLIGHT_PLATFORM_API_HOST`:
253
+
255
254
  ```bash
256
255
  splight configure get splight_platform_api_host
257
256
  ```
258
257
 
259
258
  In the same way you can modify one parameter using the `set` command
259
+
260
260
  ```bash
261
261
  splight configure set <parameter> <value>
262
262
  ```
@@ -311,6 +311,7 @@ The `engine` command is used for interacting with the Splight Engine. So far, th
311
311
  subcommands provide an interface for creating, reaading and deleting resources in the engine.
312
312
 
313
313
  The command is
314
+
314
315
  ```bash
315
316
  splight engine <subcommand> <action> [extra args]
316
317
  ```
@@ -319,6 +320,7 @@ depending on the which sub-command you use you can get different actions
319
320
  to perform
320
321
 
321
322
  The valid sub-commands so far are:
323
+
322
324
  - `asset`
323
325
  - `attribute`
324
326
  - `component`
@@ -344,7 +346,7 @@ The available subcommands are
344
346
 
345
347
  - `create <name>` to create a new workspace. After the creation of a new worskpace you need to configure _Splight CLI_ with `splight configure`.
346
348
  - `delete <name>` to delete an existing workspace.
347
- - `list` to list all workspaces. Current workspace displays a '*' to the left.
349
+ - `list` to list all workspaces. Current workspace displays a '\*' to the left.
348
350
  - `select <name>` to switch between different configured workspaces.
349
351
  - `show <name>` to display the contents of an existing workspace.
350
352
 
@@ -580,10 +582,10 @@ splight component run <component directory> --local
580
582
  ```
581
583
 
582
584
  This way, the component will run using local clients for database and datalake. This is extremely
583
- useful for development since you can create instances of the different database objects in the
584
- local database for running different scenearios or differents tests. The same can be applied for
585
+ useful for development since you can create instances of the different database objects in the
586
+ local database for running different scenearios or differents tests. The same can be applied for
585
587
  datalake data, the local client stores the data in files. In both cases, for database and datalake,
586
- the files are created in the same directory as the `__init__.py` file of the component, so you
588
+ the files are created in the same directory as the `__init__.py` file of the component, so you
587
589
  can modified it based on your needs.
588
590
 
589
591
  You can interact with the local databases using the library, for example
@@ -605,3 +607,4 @@ db_client.save(attribute)
605
607
 
606
608
  df = client.get_dataframe(Number, asset=all_assets[0].id, attribute=attribute.id)
607
609
  ```
610
+
@@ -1,8 +1,5 @@
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
3
  ---
7
4
 
8
5
  ## Table of Content
@@ -47,6 +44,7 @@ pip install splight-cli
47
44
  ```
48
45
 
49
46
  Once you have installed `splight-cli` you can check the installation with
47
+
50
48
  ```bash
51
49
  splight --version
52
50
  ```
@@ -222,12 +220,15 @@ You can also retrieve one of the parameters using the `get` command:
222
220
  ```bash
223
221
  splight configure get <parameter>
224
222
  ```
223
+
225
224
  for example for reading the `SPLIGHT_PLATFORM_API_HOST`:
225
+
226
226
  ```bash
227
227
  splight configure get splight_platform_api_host
228
228
  ```
229
229
 
230
230
  In the same way you can modify one parameter using the `set` command
231
+
231
232
  ```bash
232
233
  splight configure set <parameter> <value>
233
234
  ```
@@ -282,6 +283,7 @@ The `engine` command is used for interacting with the Splight Engine. So far, th
282
283
  subcommands provide an interface for creating, reaading and deleting resources in the engine.
283
284
 
284
285
  The command is
286
+
285
287
  ```bash
286
288
  splight engine <subcommand> <action> [extra args]
287
289
  ```
@@ -290,6 +292,7 @@ depending on the which sub-command you use you can get different actions
290
292
  to perform
291
293
 
292
294
  The valid sub-commands so far are:
295
+
293
296
  - `asset`
294
297
  - `attribute`
295
298
  - `component`
@@ -315,7 +318,7 @@ The available subcommands are
315
318
 
316
319
  - `create <name>` to create a new workspace. After the creation of a new worskpace you need to configure _Splight CLI_ with `splight configure`.
317
320
  - `delete <name>` to delete an existing workspace.
318
- - `list` to list all workspaces. Current workspace displays a '*' to the left.
321
+ - `list` to list all workspaces. Current workspace displays a '\*' to the left.
319
322
  - `select <name>` to switch between different configured workspaces.
320
323
  - `show <name>` to display the contents of an existing workspace.
321
324
 
@@ -551,10 +554,10 @@ splight component run <component directory> --local
551
554
  ```
552
555
 
553
556
  This way, the component will run using local clients for database and datalake. This is extremely
554
- useful for development since you can create instances of the different database objects in the
555
- local database for running different scenearios or differents tests. The same can be applied for
557
+ useful for development since you can create instances of the different database objects in the
558
+ local database for running different scenearios or differents tests. The same can be applied for
556
559
  datalake data, the local client stores the data in files. In both cases, for database and datalake,
557
- the files are created in the same directory as the `__init__.py` file of the component, so you
560
+ the files are created in the same directory as the `__init__.py` file of the component, so you
558
561
  can modified it based on your needs.
559
562
 
560
563
  You can interact with the local databases using the library, for example
@@ -575,4 +578,4 @@ attribute = Attribute(name="SomeAttribute")
575
578
  db_client.save(attribute)
576
579
 
577
580
  df = client.get_dataframe(Number, asset=all_assets[0].id, attribute=attribute.id)
578
- ```
581
+ ```
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "splight-cli"
3
- version = "4.9.0"
3
+ version = "4.11.0"
4
4
  description = "Splight Command Line Interface"
5
5
  authors = ["Splight Dev <dev@splight-ae.com>"]
6
6
  readme = "README.md"
@@ -20,8 +20,7 @@ PyYAML = "6.0.1"
20
20
  rich = "13.0.1"
21
21
  pathspec = "0.11.1"
22
22
  packaging = "23.0"
23
- deepdiff = "6.3.1"
24
- mock = "^5.1.0"
23
+ toposort = "^1.10"
25
24
 
26
25
  [tool.poetry.group.dev.dependencies]
27
26
  black = "23.3.0"
@@ -32,6 +31,7 @@ flake8 = "^6.1.0"
32
31
  pre-commit = "3.2.2"
33
32
  pytest = "^7.4.3"
34
33
  coverage = "^7.3.2"
34
+ mock = "^5.1.0"
35
35
 
36
36
  [tool.poetry.scripts]
37
37
  splight = "splight_cli.cli:app"
@@ -0,0 +1,88 @@
1
+ import typer
2
+ from rich.console import Console
3
+
4
+ from splight_cli.context import check_credentials
5
+ from splight_cli.solution.parser import Parser
6
+ from splight_cli.solution.resource import ResourceManager
7
+ from splight_cli.solution.state import State
8
+
9
+ solution_app = typer.Typer(
10
+ name="Splight Solution",
11
+ add_completion=True,
12
+ rich_markup_mode="rich",
13
+ no_args_is_help=True,
14
+ )
15
+
16
+ console = Console()
17
+
18
+
19
+ @solution_app.callback(invoke_without_command=True)
20
+ def callback(ctx: typer.Context):
21
+ check_credentials(ctx)
22
+
23
+
24
+ # FIXME: find a nice way to print the exceptions.
25
+ # Take into account that most exception messages are short and do not have
26
+ # that much content. I would include the traceback, or at least some part of it.
27
+ # Another option is to not catch them, and show the entire error, so the user can
28
+ # debug it.
29
+ @solution_app.command()
30
+ def apply(
31
+ _: typer.Context,
32
+ spec_file: str = typer.Argument(..., help="Path to the spec file."),
33
+ state_file: str = typer.Option(
34
+ "state.json", "--state", "-s", help="Path to the state file."
35
+ ),
36
+ ) -> None:
37
+ state = State(path=state_file)
38
+ state.load()
39
+
40
+ parser = Parser(spec_file=spec_file)
41
+ specs, dependency_graph = parser.parse()
42
+
43
+ manager = ResourceManager(
44
+ state=state,
45
+ specs=specs,
46
+ dependency_graph=dependency_graph,
47
+ )
48
+ manager.refresh()
49
+ plan = manager.plan()
50
+ if plan:
51
+ manager.apply()
52
+
53
+
54
+ @solution_app.command()
55
+ def plan(
56
+ _: typer.Context,
57
+ spec_file: str = typer.Argument(..., help="Path to the spec file."),
58
+ state_file: str = typer.Option(
59
+ "state.json", "--state", "-s", help="Path to the state file."
60
+ ),
61
+ ) -> None:
62
+ state = State(path=state_file)
63
+ state.load()
64
+
65
+ parser = Parser(spec_file=spec_file)
66
+ specs, dependency_graph = parser.parse()
67
+
68
+ manager = ResourceManager(
69
+ state=state,
70
+ specs=specs,
71
+ dependency_graph=dependency_graph,
72
+ )
73
+ manager.refresh()
74
+ manager.plan()
75
+
76
+
77
+ @solution_app.command()
78
+ def refresh(
79
+ _: typer.Context,
80
+ state_file: str = typer.Option(
81
+ "state.json", "--state", "-s", help="Path to the state file."
82
+ ),
83
+ ) -> None:
84
+ state = State(path=state_file)
85
+ state.load()
86
+
87
+ manager = ResourceManager(state=state)
88
+ manager.refresh()
@@ -0,0 +1,50 @@
1
+ from typing import Any, Dict, List
2
+
3
+
4
+ # FIXME: make this faster using a stack
5
+ def walk_dict(data: Dict, current=[]):
6
+ result = []
7
+
8
+ if isinstance(data, Dict):
9
+ for key, value in data.items():
10
+ current.append(key)
11
+ result.extend(walk_dict(value, current.copy()))
12
+ current.pop()
13
+ elif isinstance(data, List):
14
+ for index, item in enumerate(data):
15
+ current.append(index)
16
+ result.extend(walk_dict(item, current.copy()))
17
+ current.pop()
18
+ else:
19
+ result.append((current.copy(), data))
20
+
21
+ return result
22
+
23
+
24
+ def get_dict_value(path: List, data: Dict):
25
+ # Assumes the path exists
26
+ current = data
27
+ for key in path:
28
+ if isinstance(current, dict):
29
+ current = current[key]
30
+ elif isinstance(current, list):
31
+ current = current[int(key)]
32
+ else:
33
+ raise ValueError(f"Invalid path: {path}")
34
+ return current
35
+
36
+
37
+ def set_dict_value(value: Any, path: List, data: Dict):
38
+ # Assumes the path exists
39
+ current = data
40
+ for key in path[:-1]:
41
+ current = current[key]
42
+
43
+ last_key = path[-1]
44
+
45
+ if isinstance(current, dict):
46
+ current[last_key] = value
47
+ elif isinstance(current, list):
48
+ current[int(last_key)] = value
49
+ else:
50
+ raise ValueError(f"Invalid path: {path}")
@@ -0,0 +1,103 @@
1
+ import re
2
+ from typing import Dict, List, Optional
3
+
4
+ import yaml
5
+
6
+ from splight_cli.solution.dict import walk_dict
7
+
8
+
9
+ class DuplicateResourceError(Exception):
10
+ pass
11
+
12
+
13
+ def parse_reference(value: str) -> Optional[Dict]:
14
+ """Return the reference if it matches the regex or None otherwise"""
15
+ # FIXME: make it so we are able to reference the 'name' of a resource.
16
+ # This is super easy to do:
17
+ # 1. Add '.id' and '.name' at the end of the regex
18
+ # 2. Save that accessor to the references dict in each spec.
19
+ # 3. Change the 'replace_references' function in ResourceManager so it
20
+ # gets the correct attribute.
21
+
22
+ # Only process strings
23
+ if isinstance(value, str):
24
+ # Building this with format strings breaks the expression
25
+ pattern = r"^\${{(Asset|Attribute|Metadata|File|Function|Secret|Alert|Component|Routine)\.(\w+)}}$"
26
+
27
+ # Get the matches
28
+ match = re.match(pattern, value)
29
+
30
+ if match:
31
+ type, name = match.groups()
32
+ key = f"{type}:{name}"
33
+ return key
34
+
35
+
36
+ class Parser:
37
+ def __init__(self, spec_file: str) -> None:
38
+ try:
39
+ with open(spec_file, "r") as fp:
40
+ self._spec_file_data = yaml.safe_load(fp)
41
+ except FileNotFoundError:
42
+ raise Exception(f"Spec file '{spec_file}' not found.")
43
+
44
+ def parse(self) -> List[Dict]:
45
+ # Here we build the dependency graph using the resource keys
46
+ dependency_graph = {}
47
+
48
+ # - Does not destroy all elements.
49
+ specs = {}
50
+ for resource_spec in self._spec_file_data or []:
51
+ name = resource_spec["name"]
52
+ type = resource_spec["type"]
53
+ arguments = resource_spec["arguments"]
54
+
55
+ # Parse each resource leaf value to see if its a reference
56
+ depends_on = set({})
57
+ references = []
58
+ for path, value in walk_dict(resource_spec["arguments"]):
59
+ result = parse_reference(value)
60
+ if result is not None:
61
+ key = result
62
+
63
+ # Must be done here, otherwise it would affect the dependency graph.
64
+ # Paths and values are validated later on, since we may need data
65
+ # that is available after creation.
66
+ # This also prevents trying to delete a resource which has another
67
+ # one referencing it.
68
+ # Also prevents self-referencing.
69
+ if key not in specs:
70
+ raise ValueError(
71
+ f"Reference '{value}' in resource '{name}' of type '{type}' points to a non existing resource."
72
+ )
73
+
74
+ reference = {
75
+ "key": key, # Key of the referenced resource
76
+ "target": path, # Where to put the value
77
+ "string": value, # The string value of the reference
78
+ }
79
+
80
+ references.append(reference)
81
+ depends_on.add(key)
82
+
83
+ key = f"{type}:{name}"
84
+ if key in specs:
85
+ raise DuplicateResourceError(
86
+ f"Resource '{name}' of type '{type}' is defined twice."
87
+ )
88
+
89
+ specs[key] = {
90
+ "name": name,
91
+ "type": type,
92
+ "depends_on": list(depends_on),
93
+ "references": references,
94
+ "arguments": arguments,
95
+ }
96
+
97
+ # Add its dependecies to the graph
98
+ dependency_graph[key] = depends_on
99
+
100
+ # NOTE: Before adding/changing features of the parser, insert a breakpoint
101
+ # here and inspect the specs and dependency_graph objects, to see the shape
102
+ # of the objects that the rest of the code needs.
103
+ return specs, dependency_graph
@@ -0,0 +1,5 @@
1
+ from splight_cli.solution.resource.manager import ResourceManager
2
+
3
+ __all__ = [
4
+ "ResourceManager",
5
+ ]
@@ -0,0 +1,39 @@
1
+ from typing import Dict
2
+
3
+ from colorama import Fore, Style
4
+
5
+ from splight_cli.solution.dict import get_dict_value, walk_dict
6
+
7
+
8
+ def strike_text(text):
9
+ return "".join(["\u0336{}".format(c) for c in str(text)])
10
+
11
+
12
+ def Diff(new_arguments: Dict, old_arguments: Dict) -> Dict:
13
+ # FIXME: this try catch mechanic could be simplified by improving
14
+ # the 'Dict' related functions.
15
+
16
+ # FIXME: Try replacing the QueryFilter in a function for a single value.
17
+ # The diff will show incorrectly.
18
+ diff = []
19
+ for path, new_value in walk_dict(new_arguments):
20
+ try:
21
+ get_dict_value(path, old_arguments)
22
+ except:
23
+ line = f"\t{Fore.GREEN}+{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {new_value}"
24
+ diff.append(line)
25
+ continue
26
+
27
+ old_value = get_dict_value(path, old_arguments)
28
+ if new_value != old_value:
29
+ line = f"\t{Fore.YELLOW}~{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {strike_text(old_value)} -> {new_value} "
30
+ diff.append(line)
31
+
32
+ for path, old_value in walk_dict(old_arguments):
33
+ try:
34
+ get_dict_value(path, new_arguments)
35
+ except:
36
+ line = f"\t{Fore.RED}-{Style.RESET_ALL} {'.'.join([str(key) for key in path])}: {strike_text(old_value)}"
37
+ diff.append(line)
38
+
39
+ return diff
@@ -0,0 +1,36 @@
1
+ from colorama import Style
2
+
3
+
4
+ class ResourceLogger:
5
+ template = '{type}["{name}"]: {message} [id={id}]'
6
+
7
+ @staticmethod
8
+ def _bold(message: str) -> str:
9
+ return f"{Style.BRIGHT}{message}{Style.RESET_ALL}"
10
+
11
+ def resource(self, message: str, resource):
12
+ formatted_message = self.template.format(
13
+ type=resource.type,
14
+ name=resource.name,
15
+ id=resource.id,
16
+ message=message,
17
+ )
18
+ print(self._bold(formatted_message))
19
+
20
+ def event(
21
+ self,
22
+ message: str,
23
+ previous_line: bool = False,
24
+ new_line: bool = False,
25
+ bold: bool = False,
26
+ ):
27
+ formatted_message = ""
28
+ if previous_line:
29
+ formatted_message += "\n"
30
+ formatted_message += f"{self._bold(message)}" if bold else message
31
+ if new_line:
32
+ formatted_message += "\n"
33
+ print(formatted_message)
34
+
35
+ def diff(self, diff):
36
+ print("\n".join(diff))