From ab4f896b41301d44d74c98206c0846ed25965f9a Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 15 Apr 2025 11:16:06 +0200 Subject: [PATCH 1/9] REFACTOR: Examples architecture --- src/ansys/aedt/core/__init__.py | 2 +- src/ansys/aedt/core/examples/__init__.py | 23 +++ .../aedt/core/{ => examples}/downloads.py | 153 +++++++----------- src/pyaedt/downloads.py | 2 +- tests/system/general/test_01_downloads.py | 2 +- 5 files changed, 81 insertions(+), 101 deletions(-) create mode 100644 src/ansys/aedt/core/examples/__init__.py rename src/ansys/aedt/core/{ => examples}/downloads.py (83%) diff --git a/src/ansys/aedt/core/__init__.py b/src/ansys/aedt/core/__init__.py index 19a0b351da3..2a027914de2 100644 --- a/src/ansys/aedt/core/__init__.py +++ b/src/ansys/aedt/core/__init__.py @@ -82,7 +82,7 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non # isort: on if not (".NETFramework" in sys.version): # pragma: no cover - import ansys.aedt.core.downloads as downloads + import ansys.aedt.core.examples.downloads as downloads from ansys.aedt.core.edb import Edb # nosec from ansys.aedt.core.edb import Siwave # nosec diff --git a/src/ansys/aedt/core/examples/__init__.py b/src/ansys/aedt/core/examples/__init__.py new file mode 100644 index 00000000000..b78d8fed76c --- /dev/null +++ b/src/ansys/aedt/core/examples/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/src/ansys/aedt/core/downloads.py b/src/ansys/aedt/core/examples/downloads.py similarity index 83% rename from src/ansys/aedt/core/downloads.py rename to src/ansys/aedt/core/examples/downloads.py index cd9cee9dc90..eaf5f9dc3ba 100644 --- a/src/ansys/aedt/core/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -25,18 +25,21 @@ """Download example datasets from https://github.com/ansys/example-data""" import os +from pathlib import Path import shutil import tempfile +from typing import Optional +from typing import Union +from urllib.parse import urljoin import urllib.request import zipfile -from ansys.aedt.core.generic.general_methods import is_linux +from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.generic.general_methods import settings +from ansys.aedt.core.internal.errors import AEDTRuntimeError -tmpfold = tempfile.gettempdir() -EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/main/" -EXAMPLES_PATH = os.path.join(tmpfold, "PyAEDTExamples") +EXAMPLES_DATA_REPO = "https://github.com/ansys/example-data/raw/main" +EXAMPLES_PATH = Path(tempfile.gettempdir()) / "PyAEDTExamples" def delete_downloads(): @@ -44,117 +47,68 @@ def delete_downloads(): shutil.rmtree(EXAMPLES_PATH, ignore_errors=True) -@pyaedt_function_handler(filename="name") -def _get_file_url(directory, name=None): - if not name: - return EXAMPLE_REPO + "/".join([directory]) - else: - return EXAMPLE_REPO + "/".join([directory, name]) - - -@pyaedt_function_handler(filename="name") -def _retrieve_file(url, name, directory, destination=None, local_paths=None): +def _download_file(relative_path: str, local_path: Optional[Union[str, Path]] = None) -> Path: """Download a file from a URL.""" + url = urljoin(EXAMPLES_DATA_REPO + "/", relative_path + "/") + relative_path = Path(relative_path.strip("/")) - if local_paths is None: - local_paths = [] - - # First check if file has already been downloaded - if not destination: - destination = EXAMPLES_PATH - local_path = os.path.join(destination, directory, os.path.basename(name)) - local_path_no_zip = local_path.replace(".zip", "") - if os.path.isfile(local_path_no_zip) or os.path.isdir(local_path_no_zip): - local_paths.append(local_path_no_zip) - - # grab the correct url retriever - urlretrieve = urllib.request.urlretrieve - destination_dir = os.path.join(destination, directory) - if not os.path.isdir(destination_dir): - os.makedirs(destination_dir) - # Perform download - if is_linux: - command = f"wget {url} -O {local_path}" - os.system(command) + if not local_path: + local_path = EXAMPLES_PATH / relative_path else: - _, resp = urlretrieve(url, local_path) - local_paths.append(local_path) + local_path = Path(local_path) / relative_path + local_path.parent.mkdir(parents=True, exist_ok=True) + try: + if not local_path.exists(): + pyaedt_logger.debug(f"Downloading file from URL {url}") + urllib.request.urlretrieve(url, local_path) + else: + pyaedt_logger.debug(f"File already exists in {local_path}. Skipping download.") + except Exception as e: + raise AEDTRuntimeError(f"Failed to download file from URL {url}.") from e -def _retrieve_folder(url, directory, destination=None, local_paths=None): - """Download a folder from a url""" + return local_path.resolve() - if local_paths is None: - local_paths = [] - # First check if folder exists +def _download_folder(relative_path: str, local_path: Optional[Union[str, Path]] = None) -> Path: + """Download a folder from the example data repository.""" import json import re - if not destination: - destination = EXAMPLES_PATH - if directory.startswith("pyaedt/"): - local_path = os.path.join(destination, directory[7:]) - else: - local_path = os.path.join(destination, directory) - # Ensure that "/" is parsed as a path delimiter. - local_path = os.path.join(*local_path.split("/")) + url = urljoin(EXAMPLES_DATA_REPO + "/", relative_path + "/") + relative_path = Path(relative_path.strip("/")) - _get_dir = _get_file_url(directory) - with urllib.request.urlopen(_get_dir) as response: # nosec - data = response.read().decode("utf-8").split("\n") + if not local_path: + local_path = EXAMPLES_PATH + else: + local_path = Path(local_path) + local_path.mkdir(parents=True, exist_ok=True) - if not os.path.isdir(local_path): - try: - os.mkdir(local_path) - except FileNotFoundError: - os.makedirs(local_path) # Create directory recursively if the path doesn't exist. + with urllib.request.urlopen(url) as response: + data = response.read().decode("utf-8").splitlines() try: tree = [i for i in data if '"payload"' in i][0] - b = re.search(r'>({"payload".+)', tree) - itemsfromjson = json.loads(b.group(1)) - items = itemsfromjson["payload"]["tree"]["items"] + match = re.search(r'>({"payload".+)', tree) + json_data = json.loads(match.group(1)) + items = json_data["payload"]["tree"]["items"] for item in items: if item["contentType"] == "directory": - _retrieve_folder(url, item["path"], destination, local_paths) + pyaedt_logger.info(f"Calling download folder {item['path']} into {local_path}") + _download_folder(item["path"], local_path) else: - dir_folder = os.path.split(item["path"]) - _download_file(dir_folder[0], dir_folder[1], destination, local_paths) - except Exception: - return False - - -@pyaedt_function_handler(filename="name") -def _download_file(directory, name=None, destination=None, local_paths=None): - if local_paths is None: - local_paths = [] - if not name: - if not directory.startswith("pyaedt/"): - directory = "pyaedt/" + directory - _retrieve_folder(EXAMPLE_REPO, directory, destination, local_paths) - else: - if directory.startswith("pyaedt/"): - url = _get_file_url(directory, name) - directory = directory[7:] - else: - url = _get_file_url("pyaedt/" + directory, name) - _retrieve_file(url, name, directory, destination, local_paths) - if settings.remote_rpc_session: - remote_path = os.path.join(settings.remote_rpc_session_temp_folder, os.path.split(local_paths[-1])[-1]) - if not settings.remote_rpc_session.filemanager.pathexists(settings.remote_rpc_session_temp_folder): - settings.remote_rpc_session.filemanager.makedirs(settings.remote_rpc_session_temp_folder) - settings.remote_rpc_session.filemanager.upload(local_paths[-1], remote_path) - local_paths[-1] = remote_path - return local_paths[-1] + pyaedt_logger.info(f"Calling download file {item['path']} into {local_path}") + _download_file(item["path"], local_path) + except Exception as e: + raise AEDTRuntimeError(f"Failed to download {relative_path}.") from e + + return local_path / relative_path ############################################################################### -# front-facing functions -# TODO remove once examples repository is public -def download_aedb(destination=None): +def download_aedb(local_path: Optional[Union[str, Path]] = None): """Download an example of AEDB File and return the def path. Examples files are downloaded to a persistent cache to avoid @@ -162,7 +116,7 @@ def download_aedb(destination=None): Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -177,10 +131,13 @@ def download_aedb(destination=None): >>> path = ansys.aedt.core.downloads.download_aedb() """ - local_paths = [] - _download_file("pyaedt/edb/Galileo.aedb", "GRM32ER72A225KA35_25C_0V.sp", destination, local_paths) - _download_file("pyaedt/edb/Galileo.aedb", "edb.def", destination, local_paths) - return local_paths[-1] + from ansys.aedt.core.examples.downloads import _download_file + + local_path = _download_file("pyaedt/edb/Galileo.aedb/GRM32ER72A225KA35_25C_0V.sp", local_path) + local_path = local_path.parent.parent.parent.parent + local_path = _download_file("pyaedt/edb/Galileo.aedb/edb.def", local_path) + local_path = local_path.parent + return local_path def download_edb_merge_utility(force_download=False, destination=None): diff --git a/src/pyaedt/downloads.py b/src/pyaedt/downloads.py index 50764e07939..4f14acce06c 100644 --- a/src/pyaedt/downloads.py +++ b/src/pyaedt/downloads.py @@ -1 +1 @@ -from ansys.aedt.core.downloads import * +from ansys.aedt.core.examples.downloads import * diff --git a/tests/system/general/test_01_downloads.py b/tests/system/general/test_01_downloads.py index de03ba779af..4e7e2a63150 100644 --- a/tests/system/general/test_01_downloads.py +++ b/tests/system/general/test_01_downloads.py @@ -25,7 +25,7 @@ import os import tempfile -from ansys.aedt.core import downloads +from ansys.aedt.core.examples import downloads from ansys.aedt.core.generic.file_utils import generate_unique_name from ansys.aedt.core.generic.settings import is_linux import pytest From a5ecad3bc75302aef0d476f775a3f2ffb8cbdce4 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 16 Apr 2025 10:53:49 +0200 Subject: [PATCH 2/9] WIP: Draft of downloads refactoring --- src/ansys/aedt/core/examples/downloads.py | 436 +++++++++++----------- 1 file changed, 227 insertions(+), 209 deletions(-) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index eaf5f9dc3ba..34d51f33631 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -28,7 +28,9 @@ from pathlib import Path import shutil import tempfile +from typing import Callable from typing import Optional +from typing import Tuple from typing import Union from urllib.parse import urljoin import urllib.request @@ -70,7 +72,11 @@ def _download_file(relative_path: str, local_path: Optional[Union[str, Path]] = return local_path.resolve() -def _download_folder(relative_path: str, local_path: Optional[Union[str, Path]] = None) -> Path: +def _download_folder( + relative_path: str, + local_path: Optional[Union[str, Path]] = None, + filter_func: Optional[Callable[[str], bool]] = None, +) -> Path: """Download a folder from the example data repository.""" import json import re @@ -93,11 +99,15 @@ def _download_folder(relative_path: str, local_path: Optional[Union[str, Path]] json_data = json.loads(match.group(1)) items = json_data["payload"]["tree"]["items"] for item in items: + # Skip if filter_func is provided and returns False + if filter_func and filter_func(item["path"]): + pyaedt_logger.debug(f"Skipping {item['path']} due to filter") + continue if item["contentType"] == "directory": - pyaedt_logger.info(f"Calling download folder {item['path']} into {local_path}") - _download_folder(item["path"], local_path) + pyaedt_logger.debug(f"Calling download folder {item['path']} into {local_path}") + _download_folder(item["path"], local_path, filter_func=filter_func) else: - pyaedt_logger.info(f"Calling download file {item['path']} into {local_path}") + pyaedt_logger.debug(f"Calling download file {item['path']} into {local_path}") _download_file(item["path"], local_path) except Exception as e: raise AEDTRuntimeError(f"Failed to download {relative_path}.") from e @@ -108,11 +118,11 @@ def _download_folder(relative_path: str, local_path: Optional[Union[str, Path]] ############################################################################### -def download_aedb(local_path: Optional[Union[str, Path]] = None): - """Download an example of AEDB File and return the def path. +def download_aedb(local_path: Optional[Union[str, Path]] = None) -> str: + """Download an example of AEDB file and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- @@ -122,35 +132,34 @@ def download_aedb(local_path: Optional[Union[str, Path]] = None): Returns ------- str - Path to the example file. + Path to the example folder containing example files. Examples -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_aedb() - + >>> path = ansys.aedt.core.examples.downloads.download_aedb() + >>> path + r'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/edb/Galileo.aedb' """ from ansys.aedt.core.examples.downloads import _download_file - local_path = _download_file("pyaedt/edb/Galileo.aedb/GRM32ER72A225KA35_25C_0V.sp", local_path) - local_path = local_path.parent.parent.parent.parent - local_path = _download_file("pyaedt/edb/Galileo.aedb/edb.def", local_path) - local_path = local_path.parent - return local_path + _download_file("pyaedt/edb/Galileo.aedb/GRM32ER72A225KA35_25C_0V.sp", local_path) + edbdef_path = _download_file("pyaedt/edb/Galileo.aedb/edb.def", local_path) + return str(edbdef_path.parent) -def download_edb_merge_utility(force_download=False, destination=None): +def download_edb_merge_utility(force_download: bool = False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of WPF Project which allows to merge 2aedb files. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- force_download : bool Force to delete cache and download files again. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -162,34 +171,34 @@ def download_edb_merge_utility(force_download=False, destination=None): -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_edb_merge_utility(force_download=True) + >>> path = ansys.aedt.core.examples.downloads.download_edb_merge_utility(force_download=True) >>> path - 'C:/Users/user/AppData/local/temp/wpf_edb_merge/merge_wizard.py' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/wpf_edb_merge/merge_wizard.py' """ - if not destination: - destination = EXAMPLES_PATH + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + if force_download: - local_path = os.path.join(destination, "wpf_edb_merge") - if os.path.exists(local_path): - shutil.rmtree(local_path, ignore_errors=True) - local_paths = [] - _download_file("pyaedt/wpf_edb_merge/board.aedb", "edb.def", destination, local_paths) - _download_file("pyaedt/wpf_edb_merge/package.aedb", "edb.def", destination, local_paths) - _download_file("pyaedt/wpf_edb_merge", "merge_wizard_settings.json", destination, local_paths) + path_to_remove = local_path / "pyaedt" / "wpf_edb_merge" + if path_to_remove.exists(): + pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") + shutil.rmtree(path_to_remove, ignore_errors=True) - _download_file("pyaedt/wpf_edb_merge", "merge_wizard.py", destination, local_paths) - return local_paths[0] + local_path = _download_folder("pyaedt/wpf_edb_merge", local_path, filter_func=lambda f: f.endswith(".gitignore")) + script_path = local_path / "merge_wizard.py" + return str(script_path) -def download_netlist(destination=None): +def download_netlist(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of netlist File and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -202,24 +211,23 @@ def download_netlist(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_netlist() + >>> path = ansys.aedt.core.examples.downloads.download_netlist() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/netlist_small.cir' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/netlist/netlist_small.cir' """ - local_paths = [] - _download_file("pyaedt/netlist", "netlist_small.cir", destination, local_paths) - return local_paths[0] + cir_file_path = _download_file("pyaedt/netlist/netlist_small.cir", local_path=local_path) + return str(cir_file_path) -def download_antenna_array(destination=None): +def download_antenna_array(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Antenna Array and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -233,25 +241,23 @@ def download_antenna_array(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_antenna_array() + >>> path = ansys.aedt.core.examples.downloads.download_antenna_array() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/FiniteArray_Radome_77GHz_3D_CADDM.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/FiniteArray_Radome_77GHz_3D_CADDM.aedt' """ - - local_paths = [] - _download_file("pyaedt/array_antenna", "FiniteArray_Radome_77GHz_3D_CADDM.aedt", destination, local_paths) - return local_paths[0] + aedt_file_path = _download_file("pyaedt/array_antenna/FiniteArray_Radome_77GHz_3D_CADDM.aedt", local_path) + return str(aedt_file_path) -def download_sbr(destination=None): +def download_sbr(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of SBR+ Array and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -264,25 +270,23 @@ def download_sbr(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_antenna_array() + >>> path = ansys.aedt.core.examples.downloads.download_antenna_array() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/FiniteArray_Radome_77GHz_3D_CADDM.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sbr/Cassegrain.aedt' """ - - local_paths = [] - _download_file("pyaedt/sbr", "Cassegrain.aedt", destination, local_paths) - return local_paths[0] + aedt_file_path = _download_file("pyaedt/sbr/Cassegrain.aedt", local_path) + return str(aedt_file_path) -def download_sbr_time(destination=None): +def download_sbr_time(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of SBR+ Time domain animation and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -295,23 +299,23 @@ def download_sbr_time(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_sbr_time() + >>> path = ansys.aedt.core.examples.downloads.download_sbr_time() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/sbr/poc_scat_small.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sbr/poc_scat_small.aedt' """ + aedt_file_path = _download_file("pyaedt/sbr/poc_scat_small.aedt", local_path=local_path) + return str(aedt_file_path) - return _download_file("pyaedt/sbr", "poc_scat_small.aedt", destination) - -def download_icepak(destination=None): +def download_icepak(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Icepak Array and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -324,23 +328,23 @@ def download_icepak(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_icepak() + >>> path = ansys.aedt.core.examples.downloads.download_icepak() >>> pathavoid - 'C:/Users/user/AppData/local/temp/pyaedtexamples/Graphic_Card.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/Graphic_Card.aedt' """ - _download_file("pyaedt/icepak", "Graphics_card.aedt", destination) - return _download_file("pyaedt/icepak", "Graphics_card.aedt", destination) + aedt_file_path = _download_file("pyaedt/icepak/Graphics_card.aedt", local_path=local_path) + return str(aedt_file_path) -def download_icepak_3d_component(destination=None): # pragma: no cover +def download_icepak_3d_component(local_path: Optional[Union[str, Path]] = None) -> str: # pragma: no cover """Download an example of Icepak Array and return the def pathsw. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -355,26 +359,23 @@ def download_icepak_3d_component(destination=None): # pragma: no cover Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path1, path2 = ansys.aedt.core.downloads.download_icepak_3d_component() + >>> path1, path2 = ansys.aedt.core.examples.downloads.download_icepak_3d_component() >>> path1 - 'C:/Users/user/AppData/local/temp/pyaedtexamples/PCBAssembly.aedt', + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/icepak_3dcomp/PCBAssembly.aedt', """ - local_paths = [] - _download_file("pyaedt/icepak_3dcomp//PCBAssembly.aedb", destination=destination) - _download_file("pyaedt/icepak_3dcomp", "PCBAssembly.aedt", destination, local_paths) - _download_file("icepak_3dcomp", "QFP2.aedt", destination, local_paths) - return local_paths + folder_path = _download_folder("pyaedt/icepak_3dcomp", local_path=local_path) + return str(folder_path / "PCBAssembly.aedt") -def download_via_wizard(destination=None): +def download_via_wizard(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Hfss Via Wizard and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -387,23 +388,23 @@ def download_via_wizard(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_via_wizard() + >>> path = ansys.aedt.core.examples.downloads.download_via_wizard() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/Graphic_Card.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/via_wizard/viawizard_vacuum_FR4.aedt' """ - - return _download_file("pyaedt/via_wizard", "viawizard_vacuum_FR4.aedt", destination) + aedt_file = _download_file("pyaedt/via_wizard/viawizard_vacuum_FR4.aedt", local_path=local_path) + return str(aedt_file) -def download_touchstone(destination=None): +def download_touchstone(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of touchstone File and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -415,24 +416,23 @@ def download_touchstone(destination=None): -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_touchstone() + >>> path = ansys.aedt.core.examples.downloads.download_touchstone() >>> path - 'C:/Users/user/AppData/local/temp/pyaedtexamples/ssn_ssn.s6p' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/touchstone/SSN_ssn.s6p' """ - local_paths = [] - _download_file("pyaedt/touchstone", "SSN_ssn.s6p", destination, local_paths) - return local_paths[0] + s6p_file = _download_file("pyaedt/touchstone/SSN_ssn.s6p", local_path=local_path) + return str(s6p_file) -def download_sherlock(destination=None): +def download_sherlock(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of sherlock needed files and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -445,29 +445,25 @@ def download_sherlock(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_sherlock() + >>> path = ansys.aedt.core.examples.downloads.download_sherlock() + >>> path + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sherlock' """ - if not destination: - destination = EXAMPLES_PATH - local_paths = [] - _download_file("pyaedt/sherlock", "MaterialExport.csv", destination, local_paths) - _download_file("pyaedt/sherlock", "TutorialBoardPartsList.csv", destination, local_paths) - _download_file("pyaedt/sherlock", "SherlockTutorial.aedt", destination, local_paths) - _download_file("pyaedt/sherlock", "TutorialBoard.stp", destination, local_paths) - _download_file("pyaedt/sherlock/SherlockTutorial.aedb", "edb.def", destination, local_paths) - - return os.path.join(destination, "sherlock") + folder_path = _download_folder( + "pyaedt/sherlock", local_path=local_path, filter_func=lambda f: "SherkockTutorial" in f + ) + return str(folder_path) -def download_leaf(destination=None): +def download_leaf(local_path: Optional[Union[str, Path]] = None) -> Tuple[str, str]: """Download an example of Nissan leaf files and return the def path. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -480,30 +476,26 @@ def download_leaf(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_leaf(r"c:\temp") + >>> path = ansys.aedt.core.examples.downloads.download_leaf(r"c:\temp") >>> path - ('C:/temp/BH_Arnold_Magnetics_N30UH_80C.tab', 'C:/temp/BH_Arnold_Magnetics_N30UH_80C.tab') + ('C:/temp/pyaedt/nissan/30DH_20C_smooth.tab', 'C:/temp/pyaedt/nissan/BH_Arnold_Magnetics_N30UH_80C.tab') """ - if not destination: - destination = EXAMPLES_PATH - local_paths = [] - _download_file("pyaedt/nissan", "30DH_20C_smooth.tab", destination, local_paths) - _download_file("pyaedt/nissan", "BH_Arnold_Magnetics_N30UH_80C.tab", destination, local_paths) + smooth_tab_path = _download_file("pyaedt/nissan/30DH_20C_smooth.tab", local_path) + magnetics_tab_path = _download_file("pyaedt/nissan/BH_Arnold_Magnetics_N30UH_80C.tab", local_path) + return str(smooth_tab_path), str(magnetics_tab_path) - return local_paths[0], local_paths[1] - -def download_custom_reports(force_download=False, destination=None): +def download_custom_reports(force_download: bool = False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of CISPR25 with customer reports json template files. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- force_download : bool Force to delete cache and download files again. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -514,33 +506,37 @@ def download_custom_reports(force_download=False, destination=None): Examples -------- Download an example result file and return the path of the file. + >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_custom_reports(force_download=True) + >>> path = ansys.aedt.core.examples.downloads.download_custom_reports(force_download=True) >>> path - 'C:/Users/user/AppData/local/temp/custom_reports' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/custom_reports' """ - if not destination: - destination = EXAMPLES_PATH + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + if force_download: - local_path = os.path.join(destination, "custom_reports") - if os.path.exists(local_path): - shutil.rmtree(local_path, ignore_errors=True) - download_file("pyaedt/custom_reports", destination=destination) + path_to_remove = local_path / "pyaedt" / "custom_reports" + if path_to_remove.exists(): + pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") + shutil.rmtree(path_to_remove, ignore_errors=True) - return os.path.join(destination, "custom_reports") + folder_path = _download_folder("pyaedt/custom_reports", local_path=local_path) + return str(folder_path) -def download_3dcomponent(force_download=False, destination=None): +def download_3dcomponent(force_download=False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3d component array with json template files. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- force_download : bool Force to delete cache and download files again. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -552,31 +548,35 @@ def download_3dcomponent(force_download=False, destination=None): -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_3dcomponent(force_download=True) + >>> path = ansys.aedt.core.examples.downloads.download_3dcomponent(force_download=True) >>> path - 'C:/Users/user/AppData/local/temp/array_3d_component' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/array_3d_component' """ - if not destination: - destination = EXAMPLES_PATH + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + if force_download: - local_path = os.path.join(destination, "array_3d_component") - if os.path.exists(local_path): - shutil.rmtree(local_path, ignore_errors=True) - download_file("pyaedt/array_3d_component", destination=destination) - return os.path.join(destination, "array_3d_component") + path_to_remove = local_path / "pyaedt" / "array_3d_component" + if path_to_remove.exists(): + pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") + shutil.rmtree(path_to_remove, ignore_errors=True) + folder_path = _download_folder("pyaedt/array_3d_component", local_path=local_path) + return str(folder_path) -def download_FSS_3dcomponent(force_download=False, destination=None): + +def download_fss_3dcomponent(force_download=False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3d component array with json template files. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- force_download : bool Force to delete cache and download files again. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -588,29 +588,33 @@ def download_FSS_3dcomponent(force_download=False, destination=None): -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_FSS_3dcomponent(force_download=True) + >>> path = ansys.aedt.core.examples.downloads.download_FSS_3dcomponent(force_download=True) >>> path - 'C:/Users/user/AppData/local/temp/array_3d_component' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/fss_3d_component' """ - if not destination: # pragma: no cover - destination = EXAMPLES_PATH - if force_download: # pragma: no cover - local_path = os.path.join(destination, "fss_3d_component") - if os.path.exists(local_path): # pragma: no cover - shutil.rmtree(local_path, ignore_errors=True) - download_file("pyaedt/fss_3d_component", destination=destination) - return os.path.join(destination, "fss_3d_component") + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + + if force_download: + path_to_remove = local_path / "pyaedt" / "fss_3d_component" + if path_to_remove.exists(): + pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") + shutil.rmtree(path_to_remove, ignore_errors=True) + + fodler_path = _download_folder("pyaedt/fss_3d_component", local_path=local_path) + return str(fodler_path) -def download_multiparts(destination=None): +def download_multiparts(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3DComponents Multiparts. - Examples files are downloaded to a persistent cache to avoid - re-downloading the same file twice. + If example files have already been downloaded, the download is + skipped. Parameters ---------- - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path for downloading files. The default is the user's temp folder. Returns @@ -623,22 +627,26 @@ def download_multiparts(destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_multiparts() + >>> path = ansys.aedt.core.examples.downloads.download_multiparts() >>> path - 'C:/Users/user/AppData/local/temp/multiparts/library' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/multiparts/library' """ - if not destination: - destination = EXAMPLES_PATH - dest_folder = os.path.join(destination, "multiparts") - if os.path.exists(os.path.join(dest_folder, "library")): - shutil.rmtree(os.path.join(dest_folder, "library"), ignore_errors=True) - _download_file("pyaedt/multiparts", "library.zip", destination) - if os.path.exists(os.path.join(destination, "multiparts", "library.zip")): - unzip(os.path.join(destination, "multiparts", "library.zip"), dest_folder) - return os.path.join(dest_folder, "library") - - -def download_twin_builder_data(file_name, force_download=False, destination=None): + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + + if (local_path / "pyaedt" / "multiparts" / "library").exists(): + pyaedt_logger.debug(f"Deleting {local_path / 'multiparts' / 'library'} to force download.") + shutil.rmtree(local_path / "multiparts" / "library", ignore_errors=True) + + zip_file = _download_file("pyaedt/multiparts/library.zip", local_path=local_path) + unzip(zip_file, local_path / "pyaedt" / "multiparts") + return str(local_path / "pyaedt" / "multiparts") + + +def download_twin_builder_data( + file_name: Optional[str] = None, force_download=False, local_path: Optional[Union[str, Path]] = None +) -> str: """Download a Twin Builder example data file. Examples files are downloaded to a persistent cache to avoid @@ -646,11 +654,11 @@ def download_twin_builder_data(file_name, force_download=False, destination=None Parameters ---------- - file_name : str - Path of the file in the Twin Builder folder. + file_name : str, optional + Name of the file to download. If not specified, all files in the folder. force_download : bool, optional Force to delete file and download file again. Default value is ``False``. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path to download files to. The default is the user's temporary folder. Returns @@ -662,24 +670,33 @@ def download_twin_builder_data(file_name, force_download=False, destination=None -------- Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_twin_builder_data(file_name="Example1.zip",force_download=True) + >>> path = ansys.aedt.core.examples.downloads.download_twin_builder_data(force_download=True) >>> path - 'C:/Users/user/AppData/local/temp/twin_builder' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/twin_builder' """ - if not destination: - destination = EXAMPLES_PATH + if not local_path: + local_path = EXAMPLES_PATH + local_path = Path(local_path) + if force_download: - local_path = os.path.join(destination, os.path.join("twin_builder", file_name)) - if os.path.exists(local_path): - os.unlink(local_path) - _download_file("pyaedt/twin_builder", file_name, destination) - return os.path.join(destination, "twin_builder") + path_to_remove = local_path / "pyaedt" / "twin_builder" + if path_to_remove.exists(): + pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") + shutil.rmtree(path_to_remove, ignore_errors=True) + + filter_func = None + if file_name: + filter_func = lambda f: not f.endswith(file_name) + folder_path = _download_folder("pyaedt/twin_builder", local_path=local_path, filter_func=filter_func) + + if file_name: + return str(folder_path / file_name) + return str(folder_path) @pyaedt_function_handler(filename="name", directory="source") -def download_file(source, name=None, destination=None): - """ - Download a file or files from the online examples repository. +def download_file(source, name=None, local_path: Optional[Union[str, Path]] = None) -> str: + """Download a file or files from the online examples repository. Files are downloaded from the :ref:`example-data`_ repository @@ -695,7 +712,7 @@ def download_file(source, name=None, destination=None): name : str, optional File name to download. By default all files in ``directory`` will be downloaded. - destination : str, optional + local_path : str or :class:`pathlib.Path`, optional Path where the files will be saved locally. Default is the user temp folder. Returns @@ -708,7 +725,7 @@ def download_file(source, name=None, destination=None): Download an example result file and return the path of the file. >>> import ansys.aedt.core - >>> path = ansys.aedt.core.downloads.download_file("motorcad", "IPM_Vweb_Hairpin.mot") + >>> path = ansys.aedt.core.examples.downloads.download_file("motorcad", "IPM_Vweb_Hairpin.mot") >>> path 'C:/Users/user/AppData/local/temp/PyAEDTExamples/motorcad' """ @@ -726,3 +743,4 @@ def download_file(source, name=None, destination=None): def unzip(source_filename, dest_dir): with zipfile.ZipFile(source_filename) as zf: zf.extractall(dest_dir) + print(dest_dir) From 637154498a4e733e62e79959e1a93173af129690 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 16 Apr 2025 16:12:14 +0200 Subject: [PATCH 3/9] REFACTOR: Implementation to strip prefix --- src/ansys/aedt/core/examples/downloads.py | 164 +++++++++++++--------- 1 file changed, 99 insertions(+), 65 deletions(-) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index 34d51f33631..4c05ea18933 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -24,7 +24,6 @@ """Download example datasets from https://github.com/ansys/example-data""" -import os from pathlib import Path import shutil import tempfile @@ -38,6 +37,7 @@ from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.internal.errors import AEDTRuntimeError EXAMPLES_DATA_REPO = "https://github.com/ansys/example-data/raw/main" @@ -49,10 +49,17 @@ def delete_downloads(): shutil.rmtree(EXAMPLES_PATH, ignore_errors=True) -def _download_file(relative_path: str, local_path: Optional[Union[str, Path]] = None) -> Path: +def _download_file( + github_relative_path: str, + local_path: Optional[Union[str, Path]] = None, + strip_prefix: Optional[Union[str, Path]] = None, +) -> Path: """Download a file from a URL.""" - url = urljoin(EXAMPLES_DATA_REPO + "/", relative_path + "/") - relative_path = Path(relative_path.strip("/")) + url = urljoin(EXAMPLES_DATA_REPO + "/", github_relative_path + "/") + relative_path: Path = Path(github_relative_path.strip("/")) + + if strip_prefix: + relative_path = relative_path.relative_to(Path(strip_prefix)) if not local_path: local_path = EXAMPLES_PATH / relative_path @@ -73,22 +80,28 @@ def _download_file(relative_path: str, local_path: Optional[Union[str, Path]] = def _download_folder( - relative_path: str, + github_relative_path: str, local_path: Optional[Union[str, Path]] = None, filter_func: Optional[Callable[[str], bool]] = None, + strip_prefix: Optional[Union[str, Path]] = None, ) -> Path: """Download a folder from the example data repository.""" import json import re - url = urljoin(EXAMPLES_DATA_REPO + "/", relative_path + "/") - relative_path = Path(relative_path.strip("/")) + url = urljoin(EXAMPLES_DATA_REPO + "/", github_relative_path + "/") + relative_path: Path = Path(github_relative_path.strip("/")) + + if strip_prefix: + relative_path = relative_path.relative_to(Path(strip_prefix)) if not local_path: local_path = EXAMPLES_PATH else: local_path = Path(local_path) - local_path.mkdir(parents=True, exist_ok=True) + + base_local_path = local_path / relative_path + base_local_path.mkdir(parents=True, exist_ok=True) with urllib.request.urlopen(url) as response: data = response.read().decode("utf-8").splitlines() @@ -105,14 +118,14 @@ def _download_folder( continue if item["contentType"] == "directory": pyaedt_logger.debug(f"Calling download folder {item['path']} into {local_path}") - _download_folder(item["path"], local_path, filter_func=filter_func) + _download_folder(item["path"], local_path, filter_func=filter_func, strip_prefix=strip_prefix) else: pyaedt_logger.debug(f"Calling download file {item['path']} into {local_path}") - _download_file(item["path"], local_path) + _download_file(item["path"], local_path, strip_prefix=strip_prefix) except Exception as e: raise AEDTRuntimeError(f"Failed to download {relative_path}.") from e - return local_path / relative_path + return base_local_path ############################################################################### @@ -140,12 +153,12 @@ def download_aedb(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_aedb() >>> path - r'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/edb/Galileo.aedb' + r'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/Galileo.aedb' """ from ansys.aedt.core.examples.downloads import _download_file - _download_file("pyaedt/edb/Galileo.aedb/GRM32ER72A225KA35_25C_0V.sp", local_path) - edbdef_path = _download_file("pyaedt/edb/Galileo.aedb/edb.def", local_path) + _download_file("pyaedt/edb/Galileo.aedb/GRM32ER72A225KA35_25C_0V.sp", local_path, strip_prefix="pyaedt/edb") + edbdef_path = _download_file("pyaedt/edb/Galileo.aedb/edb.def", local_path, strip_prefix="pyaedt/edb") return str(edbdef_path.parent) @@ -173,19 +186,21 @@ def download_edb_merge_utility(force_download: bool = False, local_path: Optiona >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_edb_merge_utility(force_download=True) >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/wpf_edb_merge/merge_wizard.py' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/wpf_edb_merge/merge_wizard.py' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) if force_download: - path_to_remove = local_path / "pyaedt" / "wpf_edb_merge" + path_to_remove = local_path / "wpf_edb_merge" if path_to_remove.exists(): pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") shutil.rmtree(path_to_remove, ignore_errors=True) - local_path = _download_folder("pyaedt/wpf_edb_merge", local_path, filter_func=lambda f: f.endswith(".gitignore")) + local_path = _download_folder( + "pyaedt/wpf_edb_merge", local_path, filter_func=lambda f: f.endswith(".gitignore"), strip_prefix="pyaedt" + ) script_path = local_path / "merge_wizard.py" return str(script_path) @@ -213,9 +228,11 @@ def download_netlist(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_netlist() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/netlist/netlist_small.cir' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/netlist_small.cir' """ - cir_file_path = _download_file("pyaedt/netlist/netlist_small.cir", local_path=local_path) + cir_file_path = _download_file( + "pyaedt/netlist/netlist_small.cir", local_path=local_path, strip_prefix="pyaedt/netlist" + ) return str(cir_file_path) @@ -243,9 +260,11 @@ def download_antenna_array(local_path: Optional[Union[str, Path]] = None) -> str >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_antenna_array() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/FiniteArray_Radome_77GHz_3D_CADDM.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/FiniteArray_Radome_77GHz_3D_CADDM.aedt' """ - aedt_file_path = _download_file("pyaedt/array_antenna/FiniteArray_Radome_77GHz_3D_CADDM.aedt", local_path) + aedt_file_path = _download_file( + "pyaedt/array_antenna/FiniteArray_Radome_77GHz_3D_CADDM.aedt", local_path, strip_prefix="pyaedt/array_antenna" + ) return str(aedt_file_path) @@ -272,9 +291,9 @@ def download_sbr(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_antenna_array() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sbr/Cassegrain.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/sbr/Cassegrain.aedt' """ - aedt_file_path = _download_file("pyaedt/sbr/Cassegrain.aedt", local_path) + aedt_file_path = _download_file("pyaedt/sbr/Cassegrain.aedt", local_path, strip_prefix="pyaedt") return str(aedt_file_path) @@ -301,9 +320,9 @@ def download_sbr_time(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_sbr_time() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sbr/poc_scat_small.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/sbr/poc_scat_small.aedt' """ - aedt_file_path = _download_file("pyaedt/sbr/poc_scat_small.aedt", local_path=local_path) + aedt_file_path = _download_file("pyaedt/sbr/poc_scat_small.aedt", local_path=local_path, strip_prefix="pyaedt") return str(aedt_file_path) @@ -330,9 +349,11 @@ def download_icepak(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_icepak() >>> pathavoid - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/Graphic_Card.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/Graphic_Card.aedt' """ - aedt_file_path = _download_file("pyaedt/icepak/Graphics_card.aedt", local_path=local_path) + aedt_file_path = _download_file( + "pyaedt/icepak/Graphics_card.aedt", local_path=local_path, strip_prefix="pyaedt/icepak" + ) return str(aedt_file_path) @@ -361,9 +382,9 @@ def download_icepak_3d_component(local_path: Optional[Union[str, Path]] = None) >>> import ansys.aedt.core >>> path1, path2 = ansys.aedt.core.examples.downloads.download_icepak_3d_component() >>> path1 - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/icepak_3dcomp/PCBAssembly.aedt', + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/PCBAssembly.aedt', """ - folder_path = _download_folder("pyaedt/icepak_3dcomp", local_path=local_path) + folder_path = _download_folder("pyaedt/icepak_3dcomp", local_path=local_path, strip_prefix="pyaedt/icepak_3dcomp") return str(folder_path / "PCBAssembly.aedt") @@ -390,9 +411,11 @@ def download_via_wizard(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_via_wizard() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/via_wizard/viawizard_vacuum_FR4.aedt' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/viawizard_vacuum_FR4.aedt' """ - aedt_file = _download_file("pyaedt/via_wizard/viawizard_vacuum_FR4.aedt", local_path=local_path) + aedt_file = _download_file( + "pyaedt/via_wizard/viawizard_vacuum_FR4.aedt", local_path=local_path, strip_prefix="pyaedt/via_wizard" + ) return str(aedt_file) @@ -418,9 +441,9 @@ def download_touchstone(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_touchstone() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/touchstone/SSN_ssn.s6p' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/SSN_ssn.s6p' """ - s6p_file = _download_file("pyaedt/touchstone/SSN_ssn.s6p", local_path=local_path) + s6p_file = _download_file("pyaedt/touchstone/SSN_ssn.s6p", local_path=local_path, strip_prefix="pyaedt/touchstone") return str(s6p_file) @@ -447,10 +470,10 @@ def download_sherlock(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_sherlock() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/sherlock' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/sherlock' """ folder_path = _download_folder( - "pyaedt/sherlock", local_path=local_path, filter_func=lambda f: "SherkockTutorial" in f + "pyaedt/sherlock", local_path=local_path, filter_func=lambda f: "SherkockTutorial" in f, strip_prefix="pyaedt" ) return str(folder_path) @@ -478,10 +501,12 @@ def download_leaf(local_path: Optional[Union[str, Path]] = None) -> Tuple[str, s >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_leaf(r"c:\temp") >>> path - ('C:/temp/pyaedt/nissan/30DH_20C_smooth.tab', 'C:/temp/pyaedt/nissan/BH_Arnold_Magnetics_N30UH_80C.tab') + ('C:/temp/30DH_20C_smooth.tab', 'C:/temp/BH_Arnold_Magnetics_N30UH_80C.tab') """ - smooth_tab_path = _download_file("pyaedt/nissan/30DH_20C_smooth.tab", local_path) - magnetics_tab_path = _download_file("pyaedt/nissan/BH_Arnold_Magnetics_N30UH_80C.tab", local_path) + smooth_tab_path = _download_file("pyaedt/nissan/30DH_20C_smooth.tab", local_path, strip_prefix="pyaedt/nissan") + magnetics_tab_path = _download_file( + "pyaedt/nissan/BH_Arnold_Magnetics_N30UH_80C.tab", local_path, strip_prefix="pyaedt/nissan" + ) return str(smooth_tab_path), str(magnetics_tab_path) @@ -510,19 +535,19 @@ def download_custom_reports(force_download: bool = False, local_path: Optional[U >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_custom_reports(force_download=True) >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/custom_reports' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/custom_reports' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) if force_download: - path_to_remove = local_path / "pyaedt" / "custom_reports" + path_to_remove = local_path / "custom_reports" if path_to_remove.exists(): pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") shutil.rmtree(path_to_remove, ignore_errors=True) - folder_path = _download_folder("pyaedt/custom_reports", local_path=local_path) + folder_path = _download_folder("pyaedt/custom_reports", local_path=local_path, strip_prefix="pyaedt") return str(folder_path) @@ -550,19 +575,19 @@ def download_3dcomponent(force_download=False, local_path: Optional[Union[str, P >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_3dcomponent(force_download=True) >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/array_3d_component' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/array_3d_component' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) if force_download: - path_to_remove = local_path / "pyaedt" / "array_3d_component" + path_to_remove = local_path / "array_3d_component" if path_to_remove.exists(): pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") shutil.rmtree(path_to_remove, ignore_errors=True) - folder_path = _download_folder("pyaedt/array_3d_component", local_path=local_path) + folder_path = _download_folder("pyaedt/array_3d_component", local_path=local_path, strip_prefix="pyaedt") return str(folder_path) @@ -590,19 +615,19 @@ def download_fss_3dcomponent(force_download=False, local_path: Optional[Union[st >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_FSS_3dcomponent(force_download=True) >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/fss_3d_component' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/fss_3d_component' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) if force_download: - path_to_remove = local_path / "pyaedt" / "fss_3d_component" + path_to_remove = local_path / "fss_3d_component" if path_to_remove.exists(): pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") shutil.rmtree(path_to_remove, ignore_errors=True) - fodler_path = _download_folder("pyaedt/fss_3d_component", local_path=local_path) + fodler_path = _download_folder("pyaedt/fss_3d_component", local_path=local_path, strip_prefix="pyaedt") return str(fodler_path) @@ -629,19 +654,19 @@ def download_multiparts(local_path: Optional[Union[str, Path]] = None) -> str: >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_multiparts() >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/multiparts/library' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/multiparts/library' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) - if (local_path / "pyaedt" / "multiparts" / "library").exists(): + if (local_path / "multiparts" / "library").exists(): pyaedt_logger.debug(f"Deleting {local_path / 'multiparts' / 'library'} to force download.") shutil.rmtree(local_path / "multiparts" / "library", ignore_errors=True) - zip_file = _download_file("pyaedt/multiparts/library.zip", local_path=local_path) - unzip(zip_file, local_path / "pyaedt" / "multiparts") - return str(local_path / "pyaedt" / "multiparts") + zip_file = _download_file("pyaedt/multiparts/library.zip", local_path=local_path, strip_prefix="pyaedt") + unzip(zip_file, local_path / "multiparts") + return str(local_path / "multiparts") def download_twin_builder_data( @@ -672,14 +697,14 @@ def download_twin_builder_data( >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_twin_builder_data(force_download=True) >>> path - 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/pyaedt/twin_builder' + 'C:/Users/user/AppData/Local/Temp/PyAEDTExamples/twin_builder' """ if not local_path: local_path = EXAMPLES_PATH local_path = Path(local_path) if force_download: - path_to_remove = local_path / "pyaedt" / "twin_builder" + path_to_remove = local_path / "twin_builder" if path_to_remove.exists(): pyaedt_logger.debug(f"Deleting {path_to_remove} to force download.") shutil.rmtree(path_to_remove, ignore_errors=True) @@ -687,7 +712,9 @@ def download_twin_builder_data( filter_func = None if file_name: filter_func = lambda f: not f.endswith(file_name) - folder_path = _download_folder("pyaedt/twin_builder", local_path=local_path, filter_func=filter_func) + folder_path = _download_folder( + "pyaedt/twin_builder", local_path=local_path, filter_func=filter_func, strip_prefix="pyaedt" + ) if file_name: return str(folder_path / file_name) @@ -695,7 +722,7 @@ def download_twin_builder_data( @pyaedt_function_handler(filename="name", directory="source") -def download_file(source, name=None, local_path: Optional[Union[str, Path]] = None) -> str: +def download_file(source: str, name: Optional[str] = None, local_path: Optional[Union[str, Path]] = None) -> str: """Download a file or files from the online examples repository. Files are downloaded from the @@ -727,17 +754,24 @@ def download_file(source, name=None, local_path: Optional[Union[str, Path]] = No >>> import ansys.aedt.core >>> path = ansys.aedt.core.examples.downloads.download_file("motorcad", "IPM_Vweb_Hairpin.mot") >>> path - 'C:/Users/user/AppData/local/temp/PyAEDTExamples/motorcad' + 'C:/Users/user/AppData/local/temp/PyAEDTExamples/motorcad/IPM_Vweb_Hairpin.mot' """ - local_paths = [] - _download_file(source, name, destination, local_paths) - if name: - return list(set(local_paths))[0] + if not source.startswith("pyaedt/"): + source = "pyaedt/" + source + + if not name: + path = _download_folder(source, local_path, strip_prefix="pyaedt") else: - if not destination: - destination = EXAMPLES_PATH - destination_dir = os.path.join(destination, source) - return destination_dir + source = source + "/" + name + path = _download_file(source, local_path, strip_prefix="pyaedt") + + if settings.remote_rpc_session: + path = Path(settings.remote_rpc_session_temp_folder) / path.name + if not settings.remote_rpc_session.filemanager.pathexists(settings.remote_rpc_session_temp_folder): + settings.remote_rpc_session.filemanager.makedirs(settings.remote_rpc_session_temp_folder) + settings.remote_rpc_session.filemanager.upload(local_path, path) + + return str(path) def unzip(source_filename, dest_dir): From 4669746a5d9b732d384492b038dc69122da1a434 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 16 Apr 2025 16:28:55 +0200 Subject: [PATCH 4/9] REFACTOR: Improve vulnerability handling --- src/ansys/aedt/core/examples/downloads.py | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index 4c05ea18933..226038c2a54 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -32,6 +32,7 @@ from typing import Tuple from typing import Union from urllib.parse import urljoin +from urllib.parse import urlparse import urllib.request import zipfile @@ -49,13 +50,40 @@ def delete_downloads(): shutil.rmtree(EXAMPLES_PATH, ignore_errors=True) +def _build_safe_url(github_relative_path: str) -> str: + """Safely construct a URL using the user-provided input. + + This function ensures that the user-provided path does not contain an unsupported scheme + (like 'file://', 'ftp://', or a full URL), preventing the risk of overriding the base + or accessing unintended resources. + + Parameters + ---------- + github_relative_path : str + A relative path provided by the user, such as ``"pyaedt/sbr/Cassegrain.aedt"``. + + Returns + ------- + str + The safely constructed URL combining the hardcoded base and the user path. + """ + # Strip dangerous schemes + parsed = urlparse(github_relative_path) + if parsed.scheme: + raise ValueError(f"User path contains a scheme: {parsed.scheme}") + + url = urljoin(EXAMPLES_DATA_REPO + "/", github_relative_path + "/") + + return url + + def _download_file( github_relative_path: str, local_path: Optional[Union[str, Path]] = None, strip_prefix: Optional[Union[str, Path]] = None, ) -> Path: """Download a file from a URL.""" - url = urljoin(EXAMPLES_DATA_REPO + "/", github_relative_path + "/") + url = _build_safe_url(github_relative_path) relative_path: Path = Path(github_relative_path.strip("/")) if strip_prefix: @@ -70,7 +98,7 @@ def _download_file( try: if not local_path.exists(): pyaedt_logger.debug(f"Downloading file from URL {url}") - urllib.request.urlretrieve(url, local_path) + urllib.request.urlretrieve(url, local_path) # nosec else: pyaedt_logger.debug(f"File already exists in {local_path}. Skipping download.") except Exception as e: @@ -89,7 +117,7 @@ def _download_folder( import json import re - url = urljoin(EXAMPLES_DATA_REPO + "/", github_relative_path + "/") + url = _build_safe_url(github_relative_path) relative_path: Path = Path(github_relative_path.strip("/")) if strip_prefix: @@ -103,7 +131,7 @@ def _download_folder( base_local_path = local_path / relative_path base_local_path.mkdir(parents=True, exist_ok=True) - with urllib.request.urlopen(url) as response: + with urllib.request.urlopen(url) as response: # nosec data = response.read().decode("utf-8").splitlines() try: From 1d1fe46131cbbec15c1fb4e8e53b33915cbb3c69 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 16 Apr 2025 16:38:45 +0200 Subject: [PATCH 5/9] TESTS: Update download tests --- tests/system/general/test_01_downloads.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/system/general/test_01_downloads.py b/tests/system/general/test_01_downloads.py index 4e7e2a63150..ec84783eb67 100644 --- a/tests/system/general/test_01_downloads.py +++ b/tests/system/general/test_01_downloads.py @@ -83,12 +83,10 @@ def test_08_download_leaf(self): os.rename(os.path.split(out[0])[0], new_path) assert os.path.exists(new_path) - @pytest.mark.skipif(is_linux, reason="Failing on linux") def test_09_download_custom_report(self): out = self.examples.download_custom_reports() assert os.path.exists(out) - @pytest.mark.skipif(is_linux, reason="Failing on linux") def test_10_download_3dcomp(self): out = self.examples.download_3dcomponent() assert os.path.exists(out) @@ -101,18 +99,15 @@ def test_12_download_specific_file(self): example_folder = self.examples.download_file("motorcad", "IPM_Vweb_Hairpin.mot") assert os.path.exists(example_folder) - @pytest.mark.skipif(is_linux, reason="Failing download files") def test_13_download_specific_folder(self): example_folder = self.examples.download_file(directory="nissan") assert os.path.exists(example_folder) example_folder = self.examples.download_file(directory="wpf_edb_merge") assert os.path.exists(example_folder) - @pytest.mark.skipif(is_linux, reason="Failing download files") def test_14_download_icepak_3d_component(self): assert self.examples.download_icepak_3d_component() - @pytest.mark.skipif(is_linux, reason="Failing download files") def test_15_download_fss_file(self): - example_folder = self.examples.download_FSS_3dcomponent() + example_folder = self.examples.download_fss_3dcomponent() assert os.path.exists(example_folder) From ad67459cc2e6cc753f2ce93164b8395cd8ddff4c Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:41:03 +0000 Subject: [PATCH 6/9] chore: adding changelog file 6055.miscellaneous.md [dependabot-skip] --- doc/changelog.d/6055.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/6055.miscellaneous.md diff --git a/doc/changelog.d/6055.miscellaneous.md b/doc/changelog.d/6055.miscellaneous.md new file mode 100644 index 00000000000..69abfc9efea --- /dev/null +++ b/doc/changelog.d/6055.miscellaneous.md @@ -0,0 +1 @@ +Add examples folder and rework download logic \ No newline at end of file From 9716cd01288e225a990c5717c4ae4c6d4b53a671 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 17 Apr 2025 11:37:54 +0200 Subject: [PATCH 7/9] REFACTOR: Handle previous use of destination input --- src/ansys/aedt/core/examples/downloads.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index 226038c2a54..4772e417789 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -159,6 +159,7 @@ def _download_folder( ############################################################################### +@pyaedt_function_handler(destination="local_path") def download_aedb(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of AEDB file and return the def path. @@ -190,6 +191,7 @@ def download_aedb(local_path: Optional[Union[str, Path]] = None) -> str: return str(edbdef_path.parent) +@pyaedt_function_handler(destination="local_path") def download_edb_merge_utility(force_download: bool = False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of WPF Project which allows to merge 2aedb files. @@ -233,6 +235,7 @@ def download_edb_merge_utility(force_download: bool = False, local_path: Optiona return str(script_path) +@pyaedt_function_handler(destination="local_path") def download_netlist(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of netlist File and return the def path. @@ -264,6 +267,7 @@ def download_netlist(local_path: Optional[Union[str, Path]] = None) -> str: return str(cir_file_path) +@pyaedt_function_handler(destination="local_path") def download_antenna_array(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Antenna Array and return the def path. @@ -296,6 +300,7 @@ def download_antenna_array(local_path: Optional[Union[str, Path]] = None) -> str return str(aedt_file_path) +@pyaedt_function_handler(destination="local_path") def download_sbr(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of SBR+ Array and return the def path. @@ -325,6 +330,7 @@ def download_sbr(local_path: Optional[Union[str, Path]] = None) -> str: return str(aedt_file_path) +@pyaedt_function_handler(destination="local_path") def download_sbr_time(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of SBR+ Time domain animation and return the def path. @@ -354,6 +360,7 @@ def download_sbr_time(local_path: Optional[Union[str, Path]] = None) -> str: return str(aedt_file_path) +@pyaedt_function_handler(destination="local_path") def download_icepak(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Icepak Array and return the def path. @@ -385,6 +392,7 @@ def download_icepak(local_path: Optional[Union[str, Path]] = None) -> str: return str(aedt_file_path) +@pyaedt_function_handler(destination="local_path") def download_icepak_3d_component(local_path: Optional[Union[str, Path]] = None) -> str: # pragma: no cover """Download an example of Icepak Array and return the def pathsw. @@ -416,6 +424,7 @@ def download_icepak_3d_component(local_path: Optional[Union[str, Path]] = None) return str(folder_path / "PCBAssembly.aedt") +@pyaedt_function_handler(destination="local_path") def download_via_wizard(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of Hfss Via Wizard and return the def path. @@ -447,6 +456,7 @@ def download_via_wizard(local_path: Optional[Union[str, Path]] = None) -> str: return str(aedt_file) +@pyaedt_function_handler(destination="local_path") def download_touchstone(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of touchstone File and return the def path. @@ -475,6 +485,7 @@ def download_touchstone(local_path: Optional[Union[str, Path]] = None) -> str: return str(s6p_file) +@pyaedt_function_handler(destination="local_path") def download_sherlock(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of sherlock needed files and return the def path. @@ -506,6 +517,7 @@ def download_sherlock(local_path: Optional[Union[str, Path]] = None) -> str: return str(folder_path) +@pyaedt_function_handler(destination="local_path") def download_leaf(local_path: Optional[Union[str, Path]] = None) -> Tuple[str, str]: """Download an example of Nissan leaf files and return the def path. @@ -538,6 +550,7 @@ def download_leaf(local_path: Optional[Union[str, Path]] = None) -> Tuple[str, s return str(smooth_tab_path), str(magnetics_tab_path) +@pyaedt_function_handler(destination="local_path") def download_custom_reports(force_download: bool = False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of CISPR25 with customer reports json template files. @@ -579,6 +592,7 @@ def download_custom_reports(force_download: bool = False, local_path: Optional[U return str(folder_path) +@pyaedt_function_handler(destination="local_path") def download_3dcomponent(force_download=False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3d component array with json template files. @@ -619,6 +633,7 @@ def download_3dcomponent(force_download=False, local_path: Optional[Union[str, P return str(folder_path) +@pyaedt_function_handler(destination="local_path") def download_fss_3dcomponent(force_download=False, local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3d component array with json template files. @@ -659,6 +674,7 @@ def download_fss_3dcomponent(force_download=False, local_path: Optional[Union[st return str(fodler_path) +@pyaedt_function_handler(destination="local_path") def download_multiparts(local_path: Optional[Union[str, Path]] = None) -> str: """Download an example of 3DComponents Multiparts. @@ -697,6 +713,7 @@ def download_multiparts(local_path: Optional[Union[str, Path]] = None) -> str: return str(local_path / "multiparts") +@pyaedt_function_handler(destination="local_path") def download_twin_builder_data( file_name: Optional[str] = None, force_download=False, local_path: Optional[Union[str, Path]] = None ) -> str: From fd5bc5fc2461b769a09402090700d1c465d930ab Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 22 Apr 2025 16:44:44 +0200 Subject: [PATCH 8/9] REFACTOR: Improve vulnerability handling --- src/ansys/aedt/core/examples/downloads.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index 4772e417789..264adaae570 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -26,6 +26,7 @@ from pathlib import Path import shutil +import ssl import tempfile from typing import Callable from typing import Optional @@ -97,8 +98,12 @@ def _download_file( try: if not local_path.exists(): + ssl_context = ssl.create_default_context() pyaedt_logger.debug(f"Downloading file from URL {url}") - urllib.request.urlretrieve(url, local_path) # nosec + with urllib.request.urlopen(url, context=ssl_context) as response, open( + local_path, "wb" + ) as out_file: # nosec + shutil.copyfileobj(response, out_file) else: pyaedt_logger.debug(f"File already exists in {local_path}. Skipping download.") except Exception as e: From 79aa30ec0a4d95d4988ce07b8a34abf98d9595b2 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 22 Apr 2025 16:45:42 +0200 Subject: [PATCH 9/9] REFACTOR: Improve vulnerability handling --- src/ansys/aedt/core/examples/downloads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/examples/downloads.py b/src/ansys/aedt/core/examples/downloads.py index 264adaae570..ad663614804 100644 --- a/src/ansys/aedt/core/examples/downloads.py +++ b/src/ansys/aedt/core/examples/downloads.py @@ -136,7 +136,8 @@ def _download_folder( base_local_path = local_path / relative_path base_local_path.mkdir(parents=True, exist_ok=True) - with urllib.request.urlopen(url) as response: # nosec + ssl_context = ssl.create_default_context() + with urllib.request.urlopen(url, context=ssl_context) as response: # nosec data = response.read().decode("utf-8").splitlines() try: