From 61dfaf92c8211bf5b56fa902648b37766c55d2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Morais?= <146729917+SMoraisAnsys@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:40:59 +0200 Subject: [PATCH] REFACTOR: Settings from YAML configuration file (#5092) Co-authored-by: Maxime Rey <87315832+MaxJPRey@users.noreply.github.com> --- _unittest/test_utils.py | 46 + doc/source/Resources/pyaedt_settings.yaml | 122 +++ doc/source/User_guide/settings.rst | 141 +++ .../config/vocabularies/ANSYS/accept.txt | 1 + pyproject.toml | 1 + src/ansys/aedt/core/__init__.py | 9 +- src/ansys/aedt/core/application/design.py | 33 +- src/ansys/aedt/core/desktop.py | 4 + .../aedt/core/generic/general_methods.py | 1 + src/ansys/aedt/core/generic/settings.py | 867 +++++++++++------- 10 files changed, 879 insertions(+), 346 deletions(-) create mode 100644 doc/source/Resources/pyaedt_settings.yaml create mode 100644 doc/source/User_guide/settings.rst diff --git a/_unittest/test_utils.py b/_unittest/test_utils.py index 06d4252eae6..269eb6bbf3c 100644 --- a/_unittest/test_utils.py +++ b/_unittest/test_utils.py @@ -30,6 +30,7 @@ from unittest.mock import patch from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.settings import Settings from ansys.aedt.core.generic.settings import settings import pytest @@ -93,3 +94,48 @@ def test_handler_deprecation_log_warning(caplog): foo(trigger_exception=False) assert len(caplog.records) == 1 + + +def test_settings_load_yaml(tmp_path): + """Test loading a configure file with correct input.""" + default_settings = Settings() + + # Create temporary YAML configuration file + yaml_path = tmp_path / "pyaedt_settings.yaml" + yaml_path.write_text( + """ + log: + global_log_file_name: 'dummy' + lsf: + lsf_num_cores: 12 + general: + desktop_launch_timeout: 12 + """ + ) + + default_settings.load_yaml_configuration(str(yaml_path)) + + assert default_settings.global_log_file_name == "dummy" + assert default_settings.lsf_num_cores == 12 + assert default_settings.desktop_launch_timeout == 12 + + +def test_settings_load_yaml_with_non_allowed_key(tmp_path): + """Test loading a configuration file with invalid key.""" + default_settings = Settings() + + # Create temporary YAML configuration file + yaml_path = tmp_path / "pyaedt_settings.yaml" + yaml_path.write_text( + """ + general: + dummy: 12.0 + """ + ) + + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=False) + assert not hasattr(default_settings, "dummy") + + with pytest.raises(KeyError) as excinfo: + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=True) + assert str(excinfo) in "Key 'dummy' is not part of the allowed keys" diff --git a/doc/source/Resources/pyaedt_settings.yaml b/doc/source/Resources/pyaedt_settings.yaml new file mode 100644 index 00000000000..13b4b214f72 --- /dev/null +++ b/doc/source/Resources/pyaedt_settings.yaml @@ -0,0 +1,122 @@ +# This file contains the settings used by default to set the PyAEDT and AEDT including logging, +# LSF, environment variables and general settings. If you want to have a different behavior +# you can modify this file and save it. To be used in PyAEDT, the path to the configuration file +# should be specified with the environment variable ``PYAEDT_LOCAL_SETTINGS_PATH``. If no +# environment variable is set, PyAEDT looks for the configuration file ``pyaedt_settings.yaml`` +# in the user's ``APPDATA`` folder for Windows and ``HOME`` folder for Linux. + +# Settings related to logging +log: + # Enable or disable the logging of EDB API methods + enable_debug_edb_logger: false + # Enable or disable the logging of the geometry operators + enable_debug_geometry_operator_logger: false + # Enable or disable the logging of the gRPC API calls + enable_debug_grpc_api_logger: false + # Enable or disable the logging of internal methods + enable_debug_internal_methods_logger: false + # Enable or disable the logging at debug level + enable_debug_logger: false + # Enable or disable the logging of methods' arguments at debug level + enable_debug_methods_argument_logger: false + # Enable or disable the logging to the AEDT message window + enable_desktop_logs: true + # Enable or disable the logging to a file + enable_file_logs: true + # Enable or disable the global PyAEDT log file located in the global temp folder + enable_global_log_file: true + # Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder + enable_local_log_file: false + # Enable or disable the logging overall + enable_logger: true + # Enable or disable the logging to STDOUT + enable_screen_logs: true + # Global PyAEDT log file path + global_log_file_name: null + # Global PyAEDT log file size in MB + global_log_file_size: 10 + # Date format of the log entries + logger_datefmt: '%Y/%m/%d %H.%M.%S' + # PyAEDT log file path + logger_file_path: null + # Message format of the log entries + logger_formatter: '%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s' + # Path to the AEDT log file + aedt_log_file: null + +# Settings related to Linux systems running LSF scheduler +lsf: + # Command to launch in the LSF Scheduler + custom_lsf_command: null + # Command to launch the task in the LSF Scheduler + lsf_aedt_command: 'ansysedt' + # Number of LSF cores + lsf_num_cores: 2 + # Operating system string + lsf_osrel: null + # LSF queue name + lsf_queue: null + # RAM allocated for the LSF job + lsf_ram: 1000 + # Timeout in seconds for trying to start the interactive session + lsf_timeout: 3600 + # Value passed in the LSF 'select' string to the ui resource + lsf_ui: null + # Enable or disable use LSF Scheduler + use_lsf_scheduler: false + +# Settings related to environment variables thare are set before launching a new AEDT session +# This includes those that enable the beta features ! +aedt_env_var: + ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE: '1' + ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE: '1' + ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE: '1' + ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE: '1' + ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE: '1' + ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE: '1' + ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE: '1' + ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' + ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' + ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + +general: + # Enable or disable the lazy load + lazy_load: true + # Enable or disable the lazy load dedicated to objects associated to the modeler + objects_lazy_load: true + # AEDT installation path + aedt_install_dir: null + # AEDT version in the form ``"2023.x"`` + aedt_version: null + # Timeout in seconds for trying to launch AEDT + desktop_launch_timeout: 120 + # Enable or disable bounding box evaluation by exporting a SAT file + disable_bounding_box_sat: false + # Optional path for the EDB DLL file + edb_dll_path: null + # Enable or disable the internal PyAEDT error handling + enable_error_handler: true + # Enable or disable the use of Pandas to export dictionaries and lists + enable_pandas_output: false + # Enable or disable the check of the project path + force_error_on_missing_project: false + # Number of gRPC API retries + number_of_grpc_api_retries: 6 + # Enable or disable the release of AEDT on exception + release_on_exception: true + # Time interval between the retries by the ``_retry_n_times`` inner method + retry_n_times_time_interval: 0.1 + # Enable or disable the use of the gRPC API or legacy COM object + use_grpc_api: null + # Enable or disable the use of multiple desktop sessions in the same Python script + use_multi_desktop: false + # Enable or disable the use of the flag `-waitforlicense` when launching AEDT + wait_for_license: false + # State whether the remote API is used or not + remote_api: false + # Specify the port the RPyC server is to listen to + remote_rpc_service_manager_port: 17878 + # Specify the path to AEDT in the server + pyaedt_server_path: '' + # Remote temp folder + remote_rpc_session_temp_folder: '' diff --git a/doc/source/User_guide/settings.rst b/doc/source/User_guide/settings.rst new file mode 100644 index 00000000000..7bf7bf4d82f --- /dev/null +++ b/doc/source/User_guide/settings.rst @@ -0,0 +1,141 @@ +Settings +======== + +The Settings class is designed to handle the configurations of PyAEDT and AEDT. +This includes behavior for logging, LSF scheduler, environment variable and general +settings. Most of the default values used can be modified using a YAML configuration file. +The path to this YAML file should be defined through the environment variable +``PYAEDT_LOCAL_SETTINGS_PATH``. If the environment variable is set and a file exists, +the default configuration settings are updated according to the content of the file. +If the environment variable is not defined, a check is performed to see if a file named +``"pyaedt_settings.yaml"`` exist in the user's ``APPDATA`` folder for Windows and +``HOME`` folder for Linux. If such file exists, it is then used to update the default +configuration. + +Below is the content that can be updated through the YAML file. + +:download:`YAML configuration file <../Resources/pyaedt_settings.yaml>` + +.. note:: + Not all settings from class ``Settings`` can be modified through this file + as some of them expect Python objects or values obtained from code execution. + For example, that is the case for ``formatter`` which expects an object of type + ``Formatter`` and ``time_tick`` which expects a time value, in seconds, since the + `epoch `_ as a floating-point number. + + +.. code-block:: yaml + + # Settings related to logging + log: + # Enable or disable the logging of EDB API methods + enable_debug_edb_logger: false + # Enable or disable the logging of the geometry operators + enable_debug_geometry_operator_logger: false + # Enable or disable the logging of the gRPC API calls + enable_debug_grpc_api_logger: false + # Enable or disable the logging of internal methods + enable_debug_internal_methods_logger: false + # Enable or disable the logging at debug level + enable_debug_logger: false + # Enable or disable the logging of methods' arguments at debug level + enable_debug_methods_argument_logger: false + # Enable or disable the logging to the AEDT message window + enable_desktop_logs: true + # Enable or disable the logging to a file + enable_file_logs: true + # Enable or disable the global PyAEDT log file located in the global temp folder + enable_global_log_file: true + # Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder + enable_local_log_file: false + # Enable or disable the logging overall + enable_logger: true + # Enable or disable the logging to STDOUT + enable_screen_logs: true + # Global PyAEDT log file path + global_log_file_name: null + # Global PyAEDT log file size in MB + global_log_file_size: 10 + # Date format of the log entries + logger_datefmt: '%Y/%m/%d %H.%M.%S' + # PyAEDT log file path + logger_file_path: null + # Message format of the log entries + logger_formatter: '%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s' + + # Settings related to Linux systems running LSF scheduler + lsf: + # Command to launch in the LSF Scheduler + custom_lsf_command: null + # Command to launch the task in the LSF Scheduler + lsf_aedt_command: 'ansysedt' + # Number of LSF cores + lsf_num_cores: 2 + # Operating system string + lsf_osrel: null + # LSF queue name + lsf_queue: null + # RAM allocated for the LSF job + lsf_ram: 1000 + # Timeout in seconds for trying to start the interactive session + lsf_timeout: 3600 + # Value passed in the LSF 'select' string to the ui resource + lsf_ui: null + # Enable or disable use LSF Scheduler + use_lsf_scheduler: false + + # Settings related to environment variables thare are set before launching a new AEDT session + # This includes those that enable the beta features ! + aedt_env_var: + ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE: '1' + ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE: '1' + ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE: '1' + ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE: '1' + ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE: '1' + ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE: '1' + ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE: '1' + ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' + ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' + ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + + general: + # Enable or disable the lazy load + lazy_load: true + # Enable or disable the lazy load dedicated to objects associated to the modeler + objects_lazy_load: true + # AEDT installation path + aedt_install_dir: null + # AEDT version in the form ``"2023.x"`` + aedt_version: null + # Timeout in seconds for trying to launch AEDT + desktop_launch_timeout: 120 + # Enable or disable bounding box evaluation by exporting a SAT file + disable_bounding_box_sat: false + # Optional path for the EDB DLL file + edb_dll_path: null + # Enable or disable the internal PyAEDT error handling + enable_error_handler: true + # Enable or disable the use of Pandas to export dictionaries and lists + enable_pandas_output: false + # Enable or disable the check of the project path + force_error_on_missing_project: false + # Number of gRPC API retries + number_of_grpc_api_retries: 6 + # Enable or disable the release of AEDT on exception + release_on_exception: true + # Time interval between the retries by the ``_retry_n_times`` inner method + retry_n_times_time_interval: 0.1 + # Enable or disable the use of the gRPC API or legacy COM object + use_grpc_api: null + # Enable or disable the use of multiple desktop sessions in the same Python script + use_multi_desktop: false + # Enable or disable the use of the flag `-waitforlicense` when launching Electronic Desktop + wait_for_license: false + # State whether the remote API is used or not + remote_api: false + # Specify the port the RPyC server is to listen to + remote_rpc_service_manager_port: 17878 + # Specify the path to AEDT in the server + pyaedt_server_path: '' + # Remote temp folder + remote_rpc_session_temp_folder: '' diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 4125d4a4f2c..f9d92aaea61 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -39,6 +39,7 @@ Icepak IronPython [Ll]ayout limitilines +LSF matplotlib Maxwell 2D Maxwell 3D diff --git a/pyproject.toml b/pyproject.toml index 0328d3eb1c6..08beeffd978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "pyedb>=0.24.0; python_version > '3.7'", "pytomlpp; python_version < '3.12'", "rpyc>=6.0.0,<6.1", + "pyyaml", ] [project.optional-dependencies] diff --git a/src/ansys/aedt/core/__init__.py b/src/ansys/aedt/core/__init__.py index b6a1a526b37..6417813f3f8 100644 --- a/src/ansys/aedt/core/__init__.py +++ b/src/ansys/aedt/core/__init__.py @@ -67,7 +67,13 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non __version__ = "0.11.dev0" version = __version__ -# +# isort: off +# Settings have to be imported before importing other PyAEDT modules +from ansys.aedt.core.generic.general_methods import settings +from ansys.aedt.core.generic.general_methods import inner_project_settings + +# isort: on + if not ("IronPython" in sys.version or ".NETFramework" in sys.version): # pragma: no cover import ansys.aedt.core.downloads as downloads from ansys.aedt.core.edb import Edb # nosec @@ -103,7 +109,6 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non from ansys.aedt.core.generic.general_methods import is_windows from ansys.aedt.core.generic.general_methods import online_help from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.generic.general_methods import settings from ansys.aedt.core.misc import current_student_version from ansys.aedt.core.misc import current_version from ansys.aedt.core.misc import installed_versions diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index 4d579fcbe6f..1eca96b0298 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -66,6 +66,7 @@ from ansys.aedt.core.generic.general_methods import GrpcApiError from ansys.aedt.core.generic.general_methods import check_and_download_file from ansys.aedt.core.generic.general_methods import generate_unique_name +from ansys.aedt.core.generic.general_methods import inner_project_settings from ansys.aedt.core.generic.general_methods import is_ironpython from ansys.aedt.core.generic.general_methods import is_project_locked from ansys.aedt.core.generic.general_methods import is_windows @@ -88,8 +89,8 @@ def load_aedt_thread(project_path): pp = load_entire_aedt_file(project_path) - settings._project_properties[os.path.normpath(project_path)] = pp - settings._project_time_stamp = os.path.getmtime(project_path) + inner_project_settings.properties[os.path.normpath(project_path)] = pp + inner_project_settings.time_stamp = os.path.getmtime(project_path) class Design(AedtObjects): @@ -550,24 +551,28 @@ def project_properties(self): start = time.time() if self.project_timestamp_changed or ( os.path.exists(self.project_file) - and os.path.normpath(self.project_file) not in settings._project_properties + and os.path.normpath(self.project_file) not in inner_project_settings.properties ): - settings._project_properties[os.path.normpath(self.project_file)] = load_entire_aedt_file(self.project_file) + inner_project_settings.properties[os.path.normpath(self.project_file)] = load_entire_aedt_file( + self.project_file + ) self._logger.info("aedt file load time {}".format(time.time() - start)) elif ( - os.path.normpath(self.project_file) not in settings._project_properties + os.path.normpath(self.project_file) not in inner_project_settings.properties and settings.remote_rpc_session and settings.remote_rpc_session.filemanager.pathexists(self.project_file) ): file_path = check_and_download_file(self.project_file) try: - settings._project_properties[os.path.normpath(self.project_file)] = load_entire_aedt_file(file_path) + inner_project_settings.properties[os.path.normpath(self.project_file)] = load_entire_aedt_file( + file_path + ) except Exception: self._logger.info("Failed to load AEDT file.") else: self._logger.info("Time to load AEDT file: {}.".format(time.time() - start)) - if os.path.normpath(self.project_file) in settings._project_properties: - return settings._project_properties[os.path.normpath(self.project_file)] + if os.path.normpath(self.project_file) in inner_project_settings.properties: + return inner_project_settings.properties[os.path.normpath(self.project_file)] return {} @property @@ -769,15 +774,15 @@ def project_path(self): def project_time_stamp(self): """Return Project time stamp.""" if os.path.exists(self.project_file): - settings._project_time_stamp = os.path.getmtime(self.project_file) + inner_project_settings.time_stamp = os.path.getmtime(self.project_file) else: - settings._project_time_stamp = 0 - return settings._project_time_stamp + inner_project_settings.time_stamp = 0 + return inner_project_settings.time_stamp @property def project_timestamp_changed(self): """Return a bool if time stamp changed or not.""" - old_time = settings._project_time_stamp + old_time = inner_project_settings.time_stamp return old_time != self.project_time_stamp @property @@ -3297,8 +3302,8 @@ def close_project(self, name=None, save=True): i += 0.2 time.sleep(0.2) - if os.path.normpath(proj_file) in settings._project_properties: - del settings._project_properties[os.path.normpath(proj_file)] + if os.path.normpath(proj_file) in inner_project_settings.properties: + del inner_project_settings.properties[os.path.normpath(proj_file)] return True @pyaedt_function_handler() diff --git a/src/ansys/aedt/core/desktop.py b/src/ansys/aedt/core/desktop.py index 9a0e9419947..6170baea1b5 100644 --- a/src/ansys/aedt/core/desktop.py +++ b/src/ansys/aedt/core/desktop.py @@ -94,6 +94,8 @@ def launch_desktop_on_port(): command.append("-ng") if settings.wait_for_license: command.append("-waitforlicense") + if settings.aedt_log_file: + command.extend(["-Logfile", settings.aedt_log_file]) my_env = os.environ.copy() for env, val in settings.aedt_environment_variables.items(): my_env[env] = val @@ -174,6 +176,8 @@ def launch_aedt_in_lsf(non_graphical, port): # pragma: no cover command.append("-ng") if settings.wait_for_license: command.append("-waitforlicense") + if settings.aedt_log_file: + command.extend(["-Logfile", settings.aedt_log_file]) else: # pragma: no cover command = settings.custom_lsf_command.split(" ") command.append("-grpcsrv") diff --git a/src/ansys/aedt/core/generic/general_methods.py b/src/ansys/aedt/core/generic/general_methods.py index 2f96eb64080..eabcc54dcd8 100644 --- a/src/ansys/aedt/core/generic/general_methods.py +++ b/src/ansys/aedt/core/generic/general_methods.py @@ -47,6 +47,7 @@ from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.constants import CSS4_COLORS +from ansys.aedt.core.generic.settings import inner_project_settings # noqa: F401 from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.misc.misc import installed_versions diff --git a/src/ansys/aedt/core/generic/settings.py b/src/ansys/aedt/core/generic/settings.py index fbedec918fd..2dddf658537 100644 --- a/src/ansys/aedt/core/generic/settings.py +++ b/src/ansys/aedt/core/generic/settings.py @@ -22,12 +22,98 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +"""This module contains the ``Settings`` and ``_InnerProjectSettings`` classes. + +The first class encapsulates the settings associated with PyAEDT and AEDT including logging, +LSF, environment variables and general settings. Most of the default values used can be modified +using a YAML configuration file. An example of such file can be found in the documentation, see +`Settings YAML file `_. +The path to the configuration file should be specified with the environment variable +``PYAEDT_LOCAL_SETTINGS_PATH``. If no environment variable is set, the class will look for the +configuration file ``pyaedt_settings.yaml`` in the user's ``APPDATA`` folder for Windows and +``HOME`` folder for Linux. + +The second class is intended for internal use only and shouldn't be modified by users. +""" + +import logging import os import time +from typing import Any +from typing import List +from typing import Optional +from typing import Union import uuid is_linux = os.name == "posix" +# Settings allowed to be updated using a YAML configuration file. +ALLOWED_LOG_SETTINGS = [ + "enable_debug_edb_logger", + "enable_debug_geometry_operator_logger", + "enable_debug_grpc_api_logger", + "enable_debug_internal_methods_logger", + "enable_debug_logger", + "enable_debug_methods_argument_logger", + "enable_desktop_logs", + "enable_file_logs", + "enable_global_log_file", + "enable_local_log_file", + "enable_logger", + "enable_screen_logs", + "global_log_file_name", + "global_log_file_size", + "logger_datefmt", + "logger_file_path", + "logger_formatter", + "aedt_log_file", +] +ALLOWED_LSF_SETTINGS = [ + "custom_lsf_command", + "lsf_aedt_command", + "lsf_num_cores", + "lsf_osrel", + "lsf_queue", + "lsf_ram", + "lsf_timeout", + "lsf_ui", + "use_lsf_scheduler", +] +ALLOWED_GENERAL_SETTINGS = [ + "lazy_load", + "objects_lazy_load", + "aedt_install_dir", + "aedt_version", + "desktop_launch_timeout", + "disable_bounding_box_sat", + "edb_dll_path", + "enable_error_handler", + "enable_pandas_output", + "force_error_on_missing_project", + "number_of_grpc_api_retries", + "release_on_exception", + "retry_n_times_time_interval", + "use_grpc_api", + "use_multi_desktop", + "wait_for_license", + "remote_api", + "remote_rpc_service_manager_port", + "pyaedt_server_path", + "remote_rpc_session_temp_folder", +] +ALLOWED_AEDT_ENV_VAR_SETTINGS = [ + "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE", + "ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE", + "ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE", + "ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE", + "ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE", + "ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE", + "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE", + "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE", + "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE", + "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3", +] + def generate_log_filename(): """Generate a log filename.""" @@ -37,58 +123,54 @@ def generate_log_filename(): return "{}_{}_{}.log".format(base, username, unique_id) +class _InnerProjectSettings: # pragma: no cover + """Global inner project settings. + + This class is intended for internal use only. + """ + + properties: dict = {} + time_stamp: Union[int, float] = 0 + + class Settings(object): # pragma: no cover """Manages all PyAEDT environment variables and global settings.""" def __init__(self): - self._logger = None - self._enable_logger = True - self._enable_desktop_logs = True - self._enable_screen_logs = True - self._enable_file_logs = True - self.pyaedt_server_path = "" - self._logger_file_path = None - self._logger_formatter = "%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s" - self._logger_datefmt = "%Y/%m/%d %H.%M.%S" - self._enable_debug_edb_logger = False - self._enable_debug_grpc_api_logger = False - self._enable_debug_methods_argument_logger = False - self._enable_debug_geometry_operator_logger = False - self._enable_debug_internal_methods_logger = False - self._enable_debug_logger = False - self._enable_error_handler = True - self._release_on_exception = True - self._aedt_version = None - self._aedt_install_dir = None - self._use_multi_desktop = False - self.remote_api = False - self._use_grpc_api = None - self.formatter = None - self.remote_rpc_session = None - self.remote_rpc_session_temp_folder = "" - self.remote_rpc_service_manager_port = 17878 - self._project_properties = {} - self._project_time_stamp = 0 - self._disable_bounding_box_sat = False - self._force_error_on_missing_project = False - self._enable_pandas_output = False - self.time_tick = time.time() - self._global_log_file_name = generate_log_filename() - self._enable_global_log_file = True - self._enable_local_log_file = False - self._global_log_file_size = 10 - self._edb_dll_path = None - self._lsf_num_cores = 2 - self._lsf_ram = 1000 - self._use_lsf_scheduler = False - self._lsf_osrel = None - self._lsf_ui = None - self._lsf_aedt_command = "ansysedt" - self._lsf_timeout = 3600 - self._lsf_queue = None - self._custom_lsf_command = None - self._aedt_environment_variables = { - "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3": "1", + # Setup default values then load values from PersoalLib' settings_config.yaml if it exists. + # Settings related to logging + self.__logger: Optional[logging.Logger] = None + self.__enable_logger: bool = True + self.__enable_desktop_logs: bool = True + self.__enable_screen_logs: bool = True + self.__enable_file_logs: bool = True + self.__logger_file_path: Optional[str] = None + self.__logger_formatter: str = "%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s" + self.__logger_datefmt: str = "%Y/%m/%d %H.%M.%S" + self.__enable_debug_edb_logger: bool = False + self.__enable_debug_grpc_api_logger: bool = False + self.__enable_debug_methods_argument_logger: bool = False + self.__enable_debug_geometry_operator_logger: bool = False + self.__enable_debug_internal_methods_logger: bool = False + self.__enable_debug_logger: bool = False + self.__global_log_file_name: str = generate_log_filename() + self.__enable_global_log_file: bool = True + self.__enable_local_log_file: bool = False + self.__global_log_file_size: int = 10 + self.__aedt_log_file: Optional[str] = None + # Settings related to Linux systems running LSF scheduler + self.__lsf_num_cores: int = 2 + self.__lsf_ram: int = 1000 + self.__use_lsf_scheduler: bool = False + self.__lsf_osrel: Optional[str] = None + self.__lsf_ui: Optional[int] = None + self.__lsf_aedt_command: str = "ansysedt" + self.__lsf_timeout: int = 3600 + self.__lsf_queue: Optional[str] = None + self.__custom_lsf_command: Optional[str] = None + # Settings related to environment variables that are set before launching a new AEDT session + # This includes those that enable the beta features ! + self.__aedt_environment_variables: dict[str, str] = { "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE": "1", "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE": "1", "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE": "1", @@ -98,220 +180,470 @@ def __init__(self): "ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE": "1", "ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE": "1", "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE": "1", + "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3": "1", } if is_linux: - self._aedt_environment_variables["ANS_NODEPCHECK"] = "1" - self._desktop_launch_timeout = 120 - self._number_of_grpc_api_retries = 6 - self._retry_n_times_time_interval = 0.1 - self._wait_for_license = False - self.__lazy_load = True - self.__objects_lazy_load = True + self.__aedt_environment_variables["ANS_NODEPCHECK"] = "1" + # General settings + self.__enable_error_handler: bool = True + self.__release_on_exception: bool = True + self.__aedt_version: Optional[str] = None + self.__aedt_install_dir: Optional[str] = None + self.__use_multi_desktop: bool = False + self.__use_grpc_api: Optional[bool] = None + self.__disable_bounding_box_sat = False + self.__force_error_on_missing_project = False + self.__enable_pandas_output = False + self.__edb_dll_path: Optional[str] = None + self.__desktop_launch_timeout: int = 120 + self.__number_of_grpc_api_retries: int = 6 + self.__retry_n_times_time_interval: float = 0.1 + self.__wait_for_license: bool = False + self.__lazy_load: bool = True + self.__objects_lazy_load: bool = True + # Previously 'public' attributes + self.__formatter: Optional[logging.Formatter] = None + self.__remote_rpc_session: Any = None + self.__remote_rpc_session_temp_folder: str = "" + self.__remote_rpc_service_manager_port: int = 17878 + self.__remote_api: bool = False + self.__time_tick = time.time() + self.__pyaedt_server_path = "" + + # Load local settings if YAML configuration file exists. + pyaedt_settings_path = os.environ.get("PYAEDT_LOCAL_SETTINGS_PATH", "") + if not pyaedt_settings_path: + if os.name == "posix": + pyaedt_settings_path = os.path.join(os.environ["HOME"], "pyaedt_settings.yaml") + else: + pyaedt_settings_path = os.path.join(os.environ["APPDATA"], "pyaedt_settings.yaml") + self.load_yaml_configuration(pyaedt_settings_path) + + # ########################## Logging properties ########################## @property - def release_on_exception(self): - """ + def logger(self): + """Active logger.""" + return self.__logger - Returns - ------- + @logger.setter + def logger(self, val): + self.__logger = val - """ - return self._release_on_exception + @property + def enable_desktop_logs(self): + """Enable or disable the logging to the AEDT message window.""" + return self.__enable_desktop_logs - @release_on_exception.setter - def release_on_exception(self, value): - self._release_on_exception = value + @enable_desktop_logs.setter + def enable_desktop_logs(self, val): + self.__enable_desktop_logs = val @property - def objects_lazy_load(self): - """Flag for enabling and disabling the lazy load. - The default is ``True``. + def global_log_file_size(self): + """Global PyAEDT log file size in MB. The default value is ``10``.""" + return self.__global_log_file_size - Returns - ------- - bool - """ - return self.__objects_lazy_load + @global_log_file_size.setter + def global_log_file_size(self, value): + self.__global_log_file_size = value - @objects_lazy_load.setter - def objects_lazy_load(self, value): - self.__objects_lazy_load = value + @property + def enable_global_log_file(self): + """Enable or disable the global PyAEDT log file located in the global temp folder. + The default is ``True``.""" + return self.__enable_global_log_file + + @enable_global_log_file.setter + def enable_global_log_file(self, value): + self.__enable_global_log_file = value @property - def lazy_load(self): - """Flag for enabling and disabling the lazy load. - The default is ``True``. + def enable_local_log_file(self): + """Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder. + The default is ``True``.""" + return self.__enable_local_log_file - Returns - ------- - bool - """ - return self.__lazy_load + @enable_local_log_file.setter + def enable_local_log_file(self, value): + self.__enable_local_log_file = value - @lazy_load.setter - def lazy_load(self, value): - self.__lazy_load = value + @property + def global_log_file_name(self): + """Global PyAEDT log file path. The default is ``pyaedt_username.log``.""" + return self.__global_log_file_name + + @global_log_file_name.setter + def global_log_file_name(self, value): + self.__global_log_file_name = value @property - def wait_for_license(self): - """Whether if Electronics Desktop has to be launched with ``-waitforlicense`` flag enabled or not. - Default is ``False``. + def enable_debug_methods_argument_logger(self): + """Flag for whether to write out the method's arguments in the debug logger. + The default is ``False``.""" + return self.__enable_debug_methods_argument_logger - Returns - ------- - bool - """ - return self._wait_for_license + @enable_debug_methods_argument_logger.setter + def enable_debug_methods_argument_logger(self, val): + self.__enable_debug_methods_argument_logger = val - @wait_for_license.setter - def wait_for_license(self, value): - self._wait_for_license = value + @property + def enable_screen_logs(self): + """Enable or disable the logging to STDOUT.""" + return self.__enable_screen_logs + + @enable_screen_logs.setter + def enable_screen_logs(self, val): + self.__enable_screen_logs = val @property - def retry_n_times_time_interval(self): - """Time interval between the retries by the ``_retry_n_times`` method.""" - return self._retry_n_times_time_interval + def enable_file_logs(self): + """Enable or disable the logging to a file.""" + return self.__enable_file_logs - @retry_n_times_time_interval.setter - def retry_n_times_time_interval(self, value): - self._retry_n_times_time_interval = float(value) + @enable_file_logs.setter + def enable_file_logs(self, val): + self.__enable_file_logs = val @property - def number_of_grpc_api_retries(self): - """Number of gRPC API retries. The default is ``3``.""" - return self._number_of_grpc_api_retries + def enable_logger(self): + """Enable or disable the logging overall.""" + return self.__enable_logger - @number_of_grpc_api_retries.setter - def number_of_grpc_api_retries(self, value): - self._number_of_grpc_api_retries = int(value) + @enable_logger.setter + def enable_logger(self, val): + self.__enable_logger = val @property - def desktop_launch_timeout(self): - """Timeout in seconds for trying to launch AEDT. The default is ``90`` seconds.""" - return self._desktop_launch_timeout + def logger_file_path(self): + """PyAEDT log file path.""" + return self.__logger_file_path - @desktop_launch_timeout.setter - def desktop_launch_timeout(self, value): - self._desktop_launch_timeout = int(value) + @logger_file_path.setter + def logger_file_path(self, val): + self.__logger_file_path = val @property - def aedt_environment_variables(self): - """Environment variables that are set before launching a new AEDT session, - including those that enable the beta features.""" - return self._aedt_environment_variables + def logger_formatter(self): + """Message format of the log entries. + The default is ``'%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s'``""" + return self.__logger_formatter - @aedt_environment_variables.setter - def aedt_environment_variables(self, value): - self._aedt_environment_variables = value + @logger_formatter.setter + def logger_formatter(self, val): + self.__logger_formatter = val + + @property + def logger_datefmt(self): + """Date format of the log entries. + The default is ``'%Y/%m/%d %H.%M.%S'``""" + return self.__logger_datefmt + + @logger_datefmt.setter + def logger_datefmt(self, val): + self.__logger_datefmt = val + + @property + def enable_debug_edb_logger(self): + """Enable or disable the logger for any EDB API methods.""" + return self.__enable_debug_edb_logger + + @enable_debug_edb_logger.setter + def enable_debug_edb_logger(self, val): + self.__enable_debug_edb_logger = val + + @property + def enable_debug_grpc_api_logger(self): + """Enable or disable the logging for the gRPC API calls.""" + return self.__enable_debug_grpc_api_logger + + @enable_debug_grpc_api_logger.setter + def enable_debug_grpc_api_logger(self, val): + self.__enable_debug_grpc_api_logger = val + + @property + def enable_debug_geometry_operator_logger(self): + """Enable or disable the logging for the geometry operators. + This setting is useful for debug purposes.""" + return self.__enable_debug_geometry_operator_logger + + @enable_debug_geometry_operator_logger.setter + def enable_debug_geometry_operator_logger(self, val): + self.__enable_debug_geometry_operator_logger = val + + @property + def enable_debug_internal_methods_logger(self): + """Enable or disable the logging for internal methods. + This setting is useful for debug purposes.""" + return self.__enable_debug_internal_methods_logger + + @enable_debug_internal_methods_logger.setter + def enable_debug_internal_methods_logger(self, val): + self.__enable_debug_internal_methods_logger = val + + @property + def enable_debug_logger(self): + """Enable or disable the debug level logger.""" + return self.__enable_debug_logger + + @enable_debug_logger.setter + def enable_debug_logger(self, val): + self.__enable_debug_logger = val + + @property + def aedt_log_file(self): + """Path to the AEDT log file. + + Used to specify that Electronics Desktop has to be launched with ``-Logfile`` option. + """ + return self.__aedt_log_file + + @aedt_log_file.setter + def aedt_log_file(self, value: str): + self.__aedt_log_file = value + + # ############################# LSF properties ############################ @property def lsf_queue(self): """LSF queue name. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_queue + return self.__lsf_queue @lsf_queue.setter def lsf_queue(self, value): - self._lsf_queue = value + self.__lsf_queue = value @property def use_lsf_scheduler(self): """Whether to use LSF Scheduler. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._use_lsf_scheduler + return self.__use_lsf_scheduler @use_lsf_scheduler.setter def use_lsf_scheduler(self, value): - self._use_lsf_scheduler = value + self.__use_lsf_scheduler = value @property def lsf_aedt_command(self): """Command to launch the task in the LSF Scheduler. The default is ``"ansysedt"``. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_aedt_command + return self.__lsf_aedt_command @lsf_aedt_command.setter def lsf_aedt_command(self, value): - self._lsf_aedt_command = value + self.__lsf_aedt_command = value @property def lsf_num_cores(self): """Number of LSF cores. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_num_cores + return self.__lsf_num_cores @lsf_num_cores.setter def lsf_num_cores(self, value): - self._lsf_num_cores = int(value) + self.__lsf_num_cores = int(value) @property def lsf_ram(self): """RAM allocated for the LSF job. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_ram + return self.__lsf_ram @lsf_ram.setter def lsf_ram(self, value): - self._lsf_ram = int(value) + self.__lsf_ram = int(value) @property def lsf_ui(self): """Value passed in the LSF 'select' string to the ui resource.""" - return self._lsf_ui + return self.__lsf_ui @lsf_ui.setter def lsf_ui(self, value): - self._lsf_ui = int(value) + self.__lsf_ui = int(value) @property def lsf_timeout(self): """Timeout in seconds for trying to start the interactive session. The default is ``3600`` seconds.""" - return self._lsf_timeout + return self.__lsf_timeout @lsf_timeout.setter def lsf_timeout(self, value): - self._lsf_timeout = int(value) + self.__lsf_timeout = int(value) @property def lsf_osrel(self): """Operating system string. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_osrel + return self.__lsf_osrel @lsf_osrel.setter def lsf_osrel(self, value): - self._lsf_osrel = value + self.__lsf_osrel = value @property def custom_lsf_command(self): """Command to launch in the LSF Scheduler. The default is ``None``. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._custom_lsf_command + return self.__custom_lsf_command @custom_lsf_command.setter def custom_lsf_command(self, value): - self._custom_lsf_command = value + self.__custom_lsf_command = value + + # ############################## Environment variable properties ############################## + + @property + def aedt_environment_variables(self): + """Environment variables that are set before launching a new AEDT session, + including those that enable the beta features.""" + return self.__aedt_environment_variables + + @aedt_environment_variables.setter + def aedt_environment_variables(self, value): + self._aedt_environment_variables = value + + # ##################################### General properties #################################### + + @property + def remote_api(self): + """State whether remote API is used or not.""" + return self.__remote_api + + @remote_api.setter + def remote_api(self, value: bool): + self.__remote_api = value + + @property + def formatter(self): + """Get the formatter.""" + return self.__formatter + + @formatter.setter + def formatter(self, value: logging.Formatter): + self.__formatter = value + + @property + def remote_rpc_session(self): + """Get the RPyC connection.""" + return self.__remote_rpc_session + + @remote_rpc_session.setter + def remote_rpc_session(self, value: Any): + self.__remote_rpc_session = value + + @property + def remote_rpc_session_temp_folder(self): + """Get the remote RPyC session temp folder.""" + return self.__remote_rpc_session_temp_folder + + @remote_rpc_session_temp_folder.setter + def remote_rpc_session_temp_folder(self, value: str): + self.__remote_rpc_session_temp_folder = value + + @property + def remote_rpc_service_manager_port(self): + """Get the remote RPyC service manager port.""" + return self.__remote_rpc_service_manager_port + + @remote_rpc_service_manager_port.setter + def remote_rpc_service_manager_port(self, value: int): + self.__remote_rpc_service_manager_port = value + + @property + def time_tick(self): + """Time in seconds since the 'epoch' as a floating-point number.""" + return self.__time_tick + + @time_tick.setter + def time_tick(self, value: float): + self.__time_tick = value + + @property + def release_on_exception(self): + """Enable or disable the release of AEDT on exception.""" + return self.__release_on_exception + + @release_on_exception.setter + def release_on_exception(self, value): + self.__release_on_exception = value + + @property + def objects_lazy_load(self): + """Flag for enabling and disabling the lazy load. The default value is ``True``.""" + return self.__objects_lazy_load + + @objects_lazy_load.setter + def objects_lazy_load(self, value): + self.__objects_lazy_load = value + + @property + def lazy_load(self): + """Flag for enabling and disabling the lazy load. The default value is ``True``.""" + return self.__lazy_load + + @lazy_load.setter + def lazy_load(self, value): + self.__lazy_load = value + + @property + def wait_for_license(self): + """Enable or disable the use of the flag `-waitforlicense` when launching Electronic Desktop. + The default value is ``False``.""" + return self.__wait_for_license + + @wait_for_license.setter + def wait_for_license(self, value): + self.__wait_for_license = value + + @property + def retry_n_times_time_interval(self): + """Time interval between the retries by the ``_retry_n_times`` method.""" + return self.__retry_n_times_time_interval + + @retry_n_times_time_interval.setter + def retry_n_times_time_interval(self, value): + self.__retry_n_times_time_interval = float(value) + + @property + def number_of_grpc_api_retries(self): + """Number of gRPC API retries. The default is ``3``.""" + return self.__number_of_grpc_api_retries + + @number_of_grpc_api_retries.setter + def number_of_grpc_api_retries(self, value): + self.__number_of_grpc_api_retries = int(value) + + @property + def desktop_launch_timeout(self): + """Timeout in seconds for trying to launch AEDT. The default is ``120`` seconds.""" + return self.__desktop_launch_timeout + + @desktop_launch_timeout.setter + def desktop_launch_timeout(self, value): + self.__desktop_launch_timeout = int(value) @property def aedt_version(self): """AEDT version in the form ``"2023.x"``. In AEDT 2022 R2 and later, evaluating a bounding box by exporting a SAT file is disabled.""" - return self._aedt_version + return self.__aedt_version @aedt_version.setter def aedt_version(self, value): - self._aedt_version = value - if self._aedt_version >= "2023.1": + self.__aedt_version = value + if self.__aedt_version >= "2023.1": self.disable_bounding_box_sat = True @property def aedt_install_dir(self): """AEDT installation path.""" - return self._aedt_install_dir + return self.__aedt_install_dir @aedt_install_dir.setter def aedt_install_dir(self, value): - self._aedt_install_dir = value + self.__aedt_install_dir = value @property def use_multi_desktop(self): @@ -323,248 +655,123 @@ def use_multi_desktop(self): Enabling multiple desktop sessions is a beta feature.""" - return self._use_multi_desktop + return self.__use_multi_desktop @use_multi_desktop.setter def use_multi_desktop(self, value): - self._use_multi_desktop = value + self.__use_multi_desktop = value @property def edb_dll_path(self): """Optional path for the EDB DLL file.""" - return self._edb_dll_path + return self.__edb_dll_path @edb_dll_path.setter def edb_dll_path(self, value): if os.path.exists(value): - self._edb_dll_path = value - - @property - def global_log_file_size(self): - """Global PyAEDT log file size in MB. The default value is ``10``.""" - return self._global_log_file_size - - @global_log_file_size.setter - def global_log_file_size(self, value): - self._global_log_file_size = value - - @property - def enable_global_log_file(self): - """Flag for enabling and disabling the global PyAEDT log file located in the global temp folder. - The default is ``True``.""" - return self._enable_global_log_file - - @enable_global_log_file.setter - def enable_global_log_file(self, value): - self._enable_global_log_file = value - - @property - def enable_local_log_file(self): - """Flag for enabling and disabling the local PyAEDT log file located - in the ``projectname.pyaedt`` project folder. The default is ``True``.""" - return self._enable_local_log_file - - @enable_local_log_file.setter - def enable_local_log_file(self, value): - self._enable_local_log_file = value - - @property - def global_log_file_name(self): - """Global PyAEDT log file path. The default is ``pyaedt_username.log``.""" - return self._global_log_file_name - - @global_log_file_name.setter - def global_log_file_name(self, value): - self._global_log_file_name = value + self.__edb_dll_path = value @property def enable_pandas_output(self): """Flag for whether Pandas is being used to export dictionaries and lists. This attribute applies to Solution data output. The default is ``False``. If ``True``, the property or method returns a Pandas object. This property is valid only in the CPython environment.""" - return self._enable_pandas_output + return self.__enable_pandas_output @enable_pandas_output.setter def enable_pandas_output(self, val): - self._enable_pandas_output = val - - @property - def enable_debug_methods_argument_logger(self): - """Flag for whether to write out the method's arguments in the debug logger. - The default is ``False``.""" - return self._enable_debug_methods_argument_logger - - @enable_debug_methods_argument_logger.setter - def enable_debug_methods_argument_logger(self, val): - self._enable_debug_methods_argument_logger = val + self.__enable_pandas_output = val @property def force_error_on_missing_project(self): """Flag for whether to check the project path. The default is ``False``. If ``True``, when passing a project path, the project has to exist. Otherwise, an error is raised.""" - return self._force_error_on_missing_project + return self.__force_error_on_missing_project @force_error_on_missing_project.setter def force_error_on_missing_project(self, val): - self._force_error_on_missing_project = val + self.__force_error_on_missing_project = val @property def disable_bounding_box_sat(self): """Flag for enabling and disabling bounding box evaluation by exporting a SAT file.""" - return self._disable_bounding_box_sat + return self.__disable_bounding_box_sat @disable_bounding_box_sat.setter def disable_bounding_box_sat(self, val): - self._disable_bounding_box_sat = val + self.__disable_bounding_box_sat = val @property def use_grpc_api(self): """Flag for whether to use the gRPC API or legacy COM object.""" - return self._use_grpc_api + return self.__use_grpc_api @use_grpc_api.setter def use_grpc_api(self, val): - self._use_grpc_api = val - - @property - def logger(self): - """Active logger.""" - return self._logger - - @logger.setter - def logger(self, val): - self._logger = val + self.__use_grpc_api = val @property def enable_error_handler(self): """Flag for enabling and disabling the internal PyAEDT error handling.""" - return self._enable_error_handler + return self.__enable_error_handler @enable_error_handler.setter def enable_error_handler(self, val): - self._enable_error_handler = val - - @property - def enable_desktop_logs(self): - """Flag for enabling and disabling the logging to the AEDT message window.""" - return self._enable_desktop_logs - - @enable_desktop_logs.setter - def enable_desktop_logs(self, val): - self._enable_desktop_logs = val - - @property - def enable_screen_logs(self): - """Flag for enabling and disabling the logging to STDOUT.""" - return self._enable_screen_logs - - @enable_screen_logs.setter - def enable_screen_logs(self, val): - self._enable_screen_logs = val + self.__enable_error_handler = val @property def pyaedt_server_path(self): - """``PYAEDT_SERVER_AEDT_PATH`` environment variable.""" - return os.getenv("PYAEDT_SERVER_AEDT_PATH", "") + """Get ``PYAEDT_SERVER_AEDT_PATH`` environment variable.""" + self.__pyaedt_server_path = os.getenv("PYAEDT_SERVER_AEDT_PATH", "") + return self.__pyaedt_server_path + # NOTE: Convenient way to set the environment variable for RPyC @pyaedt_server_path.setter def pyaedt_server_path(self, val): os.environ["PYAEDT_SERVER_AEDT_PATH"] = str(val) - - @property - def enable_file_logs(self): - """Flag for enabling and disabling the logging to a file.""" - return self._enable_file_logs - - @enable_file_logs.setter - def enable_file_logs(self, val): - self._enable_file_logs = val - - @property - def enable_logger(self): - """Flag for enabling and disabling the logging overall.""" - return self._enable_logger - - @enable_logger.setter - def enable_logger(self, val): - self._enable_logger = val - - @property - def logger_file_path(self): - """PyAEDT log file path.""" - return self._logger_file_path - - @logger_file_path.setter - def logger_file_path(self, val): - self._logger_file_path = val - - @property - def logger_formatter(self): - """Message format of the log entries. - The default is ``'%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s'``""" - return self._logger_formatter - - @logger_formatter.setter - def logger_formatter(self, val): - self._logger_formatter = val - - @property - def logger_datefmt(self): - """Date format of the log entries. - The default is ``'%Y/%m/%d %H.%M.%S'``""" - return self._logger_datefmt - - @logger_datefmt.setter - def logger_datefmt(self, val): - self._logger_datefmt = val - - @property - def enable_debug_edb_logger(self): - """Flag for enabling and disabling the logger for any EDB API methods.""" - return self._enable_debug_edb_logger - - @enable_debug_edb_logger.setter - def enable_debug_edb_logger(self, val): - self._enable_debug_edb_logger = val - - @property - def enable_debug_grpc_api_logger(self): - """Flag for enabling and disabling the logging for the gRPC API calls.""" - return self._enable_debug_grpc_api_logger - - @enable_debug_grpc_api_logger.setter - def enable_debug_grpc_api_logger(self, val): - self._enable_debug_grpc_api_logger = val - - @property - def enable_debug_geometry_operator_logger(self): - """Flag for enabling and disabling the logging for the geometry operators. - This setting is useful for debug purposes.""" - return self._enable_debug_geometry_operator_logger - - @enable_debug_geometry_operator_logger.setter - def enable_debug_geometry_operator_logger(self, val): - self._enable_debug_geometry_operator_logger = val - - @property - def enable_debug_internal_methods_logger(self): - """Flag for enabling and disabling the logging for internal methods. - This setting is useful for debug purposes.""" - return self._enable_debug_internal_methods_logger - - @enable_debug_internal_methods_logger.setter - def enable_debug_internal_methods_logger(self, val): - self._enable_debug_internal_methods_logger = val - - @property - def enable_debug_logger(self): - """Flag for enabling and disabling the debug level logger.""" - return self._enable_debug_logger - - @enable_debug_logger.setter - def enable_debug_logger(self, val): - self._enable_debug_logger = val + self.__pyaedt_server_path = os.environ["PYAEDT_SERVER_AEDT_PATH"] + + def load_yaml_configuration(self, path: str, raise_on_wrong_key: bool = False): + """Update default settings from a YAML configuration file.""" + import yaml + + def filter_settings(settings: dict, allowed_keys: List[str]): + """Filter the items of settings based on a list of allowed keys.""" + return filter(lambda item: item[0] in allowed_keys, settings.items()) + + def filter_settings_with_raise(settings: dict, allowed_keys: List[str]): + """Filter the items of settings based on a list of allowed keys.""" + for key, value in settings.items(): + if key not in allowed_keys: + raise KeyError(f"Key '{key}' is not part of the allowed keys {allowed_keys}") + yield key, value + + if os.path.exists(path): + with open(path, "r") as yaml_file: + local_settings = yaml.safe_load(yaml_file) + pairs = [ + ("log", ALLOWED_LOG_SETTINGS), + ("lsf", ALLOWED_LSF_SETTINGS), + ("aedt_env_var", ALLOWED_AEDT_ENV_VAR_SETTINGS), + ("general", ALLOWED_GENERAL_SETTINGS), + ] + for setting_type, allowed_settings_key in pairs: + settings = local_settings.get(setting_type, {}) + if raise_on_wrong_key: + for key, value in filter_settings_with_raise(settings, allowed_settings_key): + setattr(self, key, value) + else: + for key, value in filter_settings(settings, allowed_settings_key): + setattr(self, key, value) + + def writte_yaml_configuration(self, path: str): + """Write the current settings into a YAML configuration file.""" + import yaml + + if os.path.exists(path): + yaml.safe_dump(settings, path) settings = Settings() +inner_project_settings = _InnerProjectSettings()