diff --git a/doc/changelog.d/3511.added.md b/doc/changelog.d/3511.added.md new file mode 100644 index 0000000000..49f004477a --- /dev/null +++ b/doc/changelog.d/3511.added.md @@ -0,0 +1 @@ +refactor: externalise the 'report' features to another file \ No newline at end of file diff --git a/src/ansys/mapdl/core/__init__.py b/src/ansys/mapdl/core/__init__.py index dd8981461b..f014905845 100644 --- a/src/ansys/mapdl/core/__init__.py +++ b/src/ansys/mapdl/core/__init__.py @@ -115,8 +115,9 @@ from ansys.mapdl.core.information import Information from ansys.mapdl.core.mapdl_grpc import MapdlGrpc as Mapdl -from ansys.mapdl.core.misc import Report, _check_has_ansys +from ansys.mapdl.core.misc import _check_has_ansys from ansys.mapdl.core.pool import MapdlPool +from ansys.mapdl.core.report import Report ############################################################################### # Convenient imports diff --git a/src/ansys/mapdl/core/misc.py b/src/ansys/mapdl/core/misc.py index 62fa90aa4a..e909b0d6a5 100644 --- a/src/ansys/mapdl/core/misc.py +++ b/src/ansys/mapdl/core/misc.py @@ -38,32 +38,12 @@ import numpy as np from ansys.mapdl import core as pymapdl -from ansys.mapdl.core import _HAS_ATP, _HAS_PYANSYS_REPORT, _HAS_PYVISTA, LOG - -if _HAS_ATP: - from ansys.tools.path import get_available_ansys_installations - -if _HAS_PYANSYS_REPORT: - import ansys.tools.report as pyansys_report - +from ansys.mapdl.core import _HAS_PYVISTA, LOG # path of this module MODULE_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) -ANSYS_ENV_VARS = [ - "PYMAPDL_START_INSTANCE", - "PYMAPDL_PORT", - "PYMAPDL_IP", - "PYMAPDL_MAPDL_EXEC", - "PYMAPDL_MAPDL_VERSION", - "PYMAPDL_MAX_MESSAGE_LENGTH", - "ON_CI", - "ON_LOCAL", - "P_SCHEMA", -] - - class ROUTINES(Enum): """MAPDL routines.""" @@ -117,255 +97,6 @@ def check_valid_routine(routine): return True -class Plain_Report: - def __init__(self, core, optional=None, additional=None, **kwargs): - """ - Base class for a plain report. - - - Based on `scooby `_ package. - - Parameters - ---------- - additional : iter[str] - List of packages or package names to add to output information. - core : iter[str] - The core packages to list first. - optional : iter[str] - A list of packages to list if they are available. If not available, - no warnings or error will be thrown. - """ - - self.additional = additional - self.core = core - self.optional = optional - self.kwargs = kwargs - - if os.name == "posix": - self.core.extend(["pexpect"]) - - # Information about the GPU - bare except in case there is a rendering - # bug that the user is trying to report. - if self.kwargs.get("gpu", False) and _HAS_PYVISTA: - from pyvista import PyVistaDeprecationWarning - - try: - from pyvista.utilities.errors import ( - GPUInfo, # deprecated in pyvista 0.40.0 - ) - except (PyVistaDeprecationWarning, ImportError): - from pyvista.report import GPUInfo - - try: - self.kwargs["extra_meta"] = [(t[1], t[0]) for t in GPUInfo().get_info()] - except RuntimeError as e: # pragma: no cover - self.kwargs["extra_meta"] = ("GPU Details", f"Error: {str(e)}") - else: - self.kwargs["extra_meta"] = ("GPU Details", "None") - - def get_version(self, package): - try: - import importlib.metadata as importlib_metadata - except ModuleNotFoundError: # pragma: no cover - import importlib_metadata - - try: - return importlib_metadata.version(package.replace(".", "-")) - except importlib_metadata.PackageNotFoundError: - return "Package not found" - - def __repr__(self): - header = [ - "-" * 79, - "\n", - "PyMAPDL Software and Environment Report", - "\n", - "Packages Requirements", - "*********************", - ] - - core = ["\nCore packages", "-------------"] - core.extend( - [ - f"{each.ljust(20)}: {self.get_version(each)}" - for each in self.core - if self.get_version(each) - ] - ) - - if self.optional: - optional = ["\nOptional packages", "-----------------"] - optional.extend( - [ - f"{each.ljust(20)}: {self.get_version(each)}" - for each in self.optional - if self.get_version(each) - ] - ) - else: - optional = [""] - - if self.additional: - additional = ["\nAdditional packages", "-----------------"] - additional.extend( - [ - f"{each.ljust(20)}: {self.get_version(each)}" - for each in self.additional - if self.get_version(each) - ] - ) - else: - additional = [""] - - return "\n".join(header + core + optional + additional) + self.mapdl_info() - - def mapdl_info(self): - """Return information regarding the ansys environment and installation.""" - # this is here to avoid circular imports - - # List installed Ansys - lines = ["", "Ansys Environment Report", "-" * 79] - lines = ["\n", "Ansys Installation", "******************"] - if _HAS_ATP: - mapdl_install = get_available_ansys_installations() - - if not mapdl_install: - lines.append("Unable to locate any Ansys installations") - else: - lines.append("Version Location") - lines.append("------------------") - for key in sorted(mapdl_install.keys()): - lines.append(f"{abs(key)} {mapdl_install[key]}") - else: - mapdl_install = None - lines.append( - "Unable to locate any Ansys installations because 'ansys-tools-path is not installed." - ) - - install_info = "\n".join(lines) - - env_info_lines = [ - "\n\n\nAnsys Environment Variables", - "***************************", - ] - n_var = 0 - for key, value in os.environ.items(): - if "AWP" in key or "CADOE" in key or "ANSYS" in key: - env_info_lines.append(f"{key:<30} {value}") - n_var += 1 - if not n_var: - env_info_lines.append("None") - env_info = "\n".join(env_info_lines) - - return install_info + env_info - - -# Determine which type of report will be used (depending on the -# available packages) -if _HAS_PYANSYS_REPORT: - base_report_class = pyansys_report.Report -else: # pragma: no cover - base_report_class = Plain_Report - - -class Report(base_report_class): - """A class for custom scooby.Report.""" - - def __init__( - self, - additional=None, - ncol=3, - text_width=80, - sort=False, - gpu=True, - ansys_vars=ANSYS_ENV_VARS, - ansys_libs=None, - ): - """Generate a :class:`scooby.Report` instance. - - Parameters - ---------- - additional : list(ModuleType), list(str) - List of packages or package names to add to output information. - - ncol : int, optional - Number of package-columns in html table; only has effect if - ``mode='HTML'`` or ``mode='html'``. Defaults to 3. - - text_width : int, optional - The text width for non-HTML display modes - - sort : bool, optional - Alphabetically sort the packages - - gpu : bool - Gather information about the GPU. Defaults to ``True`` but if - experiencing rendering issues, pass ``False`` to safely generate - a report. - - ansys_vars : list of str, optional - List containing the Ansys environment variables to be reported. - (e.g. ["MYVAR_1", "MYVAR_2" ...]). Defaults to ``None``. Only used for - the `pyansys-tools-report` package. - - ansys_libs : dict {str : str}, optional - Dictionary containing the Ansys libraries and versions to be reported. - (e.g. {"MyLib" : "v1.2", ...}). Defaults to ``None``. Only used for - the `pyansys-tools-report` package. - - """ - # Mandatory packages - core = [ - "ansys.mapdl.core", - "numpy", - "platformdirs", - "scipy", - "grpc", # grpcio - "ansys.api.mapdl.v0", # ansys-api-mapdl-v0 - "ansys.mapdl.reader", # ansys-mapdl-reader - "google.protobuf", # protobuf library - "ansys-math-core", - ] - - # Optional packages - optional = [ - "matplotlib", - "pyvista", - "pyiges", - "tqdm", - "ansys-tools-visualization_interface", - "pandas", - ] - - if _HAS_PYANSYS_REPORT: - # Combine all packages into one - all_mapdl_packages = core + optional - if additional is not None: - all_mapdl_packages += additional - - # Call the pyansys_report.Report constructor - super().__init__( - additional=all_mapdl_packages, - ncol=ncol, - text_width=text_width, - sort=sort, - gpu=gpu, - ansys_vars=ansys_vars, - ansys_libs=ansys_libs, - ) - else: - # Call the PlainReport constructor - super().__init__( - additional=additional, - core=core, - optional=optional, - ncol=ncol, - text_width=text_width, - sort=sort, - gpu=gpu, - ) - - def is_float(input_string): """Returns true when a string can be converted to a float""" try: diff --git a/src/ansys/mapdl/core/report.py b/src/ansys/mapdl/core/report.py new file mode 100644 index 0000000000..451915ce80 --- /dev/null +++ b/src/ansys/mapdl/core/report.py @@ -0,0 +1,295 @@ +# Copyright (C) 2016 - 2024 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. + +"""Module for report features""" +import os + +from ansys.mapdl.core import _HAS_ATP, _HAS_PYANSYS_REPORT, _HAS_PYVISTA + +if _HAS_PYANSYS_REPORT: + import ansys.tools.report as pyansys_report + +if _HAS_ATP: + from ansys.tools.path import get_available_ansys_installations + +ANSYS_ENV_VARS = [ + "PYMAPDL_START_INSTANCE", + "PYMAPDL_PORT", + "PYMAPDL_IP", + "PYMAPDL_NPROC", + "PYMAPDL_MAPDL_EXEC", + "PYMAPDL_MAPDL_VERSION", + "PYMAPDL_MAX_MESSAGE_LENGTH", + "PYMAPDL_ON_SLURM", + "ON_CI", + "ON_LOCAL", + "ON_REMOTE", + "P_SCHEMA", +] + + +class Plain_Report: + def __init__(self, core, optional=None, additional=None, **kwargs): + """ + Base class for a plain report. + + + Based on `scooby `_ package. + + Parameters + ---------- + additional : iter[str] + List of packages or package names to add to output information. + core : iter[str] + The core packages to list first. + optional : iter[str] + A list of packages to list if they are available. If not available, + no warnings or error will be thrown. + """ + + self.additional = additional + self.core = core + self.optional = optional + self.kwargs = kwargs + + if os.name == "posix": + self.core.extend(["pexpect"]) + + # Information about the GPU - bare except in case there is a rendering + # bug that the user is trying to report. + if self.kwargs.get("gpu", False) and _HAS_PYVISTA: + + try: + from pyvista.report import GPUInfo + except ImportError: + from pyvista.utilities.errors import ( + GPUInfo, # deprecated in pyvista 0.40.0 + ) + + try: + self.kwargs["extra_meta"] = [(t[1], t[0]) for t in GPUInfo().get_info()] + except RuntimeError as e: # pragma: no cover + self.kwargs["extra_meta"] = ("GPU Details", f"Error: {str(e)}") + else: + self.kwargs["extra_meta"] = ("GPU Details", "None") + + def get_version(self, package): + try: + import importlib.metadata as importlib_metadata + except ModuleNotFoundError: # pragma: no cover + import importlib_metadata + + try: + return importlib_metadata.version(package.replace(".", "-")) + except importlib_metadata.PackageNotFoundError: + return "Package not found" + + def __repr__(self): + header = [ + "-" * 79, + "\n", + "PyMAPDL Software and Environment Report", + "\n", + "Packages Requirements", + "*********************", + ] + + core = ["\nCore packages", "-------------"] + core.extend( + [ + f"{each.ljust(20)}: {self.get_version(each)}" + for each in self.core + if self.get_version(each) + ] + ) + + if self.optional: + optional = ["\nOptional packages", "-----------------"] + optional.extend( + [ + f"{each.ljust(20)}: {self.get_version(each)}" + for each in self.optional + if self.get_version(each) + ] + ) + else: + optional = [""] + + if self.additional: + additional = ["\nAdditional packages", "-----------------"] + additional.extend( + [ + f"{each.ljust(20)}: {self.get_version(each)}" + for each in self.additional + if self.get_version(each) + ] + ) + else: + additional = [""] + + return "\n".join(header + core + optional + additional) + self.mapdl_info() + + def mapdl_info(self): + """Return information regarding the ansys environment and installation.""" + # this is here to avoid circular imports + + # List installed Ansys + lines = ["", "Ansys Environment Report", "-" * 79] + lines = ["\n", "Ansys Installation", "******************"] + if _HAS_ATP: + mapdl_install = get_available_ansys_installations() + + if not mapdl_install: + lines.append("Unable to locate any Ansys installations") + else: + lines.append("Version Location") + lines.append("------------------") + for key in sorted(mapdl_install.keys()): + lines.append(f"{abs(key)} {mapdl_install[key]}") + else: + mapdl_install = None + lines.append( + "Unable to locate any Ansys installations because 'ansys-tools-path is not installed." + ) + + install_info = "\n".join(lines) + + env_info_lines = [ + "\n\n\nAnsys Environment Variables", + "***************************", + ] + n_var = 0 + for key, value in os.environ.items(): + if "AWP" in key or "CADOE" in key or "ANSYS" in key: + env_info_lines.append(f"{key:<30} {value}") + n_var += 1 + if not n_var: + env_info_lines.append("None") + env_info = "\n".join(env_info_lines) + + return install_info + env_info + + +# Determine which type of report will be used (depending on the +# available packages) +if _HAS_PYANSYS_REPORT: + base_report_class = pyansys_report.Report +else: # pragma: no cover + base_report_class = Plain_Report + + +class Report(base_report_class): + """A class for custom scooby.Report.""" + + def __init__( + self, + additional=None, + ncol=3, + text_width=80, + sort=False, + gpu=True, + ansys_vars=ANSYS_ENV_VARS, + ansys_libs=None, + ): + """Generate a :class:`scooby.Report` instance. + + Parameters + ---------- + additional : list(ModuleType), list(str) + List of packages or package names to add to output information. + + ncol : int, optional + Number of package-columns in html table; only has effect if + ``mode='HTML'`` or ``mode='html'``. Defaults to 3. + + text_width : int, optional + The text width for non-HTML display modes + + sort : bool, optional + Alphabetically sort the packages + + gpu : bool + Gather information about the GPU. Defaults to ``True`` but if + experiencing rendering issues, pass ``False`` to safely generate + a report. + + ansys_vars : list of str, optional + List containing the Ansys environment variables to be reported. + (e.g. ["MYVAR_1", "MYVAR_2" ...]). Defaults to ``None``. Only used for + the `pyansys-tools-report` package. + + ansys_libs : dict {str : str}, optional + Dictionary containing the Ansys libraries and versions to be reported. + (e.g. {"MyLib" : "v1.2", ...}). Defaults to ``None``. Only used for + the `pyansys-tools-report` package. + + """ + # Mandatory packages + core = [ + "ansys.mapdl.core", + "numpy", + "platformdirs", + "scipy", + "grpc", # grpcio + "ansys.api.mapdl.v0", # ansys-api-mapdl-v0 + "ansys.mapdl.reader", # ansys-mapdl-reader + "google.protobuf", # protobuf library + "ansys-math-core", + ] + + # Optional packages + optional = [ + "matplotlib", + "pyvista", + "pyiges", + "tqdm", + "ansys-tools-visualization_interface", + "pandas", + ] + + if _HAS_PYANSYS_REPORT: + # Combine all packages into one + all_mapdl_packages = core + optional + if additional is not None: + all_mapdl_packages += additional + + # Call the pyansys_report.Report constructor + super().__init__( + additional=all_mapdl_packages, + ncol=ncol, + text_width=text_width, + sort=sort, + gpu=gpu, + ansys_vars=ansys_vars, + ansys_libs=ansys_libs, + ) + else: + # Call the PlainReport constructor + super().__init__( + additional=additional, + core=core, + optional=optional, + ncol=ncol, + text_width=text_width, + sort=sort, + gpu=gpu, + ) diff --git a/tests/test_misc.py b/tests/test_misc.py index d9fc88681a..95266c70ca 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -27,12 +27,6 @@ import numpy as np import pytest -from conftest import has_dependency, requires - -if has_dependency("pyvista"): - from pyvista.plotting import system_supports_plotting - -from ansys.mapdl import core as pymapdl from ansys.mapdl.core.misc import ( check_valid_ip, check_valid_port, @@ -44,18 +38,7 @@ requires_package, run_as_prep7, ) - - -@requires("pyvista") -def test_report(): - report = pymapdl.Report( - additional=["matplotlib", "pyvista", "pyiges", "tqdm"], - gpu=system_supports_plotting(), - ) - assert "PyAnsys Software and Environment Report" in str(report) - - # Check that when adding additional (repeated) packages, they appear only once - assert str(report).count("pyvista") == 1 +from conftest import requires @pytest.mark.parametrize( @@ -234,68 +217,6 @@ def test_load_file_local(mapdl, tmpdir, file_): assert file_ not in mapdl.list_files() -def test_plain_report(): - from ansys.mapdl.core.misc import Plain_Report - - core = ["numpy", "ansys.mapdl.reader"] - optional = ["pyvista", "tqdm"] - additional = ["scipy", "ger"] - - report = Plain_Report(core=core, optional=optional, additional=additional, gpu=True) - rep_str = report.__repr__() - - for each in core + optional + additional: - assert each in rep_str - - # There should be only one package not found ("ger") - assert "Package not found" in rep_str - not_found_packages = 1 - - # Plus the not additional packages - if not has_dependency("pyvista"): - not_found_packages += 1 - if not has_dependency("tqdm"): - not_found_packages += 1 - if not has_dependency("ansys.mapdl.reader"): - not_found_packages += 1 - if not has_dependency("scipy"): - not_found_packages += 1 - if not has_dependency("pexpect"): - not_found_packages += 1 - - _rep_str = rep_str.replace("Package not found", "", not_found_packages) - assert "Package not found" not in _rep_str - - assert "\n" in rep_str - assert len(rep_str.splitlines()) > 3 - - assert "Core packages" in rep_str - assert "Optional packages" in rep_str - assert "Additional packages" in rep_str - - # Plain report should not represent GPU details evenif asked for - assert "GPU Details" not in rep_str - - -def test_plain_report_no_options(): - from ansys.mapdl.core.misc import Plain_Report - - core = ["numpy", "ansys.mapdl.reader"] - - report = Plain_Report(core=core) - rep_str = report.__repr__() - - for each in core: - assert each in rep_str - - assert "\n" in rep_str - assert len(rep_str.splitlines()) > 3 - - assert "Core packages" in rep_str - assert "Optional packages" not in rep_str - assert "Additional packages" not in rep_str - - def test_requires_package_decorator(): class myClass: @requires_package("numpy") diff --git a/tests/test_report.py b/tests/test_report.py new file mode 100644 index 0000000000..4857dbf345 --- /dev/null +++ b/tests/test_report.py @@ -0,0 +1,104 @@ +# Copyright (C) 2016 - 2024 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. + +"""Test report features""" + +from conftest import has_dependency, requires + +if has_dependency("pyvista"): + from pyvista.plotting import system_supports_plotting + +from ansys.mapdl import core as pymapdl + + +@requires("pyvista") +def test_report(): + report = pymapdl.Report( + additional=["matplotlib", "pyvista", "pyiges", "tqdm"], + gpu=system_supports_plotting(), + ) + assert "PyAnsys Software and Environment Report" in str(report) + + # Check that when adding additional (repeated) packages, they appear only once + assert str(report).count("pyvista") == 1 + + +def test_plain_report(): + from ansys.mapdl.core.report import Plain_Report + + core = ["numpy", "ansys.mapdl.reader"] + optional = ["pyvista", "tqdm"] + additional = ["scipy", "ger"] + + report = Plain_Report(core=core, optional=optional, additional=additional, gpu=True) + rep_str = report.__repr__() + + for each in core + optional + additional: + assert each in rep_str + + # There should be only one package not found ("ger") + assert "Package not found" in rep_str + not_found_packages = 1 + + # Plus the not additional packages + if not has_dependency("pyvista"): + not_found_packages += 1 + if not has_dependency("tqdm"): + not_found_packages += 1 + if not has_dependency("ansys.mapdl.reader"): + not_found_packages += 1 + if not has_dependency("scipy"): + not_found_packages += 1 + if not has_dependency("pexpect"): + not_found_packages += 1 + + _rep_str = rep_str.replace("Package not found", "", not_found_packages) + assert "Package not found" not in _rep_str + + assert "\n" in rep_str + assert len(rep_str.splitlines()) > 3 + + assert "Core packages" in rep_str + assert "Optional packages" in rep_str + assert "Additional packages" in rep_str + + # Plain report should not represent GPU details evenif asked for + assert "GPU Details" not in rep_str + + +def test_plain_report_no_options(): + from ansys.mapdl.core.report import Plain_Report + + core = ["numpy", "ansys.mapdl.reader"] + + report = Plain_Report(core=core) + rep_str = report.__repr__() + + for each in core: + assert each in rep_str + + assert "\n" in rep_str + assert len(rep_str.splitlines()) > 3 + + assert "Core packages" in rep_str + assert "Optional packages" not in rep_str + assert "Additional packages" not in rep_str