ansys-saf-cli 4.1.dev0__py3-none-any.whl

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 (143) hide show
  1. ansys/saf/cli/__init__.py +15 -0
  2. ansys/saf/cli/_cli/__init__.py +15 -0
  3. ansys/saf/cli/_cli/main.py +816 -0
  4. ansys/saf/cli/_cli/py.typed +0 -0
  5. ansys/saf/cli/_config/__init__.py +15 -0
  6. ansys/saf/cli/_config/const.py +45 -0
  7. ansys/saf/cli/_config/py.typed +0 -0
  8. ansys/saf/cli/_database/__init__.py +15 -0
  9. ansys/saf/cli/_database/manager.py +94 -0
  10. ansys/saf/cli/_database/models.py +53 -0
  11. ansys/saf/cli/_database/py.typed +0 -0
  12. ansys/saf/cli/_solutions/__init__.py +15 -0
  13. ansys/saf/cli/_solutions/add_step.py +256 -0
  14. ansys/saf/cli/_solutions/dependencies.py +162 -0
  15. ansys/saf/cli/_solutions/environment.py +1145 -0
  16. ansys/saf/cli/_solutions/plugins.py +254 -0
  17. ansys/saf/cli/_solutions/py.typed +0 -0
  18. ansys/saf/cli/_solutions/scaffolding.py +73 -0
  19. ansys/saf/cli/_solutions/templates/__init__.py +15 -0
  20. ansys/saf/cli/_solutions/templates/py.typed +0 -0
  21. ansys/saf/cli/_solutions/templates/solution/cookiecutter.json +18 -0
  22. ansys/saf/cli/_solutions/templates/solution/hooks/post_gen_project.py +54 -0
  23. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.codespell.ignore +2 -0
  24. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.devcontainer/devcontainer.json +26 -0
  25. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.env +5 -0
  26. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.flake8 +8 -0
  27. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/labeler.yml +9 -0
  28. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/labels.yml +27 -0
  29. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/workflows/build-release.yml +175 -0
  30. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/workflows/label.yml +89 -0
  31. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.gitignore +137 -0
  32. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.pre-commit-config.yaml +48 -0
  33. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.release-please-manifest.json +3 -0
  34. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.reuse/templates/copyright.jinja2 +1 -0
  35. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.vscode/extensions.json +8 -0
  36. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.vscode/launch.json +28 -0
  37. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/AUTHORS +12 -0
  38. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CHANGELOG.md +1 -0
  39. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CODEOWNERS +3 -0
  40. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CODE_OF_CONDUCT.md +65 -0
  41. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CONTRIBUTING.md +2 -0
  42. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CONTRIBUTORS.md +9 -0
  43. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/LICENSE.rst +41 -0
  44. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/README.md +69 -0
  45. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/Dockerfile +43 -0
  46. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/README.md +13 -0
  47. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/distributed-deployment-template/.env +27 -0
  48. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/distributed-deployment-template/compose.yaml +132 -0
  49. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone/.env +20 -0
  50. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone/compose.yaml +118 -0
  51. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-hps/.env +28 -0
  52. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-hps/compose.yaml +146 -0
  53. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-minerva/.env +25 -0
  54. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-minerva/compose.yaml +122 -0
  55. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/.vale.ini +28 -0
  56. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/Makefile +30 -0
  57. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/changelog.d/changelog_template.jinja +17 -0
  58. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/make.bat +46 -0
  59. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_static/css/custom.css +8 -0
  60. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_static/images/repository-banner.png +0 -0
  61. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_templates/README.md +1 -0
  62. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/changelog.rst +12 -0
  63. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/conf.py +253 -0
  64. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/examples.rst +8 -0
  65. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/getting_started/desktop_installation.rst +165 -0
  66. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/getting_started/docker_installation.rst +68 -0
  67. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/index.rst +51 -0
  68. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/user_guide/index.rst +6 -0
  69. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/styles/config/vocabularies/ANSYS/accept.txt +5 -0
  70. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/styles/config/vocabularies/ANSYS/reject.txt +0 -0
  71. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/examples/README.md +1 -0
  72. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/lock_files/dash/poetry.lock +7621 -0
  73. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/lock_files/no_ui/poetry.lock +7173 -0
  74. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/cli/README.md +3 -0
  75. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/oauth/App_Data/Certificates/README.md +3 -0
  76. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/oauth/oauth.config +9 -0
  77. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/pyproject.toml +192 -0
  78. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/release-please-config.json +9 -0
  79. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/sonar-project.properties +12 -0
  80. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/__init__.py +3 -0
  81. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/main.py +19 -0
  82. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/application.svg +36 -0
  83. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/description.json +3 -0
  84. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/project.svg +1 -0
  85. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/definition.py +24 -0
  86. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/first_step.py +34 -0
  87. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/method_assets/README.md +1 -0
  88. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/scripts/README.md +1 -0
  89. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/scripts/assets/README.md +1 -0
  90. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/second_step.py +12 -0
  91. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/app.py +30 -0
  92. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/css/all.css +9 -0
  93. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/css/bootstrap.min.css +6 -0
  94. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/css/style.css +47 -0
  95. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/carbon--ibm-engineering-workflow-mgmt.svg +1 -0
  96. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/carbon--return.svg +1 -0
  97. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/game-icons--crossed-air-flows.svg +1 -0
  98. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/material-symbols--home.svg +1 -0
  99. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/radix-icons--moon.svg +1 -0
  100. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/radix-icons--sun.svg +1 -0
  101. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/streamline--startup-solid.svg +1 -0
  102. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/dark/teenyicons--doc-solid.svg +1 -0
  103. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/carbon--ibm-engineering-workflow-mgmt.svg +1 -0
  104. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/carbon--return.svg +1 -0
  105. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/game-icons--crossed-air-flows.svg +1 -0
  106. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/material-symbols--home.svg +1 -0
  107. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/radix-icons--moon.svg +1 -0
  108. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/radix-icons--sun.svg +1 -0
  109. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/streamline--startup-solid.svg +1 -0
  110. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/icons/light/teenyicons--doc-solid.svg +1 -0
  111. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/images/README.md +1 -0
  112. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/images/workflow-placeholder.png +0 -0
  113. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/installer/README.md +5 -0
  114. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/logos/dark/placeholder_logo.png +0 -0
  115. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/logos/light/placeholder_logo.png +0 -0
  116. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/orchestrator/README.md +3 -0
  117. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/pywebview/README.md +3 -0
  118. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/assets/scripts/README.md +1 -0
  119. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/components/README.md +1 -0
  120. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/pages/about_page.py +71 -0
  121. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/pages/first_page.py +147 -0
  122. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/pages/page.py +367 -0
  123. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/pages/second_page.py +32 -0
  124. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/common_test_files/README.md +1 -0
  125. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/conftest.py +37 -0
  126. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/unit/test_solution_api.py +42 -0
  127. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/unit/test_solution_ui.py +30 -0
  128. ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tox.ini +56 -0
  129. ansys/saf/cli/_utilities/__init__.py +15 -0
  130. ansys/saf/cli/_utilities/archiver.py +67 -0
  131. ansys/saf/cli/_utilities/backup.py +181 -0
  132. ansys/saf/cli/_utilities/conversion.py +64 -0
  133. ansys/saf/cli/_utilities/platform.py +26 -0
  134. ansys/saf/cli/_utilities/py.typed +0 -0
  135. ansys/saf/cli/_utilities/solution_modules.py +173 -0
  136. ansys/saf/cli/integrations/solutions_manager.py +64 -0
  137. ansys/saf/cli/py.typed +0 -0
  138. ansys_saf_cli-4.1.dev0.dist-info/METADATA +201 -0
  139. ansys_saf_cli-4.1.dev0.dist-info/RECORD +143 -0
  140. ansys_saf_cli-4.1.dev0.dist-info/WHEEL +4 -0
  141. ansys_saf_cli-4.1.dev0.dist-info/entry_points.txt +3 -0
  142. ansys_saf_cli-4.1.dev0.dist-info/licenses/AUTHORS +12 -0
  143. ansys_saf_cli-4.1.dev0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,254 @@
1
+ # Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ from collections.abc import Generator
18
+ from importlib.metadata import entry_points
19
+ from pathlib import Path
20
+ import sys
21
+ from typing import Any
22
+
23
+ import click
24
+ from packaging.specifiers import SpecifierSet
25
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator, model_validator
26
+ import tomlkit
27
+
28
+
29
+ class SafTemplateValidationError:
30
+ def __init__(self, template_name: str, error: ValidationError):
31
+ self.template_name = template_name
32
+ self.error = error
33
+
34
+
35
+ class SafTemplate(BaseModel):
36
+ name: str = Field(min_length=1)
37
+ type: str = Field(min_length=1)
38
+ description: str = Field(min_length=1)
39
+ location: Path
40
+ dependencies: dict[str, Any] = Field(default_factory=dict)
41
+ saf_cli_compatibility_range: SpecifierSet | None = None
42
+
43
+ model_config = ConfigDict(arbitrary_types_allowed=True)
44
+
45
+ @field_validator("location")
46
+ @classmethod
47
+ def validate_location(cls, v: Path) -> Path:
48
+ if not v.is_dir():
49
+ raise ValueError(f"Location path '{v}' is not a valid directory")
50
+ return v
51
+
52
+ @field_validator("saf_cli_compatibility_range", mode="before")
53
+ @classmethod
54
+ def validate_saf_cli_compatibility_range(cls, value: Any) -> SpecifierSet | None:
55
+ if value is None:
56
+ return None
57
+ try:
58
+ spec_str: str = str(value).strip()
59
+ return SpecifierSet(spec_str)
60
+ except Exception as e:
61
+ raise ValueError(
62
+ f"Invalid saf-cli version compatibility range: '{value}'. "
63
+ f"Make sure you define a range of versions using standard packaging specifier format, "
64
+ f"e.g. '>=0.1.0,<1.0.0' or '==0.2.5'.",
65
+ ) from e
66
+
67
+
68
+ class SafPluginValidationError:
69
+ def __init__(self, module_name: str, error: ValidationError):
70
+ self.module_name = module_name
71
+ self.error = error
72
+
73
+
74
+ class SafPlugin(BaseModel):
75
+ path: Path
76
+ module_name: str = Field(min_length=1)
77
+ templates_config: dict[str, Any] = Field(default_factory=dict)
78
+
79
+ @model_validator(mode="after")
80
+ def load_templates_config(self) -> "SafPlugin":
81
+ config_path = self.path / "templates.toml"
82
+ if not config_path.is_file():
83
+ raise ValueError(
84
+ f"Config file 'templates.toml' not found for plugin module '{self.module_name}' "
85
+ f"at expected location {config_path}",
86
+ )
87
+ try:
88
+ templates_config = tomlkit.loads(config_path.read_bytes()).unwrap()
89
+ except Exception as e:
90
+ raise ValueError(
91
+ f"Error loading config file 'templates.toml' for plugin module '{self.module_name}' "
92
+ f"at location {config_path}: {e}",
93
+ ) from e
94
+ if not templates_config.get("templates"):
95
+ raise ValueError(
96
+ f"No templates in templates configuration file for plugin module '{self.module_name}'",
97
+ )
98
+ self.templates_config = templates_config
99
+ return self
100
+
101
+
102
+ class ClickRenderer:
103
+ def __init__(self, verbose: bool = False, steps_only: bool = False, solutions_only: bool = False):
104
+ self._verbose = verbose
105
+ self._steps_only = steps_only
106
+ self._solutions_only = solutions_only
107
+
108
+ def _render_plugin_information(self, module_name: str) -> None:
109
+ click_str = (
110
+ "Step templates" if self._steps_only else "Solution templates" if self._solutions_only else "Templates"
111
+ )
112
+ click.secho(f"{click_str} in ", fg="cyan", nl=False)
113
+ click.secho(f"{module_name}", fg="cyan", bold=True)
114
+
115
+ def _render_template_information(self, saf_template: SafTemplate) -> None:
116
+ click.secho(f" {saf_template.name} ({saf_template.type}): ", fg="green", nl=False)
117
+ click.secho(f"{saf_template.description}")
118
+ if self._verbose:
119
+ click.secho(f" location: {saf_template.location}")
120
+ if saf_template.dependencies:
121
+ for group_name in saf_template.dependencies:
122
+ click.secho(f" {group_name} dependencies:")
123
+ for dependency_name, dependency_specification in saf_template.dependencies[group_name].items():
124
+ click.secho(f" - {dependency_name}: {dependency_specification}")
125
+ click.echo()
126
+
127
+ def render_plugin_and_template_information(self, plugin_templates: dict[str, list[SafTemplate]]) -> None:
128
+ for module_name, templates in plugin_templates.items():
129
+ self._render_plugin_information(module_name)
130
+ for template in templates:
131
+ if self._steps_only and template.type != "step" or self._solutions_only and template.type != "solution":
132
+ continue
133
+ self._render_template_information(template)
134
+
135
+ def render_plugin_errors(self, plugin_errors: list[SafPluginValidationError]) -> None:
136
+ if self._verbose and plugin_errors:
137
+ for error in plugin_errors:
138
+ click.secho(f"Error in plugin module '{error.module_name}':", err=True, fg="red")
139
+ click.secho(f"{error.error}", err=True, fg="red")
140
+ click.echo()
141
+ elif plugin_errors:
142
+ click.secho(
143
+ f"Warning: {len(plugin_errors)} template plugin(s) could not be loaded. "
144
+ f"Use --verbose flag for more details.",
145
+ err=True,
146
+ fg="yellow",
147
+ )
148
+
149
+ def render_template_errors(self, template_errors: list[SafTemplateValidationError]) -> None:
150
+ if self._verbose and template_errors:
151
+ for error in template_errors:
152
+ click.secho(f"Error in template '{error.template_name}':", err=True, fg="red")
153
+ click.secho(f"{error.error}", err=True, fg="red")
154
+ click.echo()
155
+ elif template_errors:
156
+ click.secho(
157
+ f"Warning: {len(template_errors)} template(s) could not be loaded. "
158
+ f"Use --verbose flag for more details.",
159
+ err=True,
160
+ fg="yellow",
161
+ )
162
+
163
+
164
+ def _iterate_plugins() -> Generator[SafPlugin | SafPluginValidationError, None, None]:
165
+ plugins = entry_points(group="ansys_saf_templates")
166
+ if not plugins:
167
+ click.secho("No template plugins found.", fg="yellow")
168
+ sys.exit(1)
169
+ for plugin in sorted(plugins, key=lambda p: p.value.split(":")[0]):
170
+ module_name = plugin.value.split(":")[0]
171
+ try:
172
+ yield SafPlugin.model_validate(
173
+ {
174
+ "path": plugin.load()(),
175
+ "module_name": module_name,
176
+ },
177
+ )
178
+ except ValidationError as e:
179
+ yield SafPluginValidationError(module_name, e)
180
+
181
+
182
+ def _find_template_in_plugin(template_name: str, plugin: SafPlugin) -> SafTemplate | None:
183
+ if template_name not in plugin.templates_config["templates"]:
184
+ return None
185
+ template = plugin.templates_config["templates"][template_name]
186
+ try:
187
+ return SafTemplate.model_validate(
188
+ {
189
+ "name": template_name,
190
+ "type": template["type"],
191
+ "description": template["description"],
192
+ "location": plugin.path / template["location"],
193
+ "dependencies": template.get("dependencies", {}),
194
+ "saf_cli_compatibility_range": template.get("saf_cli_compatibility_range"),
195
+ },
196
+ )
197
+ except Exception as e:
198
+ raise ValueError(f"Template '{template_name}' in plugin module '{plugin.module_name}' is invalid.") from e
199
+
200
+
201
+ def resolve_template(template_name: str) -> SafTemplate:
202
+ plugin_errors: list[SafPluginValidationError] = []
203
+ for plugin in _iterate_plugins():
204
+ if isinstance(plugin, SafPluginValidationError):
205
+ plugin_errors.append(plugin)
206
+ continue
207
+ if not (saf_template := _find_template_in_plugin(template_name, plugin)):
208
+ continue
209
+ return saf_template
210
+
211
+ raise ValueError(
212
+ f"Could not find a valid template for template name '{template_name}'.\n"
213
+ f" - Run `saf templates` to list the available templates.\n"
214
+ f" - Run `saf templates --verbose` to include more details on plugin and template errors.",
215
+ )
216
+
217
+
218
+ def _scan_plugin_templates(plugin: SafPlugin) -> Generator[SafTemplate | SafTemplateValidationError, None, None]:
219
+ for template_name, template in plugin.templates_config["templates"].items():
220
+ try:
221
+ yield SafTemplate.model_validate(
222
+ {
223
+ "name": template_name,
224
+ "type": template.get("type", ""),
225
+ "description": template.get("description"),
226
+ "location": plugin.path / template.get("location", ""),
227
+ "dependencies": template.get("dependencies", {}),
228
+ "saf_cli_compatibility_range": template.get("saf_cli_compatibility_range"),
229
+ },
230
+ )
231
+ except ValidationError as e:
232
+ yield SafTemplateValidationError(template_name, e)
233
+
234
+
235
+ def list_available_templates(steps_only: bool = False, solutions_only: bool = False, verbose: bool = False) -> None:
236
+ renderer = ClickRenderer(verbose, steps_only, solutions_only)
237
+ plugin_templates: dict[str, list[SafTemplate]] = {}
238
+ plugin_errors: list[SafPluginValidationError] = []
239
+ template_errors: list[SafTemplateValidationError] = []
240
+ for plugin in _iterate_plugins():
241
+ if isinstance(plugin, SafPluginValidationError):
242
+ plugin_errors.append(plugin)
243
+ continue
244
+ for saf_template in _scan_plugin_templates(plugin):
245
+ if isinstance(saf_template, SafTemplateValidationError):
246
+ template_errors.append(saf_template)
247
+ continue
248
+ if plugin.module_name not in plugin_templates:
249
+ plugin_templates[plugin.module_name] = []
250
+ plugin_templates[plugin.module_name].append(saf_template)
251
+
252
+ renderer.render_plugin_and_template_information(plugin_templates)
253
+ renderer.render_plugin_errors(plugin_errors)
254
+ renderer.render_template_errors(template_errors)
File without changes
@@ -0,0 +1,73 @@
1
+ # Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import importlib.metadata
18
+ from pathlib import Path
19
+ import sys
20
+
21
+ from cookiecutter.main import cookiecutter # pyright: ignore[reportUnknownVariableType, reportMissingTypeStubs]
22
+
23
+ from ansys.saf.cli._config.const import DEFAULT_SOLUTION_NAMESPACE, SOLUTION_TEMPLATE_PATH
24
+ from ansys.saf.cli._utilities.conversion import (
25
+ namespace_to_path,
26
+ namespace_to_pkg_name,
27
+ to_class_name,
28
+ to_docker_name,
29
+ to_module_name,
30
+ to_package_name,
31
+ )
32
+ from ansys.saf.cli._utilities.platform import appdata_directory
33
+
34
+
35
+ def create_solution(
36
+ solution_name: str,
37
+ solution_display_name: str,
38
+ ui_framework: str,
39
+ namespace: str = DEFAULT_SOLUTION_NAMESPACE,
40
+ ) -> None:
41
+ # see docs' glossary for a detailed explanation of every term used in the cookiecutter_kwargs
42
+ solution_module_name = to_module_name(solution_name)
43
+ solution_definition_class_name = to_class_name(solution_name, "Solution")
44
+ solution_package_name = to_package_name(solution_name)
45
+ version = "0.0.0"
46
+ docker_name = to_docker_name(solution_name, version)
47
+ saf_cli_version = importlib.metadata.version("ansys-saf-cli")
48
+
49
+ cookiecutter(
50
+ SOLUTION_TEMPLATE_PATH.as_posix(),
51
+ output_dir=Path.cwd().as_posix(),
52
+ no_input=True,
53
+ # overwrite all cookiecutter.json's terms
54
+ extra_context={
55
+ "__solution_name": solution_name,
56
+ "__solution_display_name": solution_display_name,
57
+ "__ui_framework": ui_framework,
58
+ "__solution_module_name": solution_module_name,
59
+ "__solution_definition_class_name": solution_definition_class_name,
60
+ "__solution_package_name": solution_package_name,
61
+ "__solution_namespace": namespace,
62
+ "__solution_namespace_path": namespace_to_path(namespace),
63
+ "__version": version,
64
+ "__docker_name": docker_name,
65
+ "__pkg_name": f"{namespace_to_pkg_name(namespace)}-{solution_package_name}",
66
+ "__pkg_namespace": f"{namespace}.{solution_module_name}",
67
+ "__pkg_path": f"{namespace_to_path(namespace)}/{solution_module_name}",
68
+ "__repository_url": "",
69
+ "__appdata_directory": appdata_directory(),
70
+ "__python_version": f"{sys.version_info.major}.{sys.version_info.minor}",
71
+ "__saf_cli_version": saf_cli_version,
72
+ },
73
+ )
@@ -0,0 +1,15 @@
1
+ # Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
File without changes
@@ -0,0 +1,18 @@
1
+ {
2
+ "__solution_name": "my_solution",
3
+ "__solution_display_name": "My Solution",
4
+ "__ui_framework": "dash",
5
+ "__solution_module_name": "my_solution",
6
+ "__solution_package_name": "my-solution",
7
+ "__solution_definition_class_name": "MySolution",
8
+ "__solution_namespace": "saf.solutions",
9
+ "__solution_namespace_path": "saf/solutions",
10
+ "__version": "0.0.0",
11
+ "__docker_name": "my-solution_0-0-0",
12
+ "__pkg_name": "saf-solutions-my-solution",
13
+ "__pkg_namespace": "saf.solutions.my_solution",
14
+ "__repository_url": "",
15
+ "__appdata_directory": "",
16
+ "__python_version": "3.10",
17
+ "__saf_cli_version": ""
18
+ }
@@ -0,0 +1,54 @@
1
+ # Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ #
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+
20
+
21
+ from pathlib import Path
22
+ import shutil
23
+ import os
24
+
25
+ SOLUTION_UI_PATH = Path.cwd() / "src" / f"{{ cookiecutter.__solution_namespace_path }}" / f"{{ cookiecutter.__solution_module_name }}" / "ui"
26
+ SOLUTION_UI_TESTS_PATH = Path.cwd() / "tests" / "unit" / "test_solution_ui.py"
27
+
28
+
29
+ def _remove_ui_files() -> None:
30
+ if "{{ cookiecutter.__ui_framework }}" != "dash": # pyright: ignore[reportUnnecessaryComparison]
31
+ shutil.rmtree(SOLUTION_UI_PATH)
32
+
33
+
34
+ def _remove_ui_tests() -> None:
35
+ if "{{ cookiecutter.__ui_framework }}" != "dash": # pyright: ignore[reportUnnecessaryComparison]
36
+ SOLUTION_UI_TESTS_PATH.unlink()
37
+
38
+
39
+ def _copy_poetry_lock():
40
+ if "{{ cookiecutter.__ui_framework }}" == "dash": # pyright: ignore[reportUnnecessaryComparison]
41
+ shutil.copy(os.path.join("lock_files", "dash", "poetry.lock"), ".")
42
+ else:
43
+ shutil.copy(os.path.join("lock_files", "no_ui", "poetry.lock"), ".")
44
+ shutil.rmtree("lock_files")
45
+
46
+
47
+ def main():
48
+ _remove_ui_files()
49
+ _copy_poetry_lock()
50
+ _remove_ui_tests()
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,26 @@
1
+ {
2
+ "image": "mcr.microsoft.com/devcontainers/python:3.10",
3
+ "features": {
4
+ },
5
+ "onCreateCommand": "pip install packaging toml && python setup_environment.py -d all -f",
6
+ "postCreateCommand": "chmod +x /workspaces/{{ cookiecutter.__solution_name }}/.venv/bin/activate && sed -i '$a source /workspaces/{{ cookiecutter.__solution_name }}/.venv/bin/activate' ~/.bashrc && sed -i '$a source /workspaces/{{ cookiecutter.__solution_name }}/.venv/bin/activate' ~/.zshrc",
7
+ "postAttachCommand": "tox -e style",
8
+ "customizations": {
9
+ "vscode": {
10
+ "extensions": [
11
+ "davidanson.vscode-markdownlint",
12
+ "donjayamanne.python-environment-manager",
13
+ "donjayamanne.python-extension-pack",
14
+ "github.vscode-github-actions",
15
+ "github.vscode-pull-request-github",
16
+ "kevinrose.vsc-python-indent",
17
+ "mhutchie.git-graph",
18
+ "ms-python.debugpy",
19
+ "ms-python.python",
20
+ "ms-python.vscode-pylance",
21
+ "shd101wyy.markdown-preview-enhanced",
22
+ "vscode-icons-team.vscode-icons"
23
+ ]
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,5 @@
1
+ DATABASE_LOCATION={{ cookiecutter.__appdata_directory }}/ansys/portal/{{ cookiecutter.__solution_name }}
2
+ PORTAL_DATABASE_TYPE=sqlite
3
+ PORTAL_PROJECT_DATABASE_LOCATION=${DATABASE_LOCATION}/projects.db
4
+ GLOW_SOLUTION_DEFINITION={{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.solution.definition
5
+ GLOW_UI_MODULE={{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.ui.app
@@ -0,0 +1,8 @@
1
+ [flake8]
2
+ exclude = venv, __init__.py, doc/_build, .venv, tests/**
3
+ select = W191, W291, W293, W391, E115, E117, E122, E124, E125, E225, E231, E301, E303, E501, F401, F403
4
+ count = True
5
+ max-complexity = 10
6
+ max-line-length = 120
7
+ statistics = True
8
+ per-file-ignores = preprocessing.py:F401
@@ -0,0 +1,9 @@
1
+ documentation:
2
+ - changed-files:
3
+ - any-glob-to-any-file: ['doc/source/**/*']
4
+ maintenance:
5
+ - changed-files:
6
+ - any-glob-to-any-file: ['.github/**/*', '.flake8', 'pyproject.toml']
7
+ dependencies:
8
+ - changed-files:
9
+ - any-glob-to-any-file: ['pyproject.toml']
@@ -0,0 +1,27 @@
1
+ - name: bug
2
+ description: Something isn't working
3
+ color: d42a34
4
+
5
+ - name: dependencies
6
+ description: Related with project dependencies
7
+ color: ffc0cb
8
+
9
+ - name: documentation
10
+ description: Improvements or additions to documentation
11
+ color: 0677ba
12
+
13
+ - name: enhancement
14
+ description: New features or code improvements
15
+ color: FFD827
16
+
17
+ - name: good first issue
18
+ description: Easy to solve for newcomers
19
+ color: 62ca50
20
+
21
+ - name: maintenance
22
+ description: Package and maintenance related
23
+ color: f78c37
24
+
25
+ - name: release
26
+ description: Anything related to an incoming release
27
+ color: ffffff