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.
- ansys/saf/cli/__init__.py +15 -0
- ansys/saf/cli/_cli/__init__.py +15 -0
- ansys/saf/cli/_cli/main.py +816 -0
- ansys/saf/cli/_cli/py.typed +0 -0
- ansys/saf/cli/_config/__init__.py +15 -0
- ansys/saf/cli/_config/const.py +45 -0
- ansys/saf/cli/_config/py.typed +0 -0
- ansys/saf/cli/_database/__init__.py +15 -0
- ansys/saf/cli/_database/manager.py +94 -0
- ansys/saf/cli/_database/models.py +53 -0
- ansys/saf/cli/_database/py.typed +0 -0
- ansys/saf/cli/_solutions/__init__.py +15 -0
- ansys/saf/cli/_solutions/add_step.py +256 -0
- ansys/saf/cli/_solutions/dependencies.py +162 -0
- ansys/saf/cli/_solutions/environment.py +1145 -0
- ansys/saf/cli/_solutions/plugins.py +254 -0
- ansys/saf/cli/_solutions/py.typed +0 -0
- ansys/saf/cli/_solutions/scaffolding.py +73 -0
- ansys/saf/cli/_solutions/templates/__init__.py +15 -0
- ansys/saf/cli/_solutions/templates/py.typed +0 -0
- ansys/saf/cli/_solutions/templates/solution/cookiecutter.json +18 -0
- ansys/saf/cli/_solutions/templates/solution/hooks/post_gen_project.py +54 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.codespell.ignore +2 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.devcontainer/devcontainer.json +26 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.env +5 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.flake8 +8 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/labeler.yml +9 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/labels.yml +27 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/workflows/build-release.yml +175 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.github/workflows/label.yml +89 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.gitignore +137 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.pre-commit-config.yaml +48 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.release-please-manifest.json +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.reuse/templates/copyright.jinja2 +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.vscode/extensions.json +8 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/.vscode/launch.json +28 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/AUTHORS +12 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CHANGELOG.md +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CODEOWNERS +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CODE_OF_CONDUCT.md +65 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CONTRIBUTING.md +2 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/CONTRIBUTORS.md +9 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/LICENSE.rst +41 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/README.md +69 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/Dockerfile +43 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/README.md +13 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/distributed-deployment-template/.env +27 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/distributed-deployment-template/compose.yaml +132 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone/.env +20 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone/compose.yaml +118 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-hps/.env +28 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-hps/compose.yaml +146 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-minerva/.env +25 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/deployments/standalone-with-minerva/compose.yaml +122 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/.vale.ini +28 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/Makefile +30 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/changelog.d/changelog_template.jinja +17 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/make.bat +46 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_static/css/custom.css +8 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_static/images/repository-banner.png +0 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/_templates/README.md +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/changelog.rst +12 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/conf.py +253 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/examples.rst +8 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/getting_started/desktop_installation.rst +165 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/getting_started/docker_installation.rst +68 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/index.rst +51 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/source/user_guide/index.rst +6 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/styles/config/vocabularies/ANSYS/accept.txt +5 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/doc/styles/config/vocabularies/ANSYS/reject.txt +0 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/examples/README.md +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/lock_files/dash/poetry.lock +7621 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/lock_files/no_ui/poetry.lock +7173 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/cli/README.md +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/oauth/App_Data/Certificates/README.md +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/minerva/oauth/oauth.config +9 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/pyproject.toml +192 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/release-please-config.json +9 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/sonar-project.properties +12 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/__init__.py +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/main.py +19 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/application.svg +36 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/description.json +3 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/portal_assets/project.svg +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/definition.py +24 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/first_step.py +34 -0
- 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
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/scripts/README.md +1 -0
- 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
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/solution/second_step.py +12 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/app.py +30 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/components/README.md +1 -0
- 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
- 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
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/src/{{ cookiecutter.__solution_namespace_path }}/{{cookiecutter.__solution_module_name}}/ui/pages/page.py +367 -0
- 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
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/common_test_files/README.md +1 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/conftest.py +37 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/unit/test_solution_api.py +42 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/unit/test_solution_ui.py +30 -0
- ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tox.ini +56 -0
- ansys/saf/cli/_utilities/__init__.py +15 -0
- ansys/saf/cli/_utilities/archiver.py +67 -0
- ansys/saf/cli/_utilities/backup.py +181 -0
- ansys/saf/cli/_utilities/conversion.py +64 -0
- ansys/saf/cli/_utilities/platform.py +26 -0
- ansys/saf/cli/_utilities/py.typed +0 -0
- ansys/saf/cli/_utilities/solution_modules.py +173 -0
- ansys/saf/cli/integrations/solutions_manager.py +64 -0
- ansys/saf/cli/py.typed +0 -0
- ansys_saf_cli-4.1.dev0.dist-info/METADATA +201 -0
- ansys_saf_cli-4.1.dev0.dist-info/RECORD +143 -0
- ansys_saf_cli-4.1.dev0.dist-info/WHEEL +4 -0
- ansys_saf_cli-4.1.dev0.dist-info/entry_points.txt +3 -0
- ansys_saf_cli-4.1.dev0.dist-info/licenses/AUTHORS +12 -0
- ansys_saf_cli-4.1.dev0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,181 @@
|
|
|
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 pathlib import Path
|
|
18
|
+
import shutil
|
|
19
|
+
import tempfile
|
|
20
|
+
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
from ansys.saf.cli._utilities.conversion import namespace_to_path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BackupManager:
|
|
27
|
+
"""
|
|
28
|
+
Handles snapshot, backup, restore and cleanup operations for a solution add-step workflow.
|
|
29
|
+
Implements context manager protocol for automatic rollback on exceptions.
|
|
30
|
+
|
|
31
|
+
When used as a context manager, ``__enter__`` snapshots the solution module directory
|
|
32
|
+
and backs up all provided files upfront (non-existent files are silently skipped).
|
|
33
|
+
On exception, ``__exit__`` removes newly created files and restores all backed-up files;
|
|
34
|
+
the backup directory is always removed on exit.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
solution_root_dir: The root directory of the solution.
|
|
38
|
+
solution_package_name: The name of the solution package module.
|
|
39
|
+
files_to_backup: List of (file_path, base_dir) tuples to back up.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
solution_root_dir: Path,
|
|
45
|
+
solution_package_name: str,
|
|
46
|
+
namespace_root: str,
|
|
47
|
+
files_to_backup: list[tuple[Path, Path]] | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize BackupManager.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
solution_root_dir: Root directory of the solution.
|
|
53
|
+
solution_package_name: Name of the solution package (used to compute base_solution_dir).
|
|
54
|
+
namespace_root: Root namespace of the solution.
|
|
55
|
+
files_to_backup: List of (file_path, base_dir) tuples to back up. If None, uses default files
|
|
56
|
+
(page.py, definition.py, pyproject.toml, poetry.lock).
|
|
57
|
+
"""
|
|
58
|
+
self._solution_root_dir = solution_root_dir
|
|
59
|
+
# Compute base_solution_dir from solution_root_dir and solution_package_name
|
|
60
|
+
self._base_solution_dir = solution_root_dir / "src" / namespace_to_path(namespace_root) / solution_package_name
|
|
61
|
+
|
|
62
|
+
# If files_to_backup is not provided, use default files
|
|
63
|
+
if files_to_backup is None:
|
|
64
|
+
files_to_backup = [
|
|
65
|
+
# even if some of these files are not modified explicitly by the saf-cli,
|
|
66
|
+
# they can be modified by the post-gen hooks in the step templates themselves
|
|
67
|
+
(self._base_solution_dir / "ui" / "pages" / "page.py", self._base_solution_dir),
|
|
68
|
+
(self._base_solution_dir / "solution" / "definition.py", self._base_solution_dir),
|
|
69
|
+
(solution_root_dir / "pyproject.toml", solution_root_dir),
|
|
70
|
+
(solution_root_dir / "poetry.lock", solution_root_dir),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
self._files_to_backup = files_to_backup
|
|
74
|
+
self._backup_dir = Path(tempfile.mkdtemp())
|
|
75
|
+
self._preexisting_paths: set[str] = set()
|
|
76
|
+
self._backup_mapping: dict[Path, Path] = {}
|
|
77
|
+
self._missing_files_to_remove_on_restore: set[Path] = set()
|
|
78
|
+
self._snapshot_taken = False
|
|
79
|
+
|
|
80
|
+
def _snapshot_directory_paths(self) -> set[str]:
|
|
81
|
+
"""Return relative paths of all files and directories under the solution module directory."""
|
|
82
|
+
if not self._base_solution_dir.exists():
|
|
83
|
+
return set()
|
|
84
|
+
return {str(p.relative_to(self._base_solution_dir)) for p in self._base_solution_dir.rglob("*")}
|
|
85
|
+
|
|
86
|
+
def _rollback_newly_created_files_and_empty_dirs(self) -> None:
|
|
87
|
+
"""Remove newly created files and empty directories."""
|
|
88
|
+
if not self._snapshot_taken:
|
|
89
|
+
raise RuntimeError("Cannot rollback before taking a snapshot.")
|
|
90
|
+
|
|
91
|
+
post_paths = self._snapshot_directory_paths()
|
|
92
|
+
created_paths = post_paths - self._preexisting_paths
|
|
93
|
+
|
|
94
|
+
for rel in sorted(created_paths, reverse=True):
|
|
95
|
+
target = self._base_solution_dir / rel
|
|
96
|
+
try:
|
|
97
|
+
if target.is_file() or target.is_symlink():
|
|
98
|
+
target.unlink()
|
|
99
|
+
elif target.is_dir() and not any(target.iterdir()):
|
|
100
|
+
target.rmdir()
|
|
101
|
+
except OSError as e:
|
|
102
|
+
click.secho(
|
|
103
|
+
f"Warning: Failed cleanup: {target} ({e})",
|
|
104
|
+
fg="yellow",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for target in sorted(self._missing_files_to_remove_on_restore, reverse=True):
|
|
108
|
+
if not target.exists():
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
if target.is_file() or target.is_symlink():
|
|
113
|
+
target.unlink()
|
|
114
|
+
except OSError as e:
|
|
115
|
+
click.secho(
|
|
116
|
+
f"Warning: Failed cleanup: {target} ({e})",
|
|
117
|
+
fg="yellow",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _backup_files(self) -> None:
|
|
121
|
+
"""Backup files preserving relative structure."""
|
|
122
|
+
for src in (file for file, _ in self._files_to_backup):
|
|
123
|
+
if not src.is_file():
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# Find the base_dir for this file from _files_to_backup
|
|
127
|
+
base_dir = next((bd for f, bd in self._files_to_backup if f == src), self._base_solution_dir)
|
|
128
|
+
relative_path = src.relative_to(base_dir) if src.is_relative_to(base_dir) else Path(src.name)
|
|
129
|
+
backup_file = self._backup_dir / relative_path
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
backup_file.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
shutil.copy2(src, backup_file)
|
|
134
|
+
self._backup_mapping[src] = backup_file
|
|
135
|
+
except OSError as e:
|
|
136
|
+
click.secho(
|
|
137
|
+
f"Warning: Failed to backup '{src}' -> '{backup_file}' ({e})",
|
|
138
|
+
fg="yellow",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _restore_files(self) -> None:
|
|
142
|
+
"""Restore files from backup."""
|
|
143
|
+
for original, backup in self._backup_mapping.items():
|
|
144
|
+
try:
|
|
145
|
+
original.parent.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
shutil.copy2(backup, original)
|
|
147
|
+
except OSError as e:
|
|
148
|
+
click.secho(
|
|
149
|
+
f"Restore failed: {original} ({e})",
|
|
150
|
+
fg="yellow",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def __enter__(self) -> "BackupManager":
|
|
154
|
+
"""Enter context manager.
|
|
155
|
+
|
|
156
|
+
Snapshots ``base_solution_dir`` and backs up every file supplied via ``files_to_backup``.
|
|
157
|
+
Files that do not yet exist (e.g. an optional ``poetry.lock``) are silently skipped.
|
|
158
|
+
"""
|
|
159
|
+
# Take snapshot of the solution module directory
|
|
160
|
+
self._preexisting_paths = self._snapshot_directory_paths()
|
|
161
|
+
self._missing_files_to_remove_on_restore = {
|
|
162
|
+
file_path for file_path, _ in self._files_to_backup if not file_path.exists()
|
|
163
|
+
}
|
|
164
|
+
self._snapshot_taken = True
|
|
165
|
+
|
|
166
|
+
# Back up specified files
|
|
167
|
+
self._backup_files()
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool: # type: ignore
|
|
171
|
+
"""Exit context manager: restore state if exception occurred, always cleanup."""
|
|
172
|
+
try:
|
|
173
|
+
if exc_type is not None:
|
|
174
|
+
# Remove newly created files and empty directories
|
|
175
|
+
self._rollback_newly_created_files_and_empty_dirs()
|
|
176
|
+
# Restore all backed-up files
|
|
177
|
+
self._restore_files()
|
|
178
|
+
finally:
|
|
179
|
+
# Always cleanup backup directory
|
|
180
|
+
shutil.rmtree(self._backup_dir, ignore_errors=True)
|
|
181
|
+
return False
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
|
|
18
|
+
def to_display_name(name: str) -> str:
|
|
19
|
+
# input: "my-name" or "my_name" or "my name" or "My name"
|
|
20
|
+
# output: "My Name"
|
|
21
|
+
return name.replace("-", " ").replace("_", " ").title()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def to_module_name(name: str, module_type: str = "") -> str:
|
|
25
|
+
# input: "my-name" or "my_name" or "my name" or "My name"
|
|
26
|
+
# output: "my_name{_module-type}" (module_type: _step or _page)
|
|
27
|
+
module_name = name.lower().replace("-", "_").replace(" ", "_")
|
|
28
|
+
if module_type and not module_name.endswith(module_type):
|
|
29
|
+
module_name += f"_{module_type}"
|
|
30
|
+
return module_name
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def to_class_name(name: str, class_type: str) -> str:
|
|
34
|
+
# input: "my-name" or "my_name" or "my name" or "My name"
|
|
35
|
+
# output: "MyName{ClassType}" (class_type: Solution or Step)
|
|
36
|
+
class_name = name.title().replace("-", "").replace("_", "").replace(" ", "")
|
|
37
|
+
if not class_name.endswith(class_type):
|
|
38
|
+
class_name += class_type
|
|
39
|
+
return class_name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def to_package_name(name: str) -> str:
|
|
43
|
+
# input: "my-name" or "my_name" or "my name" or "My name"
|
|
44
|
+
# output: "my-name"
|
|
45
|
+
return name.lower().replace("_", "-").replace(" ", "-")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def namespace_to_pkg_name(namespace: str) -> str:
|
|
49
|
+
# input: "my_org.my_project"
|
|
50
|
+
# output: "my-org-my-project"
|
|
51
|
+
namespace_parts = [to_package_name(part) for part in namespace.split(".")]
|
|
52
|
+
return "-".join(namespace_parts)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def to_docker_name(name: str, version: str) -> str:
|
|
56
|
+
# input: "my-name" or "my_name" or "my name" or "My name" and version = "0.1.dev0"
|
|
57
|
+
# output: "my-name-0-1-dev0"
|
|
58
|
+
return f"{to_package_name(name)}_{version.replace('.', '-')}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def namespace_to_path(namespace: str) -> str:
|
|
62
|
+
# input: "my_org.my_project"
|
|
63
|
+
# output: "my_org/my_project"
|
|
64
|
+
return namespace.replace(".", "/")
|
|
@@ -0,0 +1,26 @@
|
|
|
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 os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import platform
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def appdata_directory() -> str:
|
|
23
|
+
if platform.system() == "Windows":
|
|
24
|
+
return Path(os.environ["APPDATA"]).as_posix()
|
|
25
|
+
else:
|
|
26
|
+
return str(Path(os.getenv("XDG_DATA_HOME", "~/.local/share")).expanduser())
|
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
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 ast
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import platform
|
|
21
|
+
|
|
22
|
+
from ansys.saf.cli._database.manager import SolutionDatabaseManager
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_solution_root_dir(db_manager: SolutionDatabaseManager, solution: str) -> Path:
|
|
26
|
+
"""Find the source directory of the solution given a str that can be:
|
|
27
|
+
- the name of a valid registered solution
|
|
28
|
+
- the path to a solution directory
|
|
29
|
+
- empty string, thus pointing to the current working directory.
|
|
30
|
+
"""
|
|
31
|
+
if not solution:
|
|
32
|
+
solution_root_dir = Path.cwd()
|
|
33
|
+
registered_solutions = db_manager.get_solutions_by_name(solution)
|
|
34
|
+
if not registered_solutions:
|
|
35
|
+
solution_root_dir = Path(solution).expanduser().resolve()
|
|
36
|
+
if not solution_root_dir.is_dir():
|
|
37
|
+
raise NotADirectoryError(f"Solution not found at {solution_root_dir}")
|
|
38
|
+
elif len(registered_solutions) > 1:
|
|
39
|
+
error_message = f"Multiple solutions found with the name {solution}."
|
|
40
|
+
for registered_solution in registered_solutions:
|
|
41
|
+
error_message += f"\n- Found {registered_solution.name} at {registered_solution.root_dir}"
|
|
42
|
+
error_message += (
|
|
43
|
+
"\nHint: You can specify the correct one by using the full path to the solution as follows:\n"
|
|
44
|
+
"saf <command> /full/path/to/your-solution/"
|
|
45
|
+
)
|
|
46
|
+
raise ValueError(error_message)
|
|
47
|
+
else:
|
|
48
|
+
solution_root_dir = registered_solutions[0].root_dir
|
|
49
|
+
|
|
50
|
+
return solution_root_dir
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_solution_main_file(solution_root_dir: Path) -> Path:
|
|
54
|
+
"""Find the main file of the solution given the solution directory.
|
|
55
|
+
|
|
56
|
+
Searches for a main.py file under src/ at any namespace depth.
|
|
57
|
+
If not found, falls back to searching for main.pyc.
|
|
58
|
+
Expected structure: src/<namespace_path>/<solution_name>/main.py
|
|
59
|
+
"""
|
|
60
|
+
solution_src_dir = solution_root_dir / "src"
|
|
61
|
+
if not solution_src_dir.is_dir():
|
|
62
|
+
raise NotADirectoryError(f"Solution source directory not found at {solution_src_dir}")
|
|
63
|
+
|
|
64
|
+
main_files = sorted(solution_src_dir.glob("**/main.py"))
|
|
65
|
+
if not main_files:
|
|
66
|
+
main_files = sorted(solution_src_dir.glob("**/main.pyc"))
|
|
67
|
+
|
|
68
|
+
if not main_files:
|
|
69
|
+
raise FileNotFoundError(
|
|
70
|
+
f"Solution main file not found under {solution_src_dir}. "
|
|
71
|
+
f"Expected structure: src/<namespace_path>/<solution_name>/main.py",
|
|
72
|
+
)
|
|
73
|
+
if len(main_files) > 1:
|
|
74
|
+
error_message = "Multiple main files found:\n"
|
|
75
|
+
for main_file in main_files:
|
|
76
|
+
error_message += f"- Found {main_file}\n"
|
|
77
|
+
error_message += "Only one solution is expected in the src directory."
|
|
78
|
+
raise ValueError(error_message)
|
|
79
|
+
|
|
80
|
+
return main_files[0]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_solution_module_parts(solution_main_file: Path) -> list[str]:
|
|
84
|
+
"""Return the dotted-module path parts after the src directory."""
|
|
85
|
+
resolved_path = solution_main_file.resolve()
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
src_index = resolved_path.parts.index("src")
|
|
89
|
+
except ValueError:
|
|
90
|
+
raise RuntimeError(f"Could not find 'src' in path: {resolved_path}") from None
|
|
91
|
+
|
|
92
|
+
module_parts = list(resolved_path.parts[src_index + 1 :])
|
|
93
|
+
if not module_parts:
|
|
94
|
+
raise RuntimeError(f"Could not build module path from: {resolved_path}")
|
|
95
|
+
|
|
96
|
+
module_parts[-1] = Path(module_parts[-1]).stem
|
|
97
|
+
return module_parts
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_main_module(solution_main_file: Path) -> str:
|
|
101
|
+
"""Get the main module for a Solution and, optionally, validate that it is correct. Raises an exception
|
|
102
|
+
if a Solution main module is not found.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
solution_main_file : Path
|
|
107
|
+
The path of the Solution main file (e.g., <org>/solutions/<solution_name>/main.py).
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
str
|
|
112
|
+
The main module str (e.g., my_company.solutions.my_solution.main)
|
|
113
|
+
"""
|
|
114
|
+
return ".".join(_get_solution_module_parts(solution_main_file))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def find_solution_display_name(solution_main_file: Path) -> str | None:
|
|
118
|
+
# we cannot import the solution, since the CLI env doesn't have the solution dependencies
|
|
119
|
+
# We fallback then to parsing the definition.py file, which doesn't work for compiled solutions
|
|
120
|
+
# shouldn't be an issue since src directories are not typically compiled.
|
|
121
|
+
definition_file = solution_main_file.parent / "solution" / "definition.py"
|
|
122
|
+
if not definition_file.is_file():
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
display_name: str | None = None
|
|
126
|
+
tree = ast.parse(definition_file.read_text())
|
|
127
|
+
class_definitions = [node for node in tree.body if isinstance(node, ast.ClassDef)]
|
|
128
|
+
for class_def in class_definitions:
|
|
129
|
+
if any(base.id == "Solution" for base in class_def.bases): # pyright: ignore
|
|
130
|
+
assignments = [node for node in class_def.body if isinstance(node, ast.AnnAssign)]
|
|
131
|
+
for assignment in assignments:
|
|
132
|
+
if assignment.target.id == "display_name": # pyright: ignore
|
|
133
|
+
display_name = str(assignment.value.value) # pyright: ignore
|
|
134
|
+
break
|
|
135
|
+
break
|
|
136
|
+
return display_name
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_solution_venv_bin_dir(solution_root_dir: Path) -> Path:
|
|
140
|
+
if platform.system() == "Windows":
|
|
141
|
+
solution_venv_bin_dir = solution_root_dir / ".venv" / "Scripts"
|
|
142
|
+
elif platform.system() == "Linux":
|
|
143
|
+
solution_venv_bin_dir = solution_root_dir / ".venv" / "bin"
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError("Unsupported operating system")
|
|
146
|
+
return solution_venv_bin_dir
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_exec_from_solution_venv(solution_root_dir: Path, exec_name: str) -> Path:
|
|
150
|
+
solution_venv_bin_dir = get_solution_venv_bin_dir(solution_root_dir)
|
|
151
|
+
if platform.system() == "Windows":
|
|
152
|
+
exec_path = solution_venv_bin_dir / f"{exec_name}.exe"
|
|
153
|
+
elif platform.system() == "Linux":
|
|
154
|
+
exec_path = solution_venv_bin_dir / exec_name
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError("Unsupported operating system")
|
|
157
|
+
if not exec_path.is_file():
|
|
158
|
+
raise FileNotFoundError(f"Executable not found at {exec_path}")
|
|
159
|
+
return exec_path
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_solution_environment(solution_root_dir: Path) -> dict[str, str]:
|
|
163
|
+
solution_env = os.environ.copy()
|
|
164
|
+
# Poetry will always pick the active python environment (the environment in variable VIRTUAL_ENV) when managing
|
|
165
|
+
# dependencies. Removing this environment variable lets poetry use the solution's environment.
|
|
166
|
+
solution_env.pop("VIRTUAL_ENV", None)
|
|
167
|
+
# so internal calls to tools (e.g., poetry and sphinx in installer) resolve first to the binaries in the
|
|
168
|
+
# solution's venv instead of the ones in the SAF-CLI's venv or global ones. This doesn't seem to work with others
|
|
169
|
+
# such as python/pip. See _get_executable_absolute_path.
|
|
170
|
+
solution_env["PATH"] = os.pathsep.join(
|
|
171
|
+
[str(get_solution_venv_bin_dir(solution_root_dir)), solution_env.get("PATH", "")],
|
|
172
|
+
)
|
|
173
|
+
return solution_env
|
|
@@ -0,0 +1,64 @@
|
|
|
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 json
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from ansys.saf.cli._solutions.environment import (
|
|
23
|
+
set_user_level_environment_variable,
|
|
24
|
+
validate_credentials,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group()
|
|
29
|
+
def main():
|
|
30
|
+
"""Solutions Manager Integration CLI"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@main.command("validate-credentials")
|
|
34
|
+
@click.option("--source", required=True, help="Source configuration as JSON")
|
|
35
|
+
@click.option("--username", required=True, help="The username for authentication")
|
|
36
|
+
@click.option("--password", required=True, help="The password for authentication")
|
|
37
|
+
def validate_credentials_cmd(source: str, username: str, password: str):
|
|
38
|
+
"""Validate credentials by testing PyPI access"""
|
|
39
|
+
try:
|
|
40
|
+
source_data = json.loads(source)
|
|
41
|
+
is_valid = validate_credentials(source_data, username, password)
|
|
42
|
+
click.echo("Credentials are valid!" if is_valid else "Invalid credentials.")
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
click.echo(f"Error: {e}")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@main.command("set-env-var")
|
|
50
|
+
@click.option("--key", required=True, help="The name of the environment variable")
|
|
51
|
+
@click.option("--value", required=True, help="The value to set for the environment variable")
|
|
52
|
+
def set_env_var_cmd(key: str, value: str):
|
|
53
|
+
"""Set a user-level environment variable"""
|
|
54
|
+
try:
|
|
55
|
+
set_user_level_environment_variable(key, value)
|
|
56
|
+
click.echo(f"Environment variable '{key}' set successfully!")
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
click.echo(f"Error: {e}")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
ansys/saf/cli/py.typed
ADDED
|
File without changes
|