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,367 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
"""Initialization of the frontend layout across all the steps."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
import webbrowser
|
|
12
|
+
|
|
13
|
+
from ansys.saf.glow.client import DashClient, Deployment, NotFoundException, callback
|
|
14
|
+
from ansys.solutions.dash_super_components import Tree
|
|
15
|
+
import dash
|
|
16
|
+
import dash_bootstrap_components as dbc
|
|
17
|
+
from dash_extensions.enrich import Input, Output, State, clientside_callback, dcc, html, no_update
|
|
18
|
+
import dash_mantine_components as dmc
|
|
19
|
+
|
|
20
|
+
from {{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.solution.definition import {{ cookiecutter.__solution_definition_class_name }}
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_asset(asset_name: str, relative_path: str = "", theme: str = "dark") -> str:
|
|
26
|
+
"""Return asset URL based on the current theme."""
|
|
27
|
+
path = f"{relative_path.strip('/')}/{theme}/{asset_name}"
|
|
28
|
+
return dash.get_asset_url(path)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_page_list(theme: str) -> list[dict[str, str | bool]]:
|
|
32
|
+
"""Return the page list with icons based on the current theme and registered pages."""
|
|
33
|
+
pages = []
|
|
34
|
+
for i, page in enumerate(dash.page_registry.values()):
|
|
35
|
+
if page["module"].split(".")[-1] == "not_found_404":
|
|
36
|
+
continue
|
|
37
|
+
page_id = str(i)
|
|
38
|
+
page_name = page["name"]
|
|
39
|
+
icon_name = page.get("icon_asset_name", "carbon--ibm-engineering-workflow-mgmt.svg")
|
|
40
|
+
icon_path = page.get("icon_asset_path", "icons")
|
|
41
|
+
icon_url = get_asset(icon_name, icon_path, theme)
|
|
42
|
+
pages.append({"id": page_id, "text": page_name, "icon": icon_url, "expanded": True})
|
|
43
|
+
return pages
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
theme_toggle = dmc.Switch(
|
|
47
|
+
offLabel=html.Img(src=get_asset("radix-icons--sun.svg", "icons", "light")),
|
|
48
|
+
onLabel=html.Img(src=get_asset("radix-icons--moon.svg", "icons", "light")),
|
|
49
|
+
id="color-scheme-switch",
|
|
50
|
+
persistence=True,
|
|
51
|
+
color="grey",
|
|
52
|
+
checked=False,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
header = dmc.AppShellHeader(
|
|
57
|
+
dmc.Flex(
|
|
58
|
+
[
|
|
59
|
+
html.Img(id="logo-image", src=get_asset("placeholder_logo.png", "logos", "light"), height="36px"),
|
|
60
|
+
dmc.Group(
|
|
61
|
+
[
|
|
62
|
+
dmc.Text(
|
|
63
|
+
"Project name:",
|
|
64
|
+
id="project-name",
|
|
65
|
+
size="sm",
|
|
66
|
+
style={"font-size": "16px", "color": "var(--mantine-color-text)"},
|
|
67
|
+
),
|
|
68
|
+
theme_toggle,
|
|
69
|
+
dmc.ActionIcon(
|
|
70
|
+
html.Img(src=get_asset("teenyicons--doc-solid.svg", "icons", "light")),
|
|
71
|
+
id="access-solution-doc",
|
|
72
|
+
variant="transparent",
|
|
73
|
+
style={"color": "var(--mantine-color-text)"},
|
|
74
|
+
),
|
|
75
|
+
dbc.Popover(
|
|
76
|
+
"Get access to the Solution's Documentation.",
|
|
77
|
+
target="access-solution-doc",
|
|
78
|
+
body=True,
|
|
79
|
+
trigger="hover",
|
|
80
|
+
),
|
|
81
|
+
html.Div(id="return-to-portal"),
|
|
82
|
+
],
|
|
83
|
+
gap=10,
|
|
84
|
+
),
|
|
85
|
+
],
|
|
86
|
+
justify="space-between",
|
|
87
|
+
align="center",
|
|
88
|
+
direction="row",
|
|
89
|
+
wrap="wrap",
|
|
90
|
+
h="100%",
|
|
91
|
+
px="md",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
navbar = dmc.AppShellNavbar(
|
|
97
|
+
html.Div(),
|
|
98
|
+
id="navbar-content",
|
|
99
|
+
p="md",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
main_layout = dmc.AppShellMain(
|
|
104
|
+
html.Div(
|
|
105
|
+
id="page-content",
|
|
106
|
+
style={
|
|
107
|
+
"padding-right": "0.7%",
|
|
108
|
+
"padding-left": "0.7%",
|
|
109
|
+
"backgroundColor": "var(--mantine-color-body)",
|
|
110
|
+
"color": "var(--mantine-color-text)",
|
|
111
|
+
"minHeight": "100vh",
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
layout = dmc.MantineProvider(
|
|
118
|
+
[
|
|
119
|
+
dmc.NotificationContainer(
|
|
120
|
+
id="notification-container",
|
|
121
|
+
position="bottom-left",
|
|
122
|
+
notificationMaxHeight=400,
|
|
123
|
+
),
|
|
124
|
+
dmc.AppShell(
|
|
125
|
+
[
|
|
126
|
+
header,
|
|
127
|
+
navbar,
|
|
128
|
+
main_layout,
|
|
129
|
+
],
|
|
130
|
+
header={"height": 70},
|
|
131
|
+
navbar={
|
|
132
|
+
"width": 300,
|
|
133
|
+
"breakpoint": "sm",
|
|
134
|
+
"collapsed": {"mobile": True},
|
|
135
|
+
},
|
|
136
|
+
),
|
|
137
|
+
dcc.Location(id="url", refresh="callback-nav"),
|
|
138
|
+
html.Div(
|
|
139
|
+
id="alerts-container", style={"position": "fixed", "top": 90, "right": 10, "width": 350, "zIndex": 1000}
|
|
140
|
+
),
|
|
141
|
+
dcc.Store(id="active-page-index", data=None),
|
|
142
|
+
dcc.Store(id="active-project-id", data=None),
|
|
143
|
+
],
|
|
144
|
+
id="mantine-provider",
|
|
145
|
+
defaultColorScheme="light",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _get_alert_toast(message: str, level: Literal["success", "info", "warning", "danger"]) -> dbc.Toast:
|
|
150
|
+
"""Create a toast with the alert message."""
|
|
151
|
+
content = html.Div([message], style={"font-size": "small"})
|
|
152
|
+
toast = dbc.Toast(
|
|
153
|
+
content,
|
|
154
|
+
header=level.upper(),
|
|
155
|
+
is_open=True,
|
|
156
|
+
dismissable=True,
|
|
157
|
+
icon=level,
|
|
158
|
+
color=level,
|
|
159
|
+
duration=20000,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return toast
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@callback(
|
|
166
|
+
Output("return-to-portal", "children"),
|
|
167
|
+
Input("url", "pathname"),
|
|
168
|
+
Input("color-scheme-switch", "checked"),
|
|
169
|
+
)
|
|
170
|
+
def return_to_portal(pathname: str, switch_on: bool) -> list[html.A]:
|
|
171
|
+
"""Display Solution Portal when back-to-portal button gets selected."""
|
|
172
|
+
theme = "dark" if switch_on else "light"
|
|
173
|
+
|
|
174
|
+
portal_ui_url = DashClient.get_portal_ui_url()
|
|
175
|
+
|
|
176
|
+
if portal_ui_url is None:
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
popover_text = "Back to Projects" if DashClient.get_deployment_type() == Deployment.Desktop else "Back to Portal"
|
|
180
|
+
|
|
181
|
+
return [
|
|
182
|
+
html.A(
|
|
183
|
+
[
|
|
184
|
+
dmc.ActionIcon(
|
|
185
|
+
html.Img(src=get_asset("carbon--return.svg", "icons", theme)),
|
|
186
|
+
id="back-to-projects-icon",
|
|
187
|
+
variant="transparent",
|
|
188
|
+
),
|
|
189
|
+
dbc.Popover(
|
|
190
|
+
popover_text,
|
|
191
|
+
target="back-to-projects-icon",
|
|
192
|
+
body=True,
|
|
193
|
+
trigger="hover",
|
|
194
|
+
),
|
|
195
|
+
],
|
|
196
|
+
href=portal_ui_url,
|
|
197
|
+
)
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@callback(
|
|
202
|
+
Output("project-name", "children"),
|
|
203
|
+
Input("url", "pathname"),
|
|
204
|
+
)
|
|
205
|
+
def display_project_name(project: {{ cookiecutter.__solution_definition_class_name }}) -> str:
|
|
206
|
+
"""Display current project name."""
|
|
207
|
+
return f"Project Name: {project.project_display_name}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@callback(
|
|
211
|
+
Output("alerts-container", "children"),
|
|
212
|
+
Input("access-solution-doc", "n_clicks"),
|
|
213
|
+
prevent_initial_call=True,
|
|
214
|
+
)
|
|
215
|
+
def access_solution_documentation(n_clicks: int) -> list[dbc.Toast] | None:
|
|
216
|
+
"""Open the Solution's Documentation home page in the web browser."""
|
|
217
|
+
solution_module = importlib.import_module("{{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}")
|
|
218
|
+
doc_index_path = Path(solution_module.__path__[0]) / "html-doc" / "index.html"
|
|
219
|
+
if not doc_index_path.is_file():
|
|
220
|
+
doc_index_path = Path.cwd() / "doc" / "build" / "html" / "index.html"
|
|
221
|
+
if not doc_index_path.is_file():
|
|
222
|
+
return _get_alert_toast("Solution documentation is not available.", "warning")
|
|
223
|
+
logger.info(f"Opening documentation at {doc_index_path}")
|
|
224
|
+
webbrowser.open_new(doc_index_path.as_posix())
|
|
225
|
+
return no_update
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@callback(
|
|
229
|
+
Output("active-page-index", "data"),
|
|
230
|
+
Output("active-project-id", "data"),
|
|
231
|
+
Input("url", "pathname"),
|
|
232
|
+
)
|
|
233
|
+
def resolve_active_page_and_project_information(pathname: str) -> tuple[str | None, str | None]:
|
|
234
|
+
"""Resolve the active page index and project ID based on the current URL."""
|
|
235
|
+
path_parts = pathname.strip("/").split("/")
|
|
236
|
+
for i, page in enumerate(dash.page_registry.values()):
|
|
237
|
+
if page["module"].split(".")[-1] == "not_found_404":
|
|
238
|
+
continue
|
|
239
|
+
template_parts = page["path_template"].strip("/").split("/")
|
|
240
|
+
# look for same parts, excluding <project_id> variable
|
|
241
|
+
if len(path_parts) != len(template_parts):
|
|
242
|
+
continue
|
|
243
|
+
project_id = None
|
|
244
|
+
matched = True
|
|
245
|
+
for tp, pp in zip(template_parts, path_parts, strict=True):
|
|
246
|
+
if tp == "<project_id>":
|
|
247
|
+
project_id = pp
|
|
248
|
+
elif tp != pp:
|
|
249
|
+
matched = False
|
|
250
|
+
break
|
|
251
|
+
if matched:
|
|
252
|
+
return str(i), project_id
|
|
253
|
+
return None, None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@callback(
|
|
257
|
+
Output("navbar-content", "children"),
|
|
258
|
+
Input("active-page-index", "data"),
|
|
259
|
+
Input("color-scheme-switch", "checked"),
|
|
260
|
+
prevent_initial_call=True,
|
|
261
|
+
)
|
|
262
|
+
def render_nav_tree(active_index: str | None, switch_on: bool):
|
|
263
|
+
"""Sync the active tree item based on the active page index."""
|
|
264
|
+
theme = "dark" if switch_on else "light"
|
|
265
|
+
return Tree(aio_id="navigation_tree", items=get_page_list(theme), active_item_id=active_index)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _display_404_page() -> Any:
|
|
269
|
+
for module, page in dash.page_registry.items():
|
|
270
|
+
if module.split(".")[-1] == "not_found_404":
|
|
271
|
+
return page["layout"]
|
|
272
|
+
return html.H1("404 - Page not found")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@callback(
|
|
276
|
+
Output("page-content", "children"),
|
|
277
|
+
Input("url", "pathname"),
|
|
278
|
+
Input("active-page-index", "data"),
|
|
279
|
+
prevent_initial_call=True,
|
|
280
|
+
)
|
|
281
|
+
def display_page(project: {{ cookiecutter.__solution_definition_class_name }}, active_page_index: str | None):
|
|
282
|
+
"""Return the page layout, passing the project instance.
|
|
283
|
+
|
|
284
|
+
Using dash.page_container does not allow to inject the project instance as argument to the layout function,
|
|
285
|
+
only supports passing path variables, query parameters or other Inputs/States.
|
|
286
|
+
"""
|
|
287
|
+
if active_page_index is None:
|
|
288
|
+
# triggered if pathname with correct project information but incorrect sub-path.
|
|
289
|
+
# anyway, the 404 page will be rendered by another callback
|
|
290
|
+
return no_update
|
|
291
|
+
try:
|
|
292
|
+
# verify that the project information is valid, otherwise return 404.
|
|
293
|
+
# Cannot be done in display_404_page callback because we need that one to succeed if project injection fails.
|
|
294
|
+
_ = project.project_display_name
|
|
295
|
+
except NotFoundException:
|
|
296
|
+
return _display_404_page()
|
|
297
|
+
|
|
298
|
+
pages = list(dash.page_registry.values()) # page_registry is an ordered dict
|
|
299
|
+
index = int(active_page_index)
|
|
300
|
+
page = pages[index]
|
|
301
|
+
# support layout module attribute and layout function (with or without project argument)
|
|
302
|
+
layout_func = page["layout"]
|
|
303
|
+
if callable(layout_func):
|
|
304
|
+
params = inspect.signature(layout_func).parameters
|
|
305
|
+
if "project" in params:
|
|
306
|
+
return layout_func(project=project)
|
|
307
|
+
return layout_func()
|
|
308
|
+
return layout_func
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@callback(
|
|
312
|
+
Output("page-content", "children"),
|
|
313
|
+
Input("active-page-index", "data"),
|
|
314
|
+
prevent_initial_call=True,
|
|
315
|
+
)
|
|
316
|
+
def display_404_page(active_page_index: str | None):
|
|
317
|
+
"""Return the 404 page layout when the active page index is None."""
|
|
318
|
+
if active_page_index is None:
|
|
319
|
+
return _display_404_page()
|
|
320
|
+
return no_update
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@callback(
|
|
324
|
+
Output("url", "href"),
|
|
325
|
+
Input(Tree.ids.selected_item("navigation_tree"), "data"),
|
|
326
|
+
State("active-project-id", "data"),
|
|
327
|
+
State("url", "pathname"),
|
|
328
|
+
prevent_initial_call=True,
|
|
329
|
+
)
|
|
330
|
+
def open_new_page(value, project_id: str | None, pathname: str):
|
|
331
|
+
"""Navigate to the selected page."""
|
|
332
|
+
if project_id is None or not value:
|
|
333
|
+
return no_update
|
|
334
|
+
item_index = int(Tree.ids.get_index_from_navlink_item_id(value))
|
|
335
|
+
pages = list(dash.page_registry.values())
|
|
336
|
+
if item_index >= len(pages):
|
|
337
|
+
return no_update
|
|
338
|
+
page = pages[item_index]
|
|
339
|
+
target_path = page["path_template"].replace("<project_id>", project_id)
|
|
340
|
+
if target_path == pathname:
|
|
341
|
+
return no_update
|
|
342
|
+
return target_path
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@callback(
|
|
346
|
+
Output("access-solution-doc", "children"),
|
|
347
|
+
Output("logo-image", "src"),
|
|
348
|
+
Input("color-scheme-switch", "checked"),
|
|
349
|
+
)
|
|
350
|
+
def update_nav_icons(switch_on: bool) -> tuple[html.Img, str]:
|
|
351
|
+
"""Update navigation tree icons based on the current theme."""
|
|
352
|
+
theme = "dark" if switch_on else "light"
|
|
353
|
+
return (
|
|
354
|
+
html.Img(src=get_asset("teenyicons--doc-solid.svg", "icons", theme)),
|
|
355
|
+
get_asset("placeholder_logo.png", "logos", theme),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
clientside_callback(
|
|
360
|
+
"""
|
|
361
|
+
(switchOn) => {
|
|
362
|
+
return switchOn ? 'dark' : 'light';
|
|
363
|
+
}
|
|
364
|
+
""",
|
|
365
|
+
Output("mantine-provider", "forceColorScheme"),
|
|
366
|
+
Input("color-scheme-switch", "checked"),
|
|
367
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
"""Frontend of the second step."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import dash
|
|
7
|
+
from dash_extensions.enrich import html
|
|
8
|
+
|
|
9
|
+
from {{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.solution.definition import {{cookiecutter.__solution_definition_class_name}}
|
|
10
|
+
|
|
11
|
+
dash.register_page(
|
|
12
|
+
__name__,
|
|
13
|
+
name="Second Step",
|
|
14
|
+
path_template="/projects/<project_id>/second-step",
|
|
15
|
+
icon_asset_name="carbon--ibm-engineering-workflow-mgmt.svg",
|
|
16
|
+
icon_asset_path="icons",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def layout(project: {{cookiecutter.__solution_definition_class_name}}):
|
|
21
|
+
"""Layout of the second step page."""
|
|
22
|
+
return html.Div(
|
|
23
|
+
[
|
|
24
|
+
html.H1("Second Step", className="display-3", style={"font-size": "48px", "fontWeight": "bold"}),
|
|
25
|
+
html.P(
|
|
26
|
+
"This page is empty for now.",
|
|
27
|
+
className="lead",
|
|
28
|
+
style={"font-size": "20px"},
|
|
29
|
+
),
|
|
30
|
+
html.Br(),
|
|
31
|
+
]
|
|
32
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Common test files
|
ansys/saf/cli/_solutions/templates/solution/{{cookiecutter.__solution_name}}/tests/conftest.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
# ==================================================== [Imports] ==================================================== #
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
# =================================================== [Variables] =================================================== #
|
|
11
|
+
|
|
12
|
+
# Get absolute path to tests directory
|
|
13
|
+
tests_directory = Path(__file__).parent.absolute()
|
|
14
|
+
|
|
15
|
+
# Get absolute path to project directory
|
|
16
|
+
project_directory = Path(__file__).parent.parent.absolute()
|
|
17
|
+
|
|
18
|
+
# =================================================== [Functions] =================================================== #
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _set_default_glow_env(project_directory: Path) -> None:
|
|
22
|
+
env_file = project_directory / ".env"
|
|
23
|
+
if env_file.is_file():
|
|
24
|
+
load_dotenv(env_file, override=False)
|
|
25
|
+
|
|
26
|
+
os.environ.setdefault(
|
|
27
|
+
"GLOW_SOLUTION_DEFINITION",
|
|
28
|
+
"{{cookiecutter.__solution_namespace}}.{{cookiecutter.__solution_module_name}}.solution.definition",
|
|
29
|
+
)
|
|
30
|
+
os.environ.setdefault(
|
|
31
|
+
"GLOW_UI_MODULE",
|
|
32
|
+
"{{cookiecutter.__solution_namespace}}.{{cookiecutter.__solution_module_name}}.ui.app",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def pytest_configure(config) -> None:
|
|
37
|
+
_set_default_glow_env(project_directory)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
from {{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.solution.definition import {{ cookiecutter.__solution_definition_class_name }}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_set_and_retrieve_basic_step_fields(client_project: {{ cookiecutter.__solution_definition_class_name }}):
|
|
7
|
+
first_step = client_project.steps.first_step
|
|
8
|
+
# retrieve step field
|
|
9
|
+
assert first_step.first_arg == 0
|
|
10
|
+
# set step field
|
|
11
|
+
first_step.first_arg = 1.0
|
|
12
|
+
# retrieve updated value
|
|
13
|
+
assert first_step.first_arg == 1.0
|
|
14
|
+
# set multiple fields
|
|
15
|
+
first_step.set_fields({"first_arg": 2.0, "second_arg": 3.0})
|
|
16
|
+
# retrieve updated values
|
|
17
|
+
assert first_step.first_arg == 2.0
|
|
18
|
+
assert first_step.second_arg == 3.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_calculate(client_project: {{ cookiecutter.__solution_definition_class_name }}):
|
|
22
|
+
first_step = client_project.steps.first_step
|
|
23
|
+
first_step.first_arg = 10.0
|
|
24
|
+
first_step.second_arg = 20.0
|
|
25
|
+
first_step.calculate()
|
|
26
|
+
assert first_step.result == 30.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_save_result(client_project: {{ cookiecutter.__solution_definition_class_name }}):
|
|
30
|
+
first_step = client_project.steps.first_step
|
|
31
|
+
first_step.result = 7.0
|
|
32
|
+
first_step.save_result()
|
|
33
|
+
assert client_project.storage_scope.get_text(first_step.result_file) == "7.0"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_load_result(client_project: {{ cookiecutter.__solution_definition_class_name }}):
|
|
37
|
+
result_file = client_project.storage_scope.get_storage_root() / "result_file.txt"
|
|
38
|
+
result_file.write_text("29.5")
|
|
39
|
+
first_step = client_project.steps.first_step
|
|
40
|
+
first_step.result_file = client_project.storage_scope.store(result_file)
|
|
41
|
+
result = first_step.load_result()
|
|
42
|
+
assert result == 29.5
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
from dash.testing import ignore_register_page
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from {{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.solution.definition import {{ cookiecutter.__solution_definition_class_name }}
|
|
7
|
+
|
|
8
|
+
with ignore_register_page():
|
|
9
|
+
from {{ cookiecutter.__solution_namespace }}.{{ cookiecutter.__solution_module_name }}.ui.pages.first_page import calculate, load_result, save_result
|
|
10
|
+
|
|
11
|
+
pytestmark = [pytest.mark.usefixtures("init_dashclient")]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_calculate_callback(project_name: str):
|
|
15
|
+
assert calculate(1, 1, 2, project_name) == (3.0, True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_save_result_callback(client_project: {{ cookiecutter.__solution_definition_class_name }}, project_name: str):
|
|
19
|
+
first_step = client_project.steps.first_step
|
|
20
|
+
first_step.result = 7.0
|
|
21
|
+
assert save_result(1, project_name) == True
|
|
22
|
+
assert client_project.storage_scope.get_text(first_step.result_file) == "7.0"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_load_result_callback(client_project: {{ cookiecutter.__solution_definition_class_name }}, project_name: str):
|
|
26
|
+
result_file = client_project.storage_scope.get_storage_root() / "result_file.txt"
|
|
27
|
+
result_file.write_text("29.5")
|
|
28
|
+
first_step = client_project.steps.first_step
|
|
29
|
+
first_step.result_file = client_project.storage_scope.store(result_file)
|
|
30
|
+
assert load_result(1, project_name) == (29.5, True)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[tox]
|
|
2
|
+
description = Default tox environments list
|
|
3
|
+
envlist =
|
|
4
|
+
style,{py39,py310}{,-coverage},doc,build
|
|
5
|
+
skip_missing_interpreters = true
|
|
6
|
+
isolated_build = true
|
|
7
|
+
|
|
8
|
+
[gh-actions]
|
|
9
|
+
description = The tox environment to be executed in gh-actions for a given python version
|
|
10
|
+
python =
|
|
11
|
+
3.9: style,py39-coverage,doc,build
|
|
12
|
+
3.10: style,py310-coverage,doc,build
|
|
13
|
+
|
|
14
|
+
[testenv]
|
|
15
|
+
description = Perform functional testing
|
|
16
|
+
basepython =
|
|
17
|
+
py39: python3.9
|
|
18
|
+
py310: python3.10
|
|
19
|
+
py: python3
|
|
20
|
+
{style,reformat,doc,build}: python3
|
|
21
|
+
skip_install = true
|
|
22
|
+
passenv = *
|
|
23
|
+
allowlist_externals = poetry
|
|
24
|
+
commands =
|
|
25
|
+
poetry config virtualenvs.create false
|
|
26
|
+
poetry install --only main,tests -vv
|
|
27
|
+
poetry run pytest -p no:faulthandler --cov={{ cookiecutter.__solution_namespace }} --cov-report=term --cov-report=xml --cov-report=html -vv
|
|
28
|
+
poetry run coverage lcov
|
|
29
|
+
|
|
30
|
+
[testenv:style]
|
|
31
|
+
description = Checks project code style
|
|
32
|
+
skip_install = true
|
|
33
|
+
deps =
|
|
34
|
+
pre-commit
|
|
35
|
+
setenv =
|
|
36
|
+
SKIP = poetry-check
|
|
37
|
+
commands =
|
|
38
|
+
pre-commit install
|
|
39
|
+
pre-commit run --all-files --show-diff-on-failure
|
|
40
|
+
|
|
41
|
+
[testenv:doc]
|
|
42
|
+
description = Check if documentation generates properly
|
|
43
|
+
skip_install = true
|
|
44
|
+
commands =
|
|
45
|
+
poetry config virtualenvs.create false
|
|
46
|
+
poetry install --only doc -vv
|
|
47
|
+
poetry run sphinx-build doc/source doc/build/html --color -vW -bhtml
|
|
48
|
+
|
|
49
|
+
[testenv:build]
|
|
50
|
+
description = Check source code build
|
|
51
|
+
skip_install = true
|
|
52
|
+
commands =
|
|
53
|
+
poetry config virtualenvs.create false
|
|
54
|
+
poetry install --only build -vv
|
|
55
|
+
poetry run python -m build
|
|
56
|
+
|
|
@@ -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.
|
|
@@ -0,0 +1,67 @@
|
|
|
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 zipfile
|
|
20
|
+
|
|
21
|
+
SOLUTION_EXT = ".saf"
|
|
22
|
+
DEFAULT_EXCLUDED_DIRS = [".venv", ".poetry", ".git", "glow", "portal", ".github"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def archive_solution(
|
|
26
|
+
solution_root_dir: Path,
|
|
27
|
+
archive_name: str,
|
|
28
|
+
archive_path: Path | None = None,
|
|
29
|
+
archive_extension: str | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Archive a solution directory into a zip file.
|
|
32
|
+
|
|
33
|
+
This function takes a solution directory, compresses its contents into a zip file, and saves
|
|
34
|
+
it to the specified archive path or the current working directory if no path is provided.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
solution_root_dir : Path
|
|
39
|
+
The path to the solution's root directory (which contains src/) to be archived.
|
|
40
|
+
archive_name : str
|
|
41
|
+
The name of the archive file (without extension).
|
|
42
|
+
archive_path : Path, optional
|
|
43
|
+
The path where the archive file will be saved. Default path: current working directory.
|
|
44
|
+
archive_extension : str, optional
|
|
45
|
+
The extension for the archive file, including the initial dot. Default extension: ".saf".
|
|
46
|
+
"""
|
|
47
|
+
if archive_path and not archive_path.is_dir():
|
|
48
|
+
raise NotADirectoryError(f"{archive_path} is not a valid directory.")
|
|
49
|
+
|
|
50
|
+
extension = f"{archive_extension}" if archive_extension is not None else SOLUTION_EXT
|
|
51
|
+
|
|
52
|
+
archive_path = (archive_path if archive_path else Path.cwd()) / f"{archive_name}{extension}"
|
|
53
|
+
|
|
54
|
+
with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
55
|
+
stack = [solution_root_dir]
|
|
56
|
+
while stack:
|
|
57
|
+
current_path = stack.pop()
|
|
58
|
+
with os.scandir(current_path) as entries:
|
|
59
|
+
for entry in entries:
|
|
60
|
+
entry_path = current_path / entry.name
|
|
61
|
+
# Makes sure that the created .saf file is not part of the archive itself!
|
|
62
|
+
# (This typically happens when running "saf archive .").
|
|
63
|
+
if entry.is_file() and str(entry_path.resolve()) != zip_file.filename:
|
|
64
|
+
parent_path = os.path.relpath(entry_path, solution_root_dir)
|
|
65
|
+
zip_file.write(entry_path, parent_path)
|
|
66
|
+
elif entry.is_dir() and entry.name not in DEFAULT_EXCLUDED_DIRS:
|
|
67
|
+
stack.append(entry_path)
|