From be68386b38ff530422a856d6794f61cb08bc58ba Mon Sep 17 00:00:00 2001 From: German Date: Fri, 24 Nov 2023 13:55:53 +0100 Subject: [PATCH 01/20] Adding entry point to launch_mapdl --- pyproject.toml | 3 +- src/ansys/mapdl/core/cli.py | 376 +++++++++++++++++++++++++++++++ src/ansys/mapdl/core/convert.py | 152 ------------- src/ansys/mapdl/core/launcher.py | 20 +- tests/test_launcher.py | 33 +++ 5 files changed, 423 insertions(+), 161 deletions(-) create mode 100644 src/ansys/mapdl/core/cli.py diff --git a/pyproject.toml b/pyproject.toml index b196b3f25b..d5e4a78c67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,8 @@ name = "ansys.mapdl.core" Source = "https://github.com/ansys/pymapdl" [project.scripts] -pymapdl_convert_script = "ansys.mapdl.core.convert:cli" +pymapdl_convert_script = "ansys.mapdl.core.cli:convert" +launch_mapdl = "ansys.mapdl.core.cli:launch_mapdl" [tool.pytest.ini_options] junit_family = "legacy" diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py new file mode 100644 index 0000000000..de8dfa1131 --- /dev/null +++ b/src/ansys/mapdl/core/cli.py @@ -0,0 +1,376 @@ +import os +from warnings import warn + +try: + import click + + _HAS_CLICK = True +except ModuleNotFoundError: + _HAS_CLICK = False + +if _HAS_CLICK: + ################################### + # Convert CLI + + @click.command() + @click.argument("filename_in") + @click.option("-o", default=None, help="Name of the output Python script.") + @click.option( + "--filename_out", default=None, help="Name of the output Python script." + ) + @click.option( + "--loglevel", + default="WARNING", + help="Logging level of the ansys object within the script.", + ) + @click.option( + "--auto_exit", + default=True, + help="Adds a line to the end of the script to exit MAPDL. Default ``True``", + ) + @click.option( + "--line_ending", default=None, help="When None, automatically is ``\n.``" + ) + @click.option( + "--exec_file", + default=None, + help="Specify the location of the ANSYS executable and include it in the converter output ``launch_mapdl`` call.", + ) + @click.option( + "--macros_as_functions", + default=True, + help="Attempt to convert MAPDL macros to python functions.", + ) + @click.option( + "--use_function_names", + default=True, + help="Convert MAPDL functions to ansys.mapdl.core.Mapdl class methods. When ``True``, the MAPDL command ``K`` will be converted to ``mapdl.k``. When ``False``, it will be converted to ``mapdl.run('k')``.", + ) + @click.option( + "--show_log", + default=False, + help="Print the converted commands using a logger (from ``logging`` Python module).", + ) + @click.option( + "--add_imports", + default=True, + help='If ``True``, add the lines ``from ansys.mapdl.core import launch_mapdl`` and ``mapdl = launch_mapdl(loglevel="WARNING")`` to the beginning of the output file. This option is useful if you are planning to use the output script from another mapdl session. See examples section. This option overrides ``auto_exit``.', + ) + @click.option( + "--comment_solve", + default=False, + help='If ``True``, it will pythonically comment the lines that contain ``"SOLVE"`` or ``"/EOF"``.', + ) + @click.option( + "--cleanup_output", + default=True, + help="If ``True`` the output is formatted using ``autopep8`` before writing the file or returning the string. This requires ``autopep8`` to be installed.", + ) + @click.option( + "--header", + default=True, + help="If ``True``, the default header is written in the first line of the output. If a string is provided, this string will be used as header.", + ) + @click.option( + "--print_com", + default=True, + help="Print command ``/COM`` arguments to python console. Defaults to ``True``.", + ) + def convert( + filename_in, + o, + filename_out, + loglevel, + auto_exit, + line_ending, + exec_file, + macros_as_functions, + use_function_names, + show_log, + add_imports, + comment_solve, + cleanup_output, + header, + print_com, + ): + """PyMAPDL CLI tool for converting MAPDL scripts to PyMAPDL scripts. + + USAGE: + + This example demonstrates the main use of this tool: + + $ pymapdl_convert_script mapdl.dat -o python.py + + File mapdl.dat successfully converted to python.py. + + The output argument is optional, in which case the "py" extension is used: + + $ pymapdl_convert_script mapdl.dat + + File mapdl.dat successfully converted to mapdl.py. + + You can use any option from ``ansys.mapdl.core.convert.convert_script`` function: + + $ pymapdl_convert_script mapdl.dat --auto-exit False + + File mapdl.dat successfully converted to mapdl.py. + + $ pymapdl_convert_script.exe mapdl.dat --filename_out mapdl.out --add_imports False + + File mapdl.dat successfully converted to mapdl.out. + + + """ + from ansys.mapdl.core.convert import convert_script + + if o: + filename_out = o + + convert_script( + filename_in, + filename_out, + loglevel, + auto_exit, + line_ending, + exec_file, + macros_as_functions, + use_function_names, + show_log, + add_imports, + comment_solve, + cleanup_output, + header, + print_com, + ) + + if filename_out: + print(f"File {filename_in} successfully converted to {filename_out}.") + else: + print( + f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." + ) + + from ansys.mapdl.core.launcher import launch_mapdl + + @click.command( + short_help="Launch MAPDL instances.", + help="For more information see :func:`ansys.mapdl.core.launcher.launch_mapdl`.", + ) + @click.option( + "--exec_file", + default=None, + help="The location of the MAPDL executable. Will use the cached location when left at the default ``None`` and no environment variable is set. The executable path can be also set through the environment variable ``PYMAPDL_MAPDL_EXEC``.", + ) + @click.option( + "--run_location", + default=None, + help="MAPDL working directory. Defaults to a temporary working directory. If directory doesn't exist, one is created.", + ) + @click.option( + "--jobname", default="", help="MAPDL jobname. Defaults to ``'file'``." + ) + @click.option("--nproc", default=2, help="Number of processors. Defaults to 2.") + @click.option( + "--ram", + default=None, + help="Fixed amount of memory to request for MAPDL. If ``None``, then MAPDL will use as much as available on the host machine.", + ) + @click.option( + "--mode", default=None, help="Argument not allowed in CLI. It will be ignored." + ) + @click.option( + "--override", + default=False, + help="Attempts to delete the lock file at the ``run_location``. Useful when a prior MAPDL session has exited prematurely and the lock file has not been deleted.", + ) + @click.option( + "--loglevel", + default="", + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--additional_switches", + default="", + help="Additional switches for MAPDL, for example ``'aa_r'``, the academic research license. Avoid adding switches like ``-i``, ``-o`` or ``-b`` as these are already included to start up the MAPDL server.", + ) + @click.option( + "--start_timeout", + default=45, + help="Maximum allowable time to connect to the MAPDL server.", + ) + @click.option( + "--port", + default=None, + help="Port to launch MAPDL gRPC on. Final port will be the first port available after (or including) this port. Defaults to 50052. You can also override the port default with the environment variable ``PYMAPDL_PORT=`` This argument has priority over the environment variable.", + ) + @click.option( + "--cleanup_on_exit", + default=False, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--start_instance", + default=None, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--ip", + default=None, + help="Used only when ``start_instance`` is ``False``. If provided, it will force ``start_instance`` to be ``False``. Specify the IP address of the MAPDL instance to connect to. You can also provide a hostname as an alternative to an IP address. Defaults to ``'127.0.0.1'``. You can also override the default behavior of this keyword argument with the environment variable ``PYMAPDL_IP=``. This argument has priority over the environment variable.", + ) + @click.option( + "--clear_on_connect", + default=True, + help="Defaults to ``True``, giving you a fresh environment when connecting to MAPDL. When if ``start_instance`` is specified it defaults to ``False``.", + ) + @click.option( + "--log_apdl", + default=None, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--remove_temp_files", + default=None, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--remove_temp_dir_on_exit", + default=False, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--verbose_mapdl", + default=None, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--license_server_check", + default=True, + help="Check if the license server is available if MAPDL fails to start. Only available on ``mode='grpc'``. Defaults ``True``.", + ) + @click.option( + "--license_type", + default=None, + help="Enable license type selection. You can input a string for its license name (for example ``'meba'`` or ``'ansys'``) or its description ('enterprise solver' or 'enterprise' respectively). You can also use legacy licenses (for example ``'aa_t_a'``) but it will also raise a warning. If it is not used (``None``), no specific license will be requested, being up to the license server to provide a specific license type. Default is ``None``.", + ) + @click.option( + "--print_com", + default=False, + help="Argument not allowed in CLI. It will be ignored.", + ) + @click.option( + "--add_env_vars", + default=None, + help="The provided dictionary will be used to extend the MAPDL process environment variables. If you want to control all of the environment variables, use the argument ``replace_env_vars``. Defaults to ``None``.", + ) + @click.option( + "--replace_env_vars", + default=None, + help="The provided dictionary will be used to replace all the MAPDL process environment variables. It replace the system environment variables which otherwise would be used in the process. To just add some environment variables to the MAPDL process, use ``add_env_vars``. Defaults to ``None``.", + ) + @click.option( + "--version", + default=None, + help="Version of MAPDL to launch. If ``None``, the latest version is used. Versions can be provided as integers (i.e. ``version=222``) or floats (i.e. ``version=22.2``). To retrieve the available installed versions, use the function :meth:`ansys.tools.path.path.get_available_ansys_installations`.", + ) + def launch_mapdl( + exec_file, + run_location, + jobname, + nproc, + ram, + mode, + override, + loglevel, + additional_switches, + start_timeout, + port, + cleanup_on_exit, + start_instance, + ip, + clear_on_connect, + log_apdl, + remove_temp_files, + remove_temp_dir_on_exit, + verbose_mapdl, + license_server_check, + license_type, + print_com, + add_env_vars, + replace_env_vars, + version, + ): + from ansys.mapdl.core.launcher import launch_mapdl + + if mode and mode != "grpc": + warn( + "Only gRPC mode is allowed when using CLI to launch MAPDL instances.\nIgnoring argument." + ) + + if loglevel: + warn( + "The following argument is not allowed in CLI: 'loglevel'.\nIgnoring argument." + ) + + if cleanup_on_exit: + warn( + "The following argument is not allowed in CLI: 'cleanup_on_exit'.\nIgnoring argument." + ) + + if start_instance: + warn( + "The following argument is not allowed in CLI: 'start_instance'.\nIgnoring argument." + ) + + if log_apdl: + warn( + "The following argument is not allowed in CLI: 'log_apdl'.\nIgnoring argument." + ) + + if remove_temp_files: + warn( + "The following argument is not allowed in CLI: 'remove_temp_files'.\nIgnoring argument." + ) + + if remove_temp_dir_on_exit: + warn( + "The following argument is not allowed in CLI: 'remove_temp_dir_on_exit'.\nIgnoring argument." + ) + + if verbose_mapdl: + warn( + "The following argument is not allowed in CLI: 'verbose_mapdl'.\nIgnoring argument." + ) + + if print_com: + warn( + "The following argument is not allowed in CLI: 'print_com'.\nIgnoring argument." + ) + + launch_mapdl( + just_launch=True, + run_location=run_location, + jobname=jobname, + nproc=nproc, + ram=ram, + mode=mode, + override=override, + additional_switches=additional_switches, + start_timeout=start_timeout, + port=port, + ip=ip, + clear_on_connect=clear_on_connect, + license_server_check=license_server_check, + license_type=license_type, + add_env_vars=add_env_vars, + replace_env_vars=replace_env_vars, + version=version, + ) + +else: + + def convert(): + print("PyMAPDL CLI requires click to be installed.") + + def launch_mapdl(): + print("PyMAPDL CLI requires click to be installed.") diff --git a/src/ansys/mapdl/core/convert.py b/src/ansys/mapdl/core/convert.py index 3a1f3d1f41..652c6b3ee9 100644 --- a/src/ansys/mapdl/core/convert.py +++ b/src/ansys/mapdl/core/convert.py @@ -1201,155 +1201,3 @@ def find_match(self, cmd): for each in pymethods: if each.startswith(cmd): return each - - -try: - import click - - _HAS_CLICK = True -except ModuleNotFoundError: - _HAS_CLICK = False - -if _HAS_CLICK: - # Loading CLI - - @click.command() - @click.argument("filename_in") - @click.option("-o", default=None, help="Name of the output Python script.") - @click.option( - "--filename_out", default=None, help="Name of the output Python script." - ) - @click.option( - "--loglevel", - default="WARNING", - help="Logging level of the ansys object within the script.", - ) - @click.option( - "--auto_exit", - default=True, - help="Adds a line to the end of the script to exit MAPDL. Default ``True``", - ) - @click.option( - "--line_ending", default=None, help="When None, automatically is ``\n.``" - ) - @click.option( - "--exec_file", - default=None, - help="Specify the location of the ANSYS executable and include it in the converter output ``launch_mapdl`` call.", - ) - @click.option( - "--macros_as_functions", - default=True, - help="Attempt to convert MAPDL macros to python functions.", - ) - @click.option( - "--use_function_names", - default=True, - help="Convert MAPDL functions to ansys.mapdl.core.Mapdl class methods. When ``True``, the MAPDL command ``K`` will be converted to ``mapdl.k``. When ``False``, it will be converted to ``mapdl.run('k')``.", - ) - @click.option( - "--show_log", - default=False, - help="Print the converted commands using a logger (from ``logging`` Python module).", - ) - @click.option( - "--add_imports", - default=True, - help='If ``True``, add the lines ``from ansys.mapdl.core import launch_mapdl`` and ``mapdl = launch_mapdl(loglevel="WARNING")`` to the beginning of the output file. This option is useful if you are planning to use the output script from another mapdl session. See examples section. This option overrides ``auto_exit``.', - ) - @click.option( - "--comment_solve", - default=False, - help='If ``True``, it will pythonically comment the lines that contain ``"SOLVE"`` or ``"/EOF"``.', - ) - @click.option( - "--cleanup_output", - default=True, - help="If ``True`` the output is formatted using ``autopep8`` before writing the file or returning the string. This requires ``autopep8`` to be installed.", - ) - @click.option( - "--header", - default=True, - help="If ``True``, the default header is written in the first line of the output. If a string is provided, this string will be used as header.", - ) - @click.option( - "--print_com", - default=True, - help="Print command ``/COM`` arguments to python console. Defaults to ``True``.", - ) - def cli( - filename_in, - o, - filename_out, - loglevel, - auto_exit, - line_ending, - exec_file, - macros_as_functions, - use_function_names, - show_log, - add_imports, - comment_solve, - cleanup_output, - header, - print_com, - ): - """PyMAPDL CLI tool for converting MAPDL scripts to PyMAPDL scripts. - - USAGE: - - This example demonstrates the main use of this tool: - - $ pymapdl_convert_script mapdl.dat -o python.py - - File mapdl.dat successfully converted to python.py. - - The output argument is optional, in which case the "py" extension is used: - - $ pymapdl_convert_script mapdl.dat - - File mapdl.dat successfully converted to mapdl.py. - - You can use any option from ``ansys.mapdl.core.convert.convert_script`` function: - - $ pymapdl_convert_script mapdl.dat --auto-exit False - - File mapdl.dat successfully converted to mapdl.py. - - $ pymapdl_convert_script.exe mapdl.dat --filename_out mapdl.out --add_imports False - - File mapdl.dat successfully converted to mapdl.out. - - - """ - if o: - filename_out = o - - convert_script( - filename_in, - filename_out, - loglevel, - auto_exit, - line_ending, - exec_file, - macros_as_functions, - use_function_names, - show_log, - add_imports, - comment_solve, - cleanup_output, - header, - print_com, - ) - - if filename_out: - print(f"File {filename_in} successfully converted to {filename_out}.") - else: - print( - f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." - ) - -else: - - def cli(): - print("PyMAPDL CLI requires click to be installed.") diff --git a/src/ansys/mapdl/core/launcher.py b/src/ansys/mapdl/core/launcher.py index ff1620e2ab..ab8751b12b 100644 --- a/src/ansys/mapdl/core/launcher.py +++ b/src/ansys/mapdl/core/launcher.py @@ -249,9 +249,6 @@ def launch_grpc( these are already included to start up the MAPDL server. See the notes section for additional details. - custom_bin : str, optional - Path to the MAPDL custom executable. - override : bool, optional Attempts to delete the lock file at the run_location. Useful when a prior MAPDL session has exited prematurely and @@ -1059,11 +1056,6 @@ def launch_mapdl( environment variable ``PYMAPDL_PORT=`` This argument has priority over the environment variable. - custom_bin : str, optional - Path to the MAPDL custom executable. On release 2020R2 on - Linux, if ``None``, will check to see if you have - ``ansys.mapdl_bin`` installed and use that executable. - cleanup_on_exit : bool, optional Exit MAPDL when python exits or the mapdl Python instance is garbage collected. @@ -1375,6 +1367,7 @@ def launch_mapdl( force_intel = kwargs.pop("force_intel", False) broadcast = kwargs.pop("log_broadcast", False) use_vtk = kwargs.pop("use_vtk", None) + just_launch = kwargs.pop("just_launch", None) # Transferring MAPDL arguments to start_parameters: start_parm = {} @@ -1515,6 +1508,10 @@ def launch_mapdl( if not start_instance: LOG.debug("Connecting to an existing instance of MAPDL at %s:%s", ip, port) + if just_launch: + print(f"There is an existing MAPDL instance at: {ip}:{port}") + return + mapdl = MapdlGrpc( ip=ip, port=port, @@ -1660,6 +1657,13 @@ def launch_mapdl( verbose=verbose_mapdl, **start_parm, ) + + if just_launch: + print(f"Launched an MAPDL instance at: {ip}:{port}") + if hasattr(process, "pid"): + print(f"MAPDL instance PID: {process.pid}") + return + mapdl = MapdlGrpc( ip=ip, port=port, diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 11c055ca60..e393d5041c 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -1,9 +1,11 @@ """Test the mapdl launcher""" import os +import subprocess import tempfile import weakref +import psutil import pytest from ansys.mapdl import core as pymapdl @@ -480,3 +482,34 @@ def test__parse_ip_route(): 172.23.112.0/20 dev eth0 proto kernel scope link src 172.23.121.145""" assert "172.23.112.1" == _parse_ip_route(output) + + +@pytest.fixture +def run_cli(): + def do_run(*args): + args = ["pymapdl_convert_script"] + list(args) + proc = subprocess.Popen( + args, + shell=True, + stdout=subprocess.PIPE, + ) + return proc.stdout.read().decode().lower() + + return do_run + + +@requires("click") +def test_launch_mapdl_cli(run_cli): + output = run_cli("") + + if os.environ.get("PYMAPDL_START_INSTANCE", "TRUE") == "FALSE": + # In remote + assert "There is an existing MAPDL instance at:" in output + else: + # In local + assert "Launched an MAPDL instance at" in output + + # grab ips and port + pid = int(output.splitlines()[1].split(":")[1].strip()) + p = psutil.Process(pid) + p.kill() From c83b32a98ff92b68122cd618ecafb1f2c547960d Mon Sep 17 00:00:00 2001 From: German Date: Fri, 24 Nov 2023 14:06:36 +0100 Subject: [PATCH 02/20] setting type --- src/ansys/mapdl/core/cli.py | 42 ++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index de8dfa1131..0e6583250c 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -159,118 +159,148 @@ def convert( @click.option( "--exec_file", default=None, + type=str, help="The location of the MAPDL executable. Will use the cached location when left at the default ``None`` and no environment variable is set. The executable path can be also set through the environment variable ``PYMAPDL_MAPDL_EXEC``.", ) @click.option( "--run_location", default=None, + type=str, help="MAPDL working directory. Defaults to a temporary working directory. If directory doesn't exist, one is created.", ) @click.option( - "--jobname", default="", help="MAPDL jobname. Defaults to ``'file'``." + "--jobname", + default="", + type=str, + help="MAPDL jobname. Defaults to ``'file'``.", + ) + @click.option( + "--nproc", type=int, default=2, help="Number of processors. Defaults to 2." ) - @click.option("--nproc", default=2, help="Number of processors. Defaults to 2.") @click.option( "--ram", default=None, + type=int, help="Fixed amount of memory to request for MAPDL. If ``None``, then MAPDL will use as much as available on the host machine.", ) @click.option( - "--mode", default=None, help="Argument not allowed in CLI. It will be ignored." + "--mode", + type=str, + default=None, + help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--override", default=False, + type=bool, help="Attempts to delete the lock file at the ``run_location``. Useful when a prior MAPDL session has exited prematurely and the lock file has not been deleted.", ) @click.option( "--loglevel", default="", + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--additional_switches", default="", + type=str, help="Additional switches for MAPDL, for example ``'aa_r'``, the academic research license. Avoid adding switches like ``-i``, ``-o`` or ``-b`` as these are already included to start up the MAPDL server.", ) @click.option( "--start_timeout", default=45, + type=int, help="Maximum allowable time to connect to the MAPDL server.", ) @click.option( "--port", default=None, + type=int, help="Port to launch MAPDL gRPC on. Final port will be the first port available after (or including) this port. Defaults to 50052. You can also override the port default with the environment variable ``PYMAPDL_PORT=`` This argument has priority over the environment variable.", ) @click.option( "--cleanup_on_exit", default=False, + type=bool, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--start_instance", default=None, + type=bool, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--ip", default=None, + type=str, help="Used only when ``start_instance`` is ``False``. If provided, it will force ``start_instance`` to be ``False``. Specify the IP address of the MAPDL instance to connect to. You can also provide a hostname as an alternative to an IP address. Defaults to ``'127.0.0.1'``. You can also override the default behavior of this keyword argument with the environment variable ``PYMAPDL_IP=``. This argument has priority over the environment variable.", ) @click.option( "--clear_on_connect", default=True, + type=bool, help="Defaults to ``True``, giving you a fresh environment when connecting to MAPDL. When if ``start_instance`` is specified it defaults to ``False``.", ) @click.option( "--log_apdl", default=None, + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--remove_temp_files", default=None, + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--remove_temp_dir_on_exit", default=False, + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--verbose_mapdl", default=None, + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--license_server_check", default=True, + type=bool, help="Check if the license server is available if MAPDL fails to start. Only available on ``mode='grpc'``. Defaults ``True``.", ) @click.option( "--license_type", default=None, + type=str, help="Enable license type selection. You can input a string for its license name (for example ``'meba'`` or ``'ansys'``) or its description ('enterprise solver' or 'enterprise' respectively). You can also use legacy licenses (for example ``'aa_t_a'``) but it will also raise a warning. If it is not used (``None``), no specific license will be requested, being up to the license server to provide a specific license type. Default is ``None``.", ) @click.option( "--print_com", default=False, + type=str, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--add_env_vars", default=None, - help="The provided dictionary will be used to extend the MAPDL process environment variables. If you want to control all of the environment variables, use the argument ``replace_env_vars``. Defaults to ``None``.", + type=str, + help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--replace_env_vars", default=None, - help="The provided dictionary will be used to replace all the MAPDL process environment variables. It replace the system environment variables which otherwise would be used in the process. To just add some environment variables to the MAPDL process, use ``add_env_vars``. Defaults to ``None``.", + type=str, + help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--version", default=None, + type=str, help="Version of MAPDL to launch. If ``None``, the latest version is used. Versions can be provided as integers (i.e. ``version=222``) or floats (i.e. ``version=22.2``). To retrieve the available installed versions, use the function :meth:`ansys.tools.path.path.get_available_ansys_installations`.", ) def launch_mapdl( @@ -296,8 +326,6 @@ def launch_mapdl( license_server_check, license_type, print_com, - add_env_vars, - replace_env_vars, version, ): from ansys.mapdl.core.launcher import launch_mapdl From 1b188395fb4ebe0d79885d79078721c069328046 Mon Sep 17 00:00:00 2001 From: German Date: Fri, 24 Nov 2023 14:07:44 +0100 Subject: [PATCH 03/20] better help message --- src/ansys/mapdl/core/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index 0e6583250c..5185b4fc96 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -154,7 +154,9 @@ def convert( @click.command( short_help="Launch MAPDL instances.", - help="For more information see :func:`ansys.mapdl.core.launcher.launch_mapdl`.", + help="""This command aims to replicate the behavior of :func:`ansys.mapdl.core.launcher.launch_mapdl` + +For more information see :func:`ansys.mapdl.core.launcher.launch_mapdl`.""", ) @click.option( "--exec_file", From 7152207abc61791418211414df2255feebac3821 Mon Sep 17 00:00:00 2001 From: German Date: Fri, 24 Nov 2023 14:09:18 +0100 Subject: [PATCH 04/20] avoiding test in remote --- tests/test_launcher.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index e393d5041c..95234d434f 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -499,17 +499,14 @@ def do_run(*args): @requires("click") +@requires("local") def test_launch_mapdl_cli(run_cli): output = run_cli("") - if os.environ.get("PYMAPDL_START_INSTANCE", "TRUE") == "FALSE": - # In remote - assert "There is an existing MAPDL instance at:" in output - else: - # In local - assert "Launched an MAPDL instance at" in output + # In local + assert "Launched an MAPDL instance at" in output - # grab ips and port - pid = int(output.splitlines()[1].split(":")[1].strip()) - p = psutil.Process(pid) - p.kill() + # grab ips and port + pid = int(output.splitlines()[1].split(":")[1].strip()) + p = psutil.Process(pid) + p.kill() From 79e2818b17b6eb2d8230fa1fe9d4934965980a37 Mon Sep 17 00:00:00 2001 From: German Date: Fri, 24 Nov 2023 16:37:13 +0100 Subject: [PATCH 05/20] attemp to fix test --- tests/test_launcher.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 95234d434f..d73940e7e5 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -487,11 +487,13 @@ def test__parse_ip_route(): @pytest.fixture def run_cli(): def do_run(*args): - args = ["pymapdl_convert_script"] + list(args) + args = ["launch_mapdl"] + list(args) proc = subprocess.Popen( args, - shell=True, + shell=os.name != "nt", + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ) return proc.stdout.read().decode().lower() From b615d5c42cd0acf8ff85627aa9a445e659c574be Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 Dec 2023 12:38:16 +0100 Subject: [PATCH 06/20] improving arguments passing and strings --- src/ansys/mapdl/core/cli.py | 139 ++++++++++++++++++++----------- src/ansys/mapdl/core/launcher.py | 6 +- 2 files changed, 93 insertions(+), 52 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index 5185b4fc96..cd604deeb5 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -1,5 +1,4 @@ import os -from warnings import warn try: import click @@ -150,8 +149,6 @@ def convert( f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." ) - from ansys.mapdl.core.launcher import launch_mapdl - @click.command( short_help="Launch MAPDL instances.", help="""This command aims to replicate the behavior of :func:`ansys.mapdl.core.launcher.launch_mapdl` @@ -172,7 +169,7 @@ def convert( ) @click.option( "--jobname", - default="", + default="file", type=str, help="MAPDL jobname. Defaults to ``'file'``.", ) @@ -237,13 +234,13 @@ def convert( "--ip", default=None, type=str, - help="Used only when ``start_instance`` is ``False``. If provided, it will force ``start_instance`` to be ``False``. Specify the IP address of the MAPDL instance to connect to. You can also provide a hostname as an alternative to an IP address. Defaults to ``'127.0.0.1'``. You can also override the default behavior of this keyword argument with the environment variable ``PYMAPDL_IP=``. This argument has priority over the environment variable.", + help="Argument not allowed in CLI. It will be ignored", ) @click.option( "--clear_on_connect", - default=True, + default=False, type=bool, - help="Defaults to ``True``, giving you a fresh environment when connecting to MAPDL. When if ``start_instance`` is specified it defaults to ``False``.", + help="Argument not allowed in CLI. It will be ignored", ) @click.option( "--log_apdl", @@ -260,7 +257,7 @@ def convert( @click.option( "--remove_temp_dir_on_exit", default=False, - type=str, + type=bool, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( @@ -271,9 +268,9 @@ def convert( ) @click.option( "--license_server_check", - default=True, + default=False, type=bool, - help="Check if the license server is available if MAPDL fails to start. Only available on ``mode='grpc'``. Defaults ``True``.", + help="Argument not allowed in CLI. It will be ignored.", ) @click.option( "--license_type", @@ -284,7 +281,7 @@ def convert( @click.option( "--print_com", default=False, - type=str, + type=bool, help="Argument not allowed in CLI. It will be ignored.", ) @click.option( @@ -311,96 +308,140 @@ def launch_mapdl( jobname, nproc, ram, - mode, + mode, # ignored override, - loglevel, + loglevel, # ignored additional_switches, start_timeout, port, - cleanup_on_exit, - start_instance, + cleanup_on_exit, # ignored + start_instance, # ignored ip, - clear_on_connect, - log_apdl, - remove_temp_files, - remove_temp_dir_on_exit, - verbose_mapdl, - license_server_check, + clear_on_connect, # ignored + log_apdl, # ignored + remove_temp_files, # ignored + remove_temp_dir_on_exit, # ignored + verbose_mapdl, # ignored + license_server_check, # ignored license_type, - print_com, + print_com, # ignored + add_env_vars, # ignored + replace_env_vars, # ignored version, ): from ansys.mapdl.core.launcher import launch_mapdl - if mode and mode != "grpc": - warn( - "Only gRPC mode is allowed when using CLI to launch MAPDL instances.\nIgnoring argument." + if mode: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'mode'.\nIgnoring argument." ) if loglevel: - warn( - "The following argument is not allowed in CLI: 'loglevel'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'loglevel'.\nIgnoring argument." ) if cleanup_on_exit: - warn( - "The following argument is not allowed in CLI: 'cleanup_on_exit'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'cleanup_on_exit'.\nIgnoring argument." ) if start_instance: - warn( - "The following argument is not allowed in CLI: 'start_instance'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'start_instance'.\nIgnoring argument." + ) + + if ip: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'ip'.\nIgnoring argument." + ) + + if clear_on_connect: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'clear_on_connect'.\nIgnoring argument." ) if log_apdl: - warn( - "The following argument is not allowed in CLI: 'log_apdl'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'log_apdl'.\nIgnoring argument." ) if remove_temp_files: - warn( - "The following argument is not allowed in CLI: 'remove_temp_files'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'remove_temp_files'.\nIgnoring argument." ) if remove_temp_dir_on_exit: - warn( - "The following argument is not allowed in CLI: 'remove_temp_dir_on_exit'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'remove_temp_dir_on_exit'.\nIgnoring argument." ) if verbose_mapdl: - warn( - "The following argument is not allowed in CLI: 'verbose_mapdl'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'verbose_mapdl'.\nIgnoring argument." ) if print_com: - warn( - "The following argument is not allowed in CLI: 'print_com'.\nIgnoring argument." + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'print_com'.\nIgnoring argument." ) - launch_mapdl( + if add_env_vars: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'add_env_vars'.\nIgnoring argument." + ) + + if replace_env_vars: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'replace_env_vars'.\nIgnoring argument." + ) + + if license_server_check: + click.echo( + click.style("Warn:", fg="yellow") + + " The following argument is not allowed in CLI: 'license_server_check'.\nIgnoring argument." + ) + + out = launch_mapdl( + exec_file=exec_file, just_launch=True, run_location=run_location, jobname=jobname, nproc=nproc, ram=ram, - mode=mode, override=override, additional_switches=additional_switches, start_timeout=start_timeout, port=port, - ip=ip, - clear_on_connect=clear_on_connect, license_server_check=license_server_check, license_type=license_type, - add_env_vars=add_env_vars, - replace_env_vars=replace_env_vars, version=version, ) + if len(out) == 3: + header = f"Launched an MAPDL instance (PID={out[2]}) at " + else: + header = "Launched an MAPDL instance at " + + click.echo(click.style("Success: ", fg="green") + header + f"{out[0]}:{out[1]}") + else: def convert(): - print("PyMAPDL CLI requires click to be installed.") + print("PyMAPDL CLI requires 'click' python package to be installed.") def launch_mapdl(): - print("PyMAPDL CLI requires click to be installed.") + print("PyMAPDL CLI requires 'click' python package to be installed.") diff --git a/src/ansys/mapdl/core/launcher.py b/src/ansys/mapdl/core/launcher.py index ab8751b12b..6006b2af52 100644 --- a/src/ansys/mapdl/core/launcher.py +++ b/src/ansys/mapdl/core/launcher.py @@ -1659,10 +1659,10 @@ def launch_mapdl( ) if just_launch: - print(f"Launched an MAPDL instance at: {ip}:{port}") + out = [ip, port] if hasattr(process, "pid"): - print(f"MAPDL instance PID: {process.pid}") - return + out += [process.pid] + return out mapdl = MapdlGrpc( ip=ip, From 9d30dc0cba1d943aa29c7255bb836923130e04ca Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 Dec 2023 12:39:05 +0100 Subject: [PATCH 07/20] adding tests --- tests/test_launcher.py | 62 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index d73940e7e5..03041f5f25 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -1,6 +1,7 @@ """Test the mapdl launcher""" import os +import re import subprocess import tempfile import weakref @@ -486,8 +487,12 @@ def test__parse_ip_route(): @pytest.fixture def run_cli(): - def do_run(*args): - args = ["launch_mapdl"] + list(args) + def do_run(arguments=""): + if arguments: + args = ["launch_mapdl"] + list(arguments.split(" ")) + else: + args = ["launch_mapdl"] + proc = subprocess.Popen( args, shell=os.name != "nt", @@ -495,7 +500,7 @@ def do_run(*args): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - return proc.stdout.read().decode().lower() + return proc.stdout.read().decode() return do_run @@ -503,12 +508,59 @@ def do_run(*args): @requires("click") @requires("local") def test_launch_mapdl_cli(run_cli): - output = run_cli("") + output = run_cli() # In local - assert "Launched an MAPDL instance at" in output + assert "Success: Launched an MAPDL instance " in output # grab ips and port pid = int(output.splitlines()[1].split(":")[1].strip()) p = psutil.Process(pid) p.kill() + + +@requires("click") +@requires("local") +def test_launch_mapdl_cli_config(run_cli): + cmds_ = ["--port 50090", "--jobname myjob"] + cmd_warnings = [ + "ip", + "license_server_check", + "mode", + "loglevel", + "cleanup_on_exit", + "start_instance", + "clear_on_connect", + "log_apdl", + "remove_temp_files", + "remove_temp_dir_on_exit", + "verbose_mapdl", + "print_com", + "add_env_vars", + "replace_env_vars", + ] + + cmd = " ".join(cmds_) + cmd_warnings_ = ["--" + each + " True" for each in cmd_warnings] + + cmd = cmd + " " + " ".join(cmd_warnings_) + + output = run_cli(cmd) + # In local + assert "Launched an MAPDL instance" in output + assert "50090" in output + + # assert warnings + for each in cmd_warnings: + assert ( + f"The following argument is not allowed in CLI: '{each}'" in output + ), f"Warning about '{each}' not printed" + + # grab ips and port + pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) + p = psutil.Process(pid) + cmdline = " ".join(p.cmdline()) + + assert "50090" in cmdline + assert "myjob" in cmdline + p.kill() From 847b11009c12e734ebbcba78b41cba356aa8ed91 Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 Dec 2023 17:20:33 +0100 Subject: [PATCH 08/20] Adding `stop` and `list`. Reog the cli. --- src/ansys/mapdl/core/cli.py | 212 +++++++++++++++++++++++++++++++++++- tests/test_launcher.py | 58 +++++++++- 2 files changed, 262 insertions(+), 8 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index cd604deeb5..d72cc99ec1 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -1,5 +1,8 @@ import os +import psutil +from tabulate import tabulate + try: import click @@ -149,7 +152,21 @@ def convert( f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." ) - @click.command( + class MyGroup(click.Group): + def invoke(self, ctx): + ctx.obj = tuple(ctx.args) + super(MyGroup, self).invoke(ctx) + + @click.group(invoke_without_command=True, cls=MyGroup) + @click.pass_context + def launch_mapdl(ctx): + args = ctx.obj + if ctx.invoked_subcommand is None: + from ansys.mapdl.core.cli import start + + start(args) + + @launch_mapdl.command( short_help="Launch MAPDL instances.", help="""This command aims to replicate the behavior of :func:`ansys.mapdl.core.launcher.launch_mapdl` @@ -302,7 +319,7 @@ def convert( type=str, help="Version of MAPDL to launch. If ``None``, the latest version is used. Versions can be provided as integers (i.e. ``version=222``) or floats (i.e. ``version=22.2``). To retrieve the available installed versions, use the function :meth:`ansys.tools.path.path.get_available_ansys_installations`.", ) - def launch_mapdl( + def start( exec_file, run_location, jobname, @@ -438,6 +455,194 @@ def launch_mapdl( click.echo(click.style("Success: ", fg="green") + header + f"{out[0]}:{out[1]}") + @launch_mapdl.command( + short_help="Stop MAPDL instances.", + help="""This command stop MAPDL instances running on a given port or with a given process id (PID). + +By default, it stops instances running on the port 50052.""", + ) + @click.option( + "--port", + default=None, + type=int, + help="Port where the MAPDL instance is running.", + ) + @click.option( + "--pid", + default=None, + type=int, + help="Process PID where the MAPDL instance is running.", + ) + @click.option( + "--all", + is_flag=True, + flag_value=True, + type=bool, + default=False, + help="Kill all MAPDL instances", + ) + def stop(port, pid, all): + if not pid and not port: + port = 50052 + + if port or all: + killed_ = False + for proc in psutil.process_iter(): + for conns in proc.connections(kind="inet"): + if (conns.laddr.port == port or all) and ( + "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() + ): + killed_ = True + try: + proc.kill() + except psutil.NoSuchProcess: + # Cases where the child process has already died. + pass + if all: + str_ = f"" + else: + str_ = f" running on port {port}" + + if not killed_: + click.echo( + click.style("ERROR: ", fg="red") + + f"No Ansys instances" + + str_ + + "have been found." + ) + else: + click.echo( + click.style("Success: ", fg="green") + + f"Ansys instances" + + str_ + + " have been stopped." + ) + return + + if pid: + try: + pid = int(pid) + except ValueError: + click.echo( + click.style("ERROR: ", fg="red") + + "PID provided could not be converted to int." + ) + + p = psutil.Process(pid) + for child in p.children(recursive=True): + child.kill() + p.kill() + + if p.status == "running": + click.echo( + click.style("ERROR: ", fg="red") + + f"The process with PID {pid} and its children could not be killed." + ) + else: + click.echo( + click.style("Success: ", fg="green") + + f"The process with PID {pid} and its children have been stopped." + ) + return + + @launch_mapdl.command( + short_help="List MAPDL instances.", + help="""This command list MAPDL instances""", + ) + @click.option( + "--instances", + "-i", + is_flag=True, + flag_value=True, + type=bool, + default=False, + help="Print only instances", + ) + @click.option( + "--long", + "-l", + is_flag=True, + flag_value=True, + type=bool, + default=False, + help="Print all info.", + ) + @click.option( + "--cmd", + "-c", + is_flag=True, + flag_value=True, + type=bool, + default=False, + help="Print cmd", + ) + @click.option( + "--location", + "-cwd", + is_flag=True, + flag_value=True, + type=bool, + default=False, + help="Print running location info.", + ) + def list(instances, long, cmd, location): + # Assuming all ansys processes have -grpc flag + mapdl_instances = [] + for proc in psutil.process_iter(): + if ( + "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() + ) and "-grpc" in proc.cmdline(): + if len(proc.children(recursive=True)) < 2: + proc.ansys_instance = False + else: + proc.ansys_instance = True + mapdl_instances.append(proc) + + # printing + table = [] + + if long: + cmd = True + location = True + + if instances: + headers = ["Name", "Status", "gRPC port", "PID"] + else: + headers = ["Name", "Is Instance", "Status", "gRPC port", "PID"] + + if cmd: + headers.append("Command line") + if location: + headers.append("Working directory") + + def get_port(proc): + cmdline = proc.cmdline() + ind_grpc = cmdline.index("-port") + return cmdline[ind_grpc + 1] + + table = [] + for each_p in mapdl_instances: + if instances and not each_p.ansys_instance: + continue + + proc_line = [] + proc_line.append(each_p.name()) + + if not instances: + proc_line.append(each_p.ansys_instance) + + proc_line.extend([each_p.status(), get_port(each_p), each_p.pid]) + + if cmd: + proc_line.append(" ".join(each_p.cmdline())) + + if location: + proc_line.append(each_p.cwd()) + + table.append(proc_line) + + print(tabulate(table, headers)) + else: def convert(): @@ -445,3 +650,6 @@ def convert(): def launch_mapdl(): print("PyMAPDL CLI requires 'click' python package to be installed.") + + def stop_mapdl(): + print("PyMAPDL CLI requires 'click' python package to be installed.") diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 03041f5f25..6bf607a21d 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -514,15 +514,22 @@ def test_launch_mapdl_cli(run_cli): assert "Success: Launched an MAPDL instance " in output # grab ips and port - pid = int(output.splitlines()[1].split(":")[1].strip()) - p = psutil.Process(pid) - p.kill() + pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) + + output = run_cli(f"stop --pid {pid}") + + try: + p = psutil.Process(pid) + assert not p.status() + except: + # An exception means the process is dead? + pass @requires("click") @requires("local") def test_launch_mapdl_cli_config(run_cli): - cmds_ = ["--port 50090", "--jobname myjob"] + cmds_ = ["start", "--port 50090", "--jobname myjob"] cmd_warnings = [ "ip", "license_server_check", @@ -546,7 +553,7 @@ def test_launch_mapdl_cli_config(run_cli): cmd = cmd + " " + " ".join(cmd_warnings_) output = run_cli(cmd) - # In local + assert "Launched an MAPDL instance" in output assert "50090" in output @@ -563,4 +570,43 @@ def test_launch_mapdl_cli_config(run_cli): assert "50090" in cmdline assert "myjob" in cmdline - p.kill() + + run_cli(f"stop --pid {pid}") + + +@requires("click") +@requires("local") +def test_launch_mapdl_cli_list(run_cli): + output = run_cli("list") + assert "running" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -i") + assert "running" in output + assert "Is Instance" not in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -c") + assert "running" in output + assert "Command line" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -cwd") + assert "running" in output + assert "Command line" not in output + assert "Working directory" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -l") + assert "running" in output + assert "Is Instance" in output + assert "Command line" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() From bdfc3d5567e631b75927fe1943e27c60acb75dd3 Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 Dec 2023 18:32:33 +0100 Subject: [PATCH 09/20] Adding dependencies --- pyproject.toml | 1 + src/ansys/mapdl/core/cli.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2932bb7ce4..3ed02af8c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "ansys-platform-instancemanagement~=1.0", "platformdirs>=3.6.0", "click>=8.1.3", # for CLI interface + "tabulate>=0.8.0", # for cli plotting "grpcio>=1.30.0", # tested up to grpcio==1.35 "importlib-metadata>=4.0", "matplotlib>=3.0.0", # for colormaps for pyvista diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index d72cc99ec1..ebe9526009 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -1,10 +1,10 @@ import os import psutil -from tabulate import tabulate try: import click + from tabulate import tabulate _HAS_CLICK = True except ModuleNotFoundError: From 773ddb7dd10a7f4283fc62ee4f37a8e1f8f582bf Mon Sep 17 00:00:00 2001 From: German Date: Thu, 21 Dec 2023 18:34:24 +0100 Subject: [PATCH 10/20] adding docs --- doc/source/getting_started/cli.rst | 276 ++++++++++++++++++++++++ doc/source/getting_started/index.rst | 1 + doc/source/getting_started/launcher.rst | 3 + doc/source/user_guide/convert.rst | 2 + 4 files changed, 282 insertions(+) create mode 100644 doc/source/getting_started/cli.rst diff --git a/doc/source/getting_started/cli.rst b/doc/source/getting_started/cli.rst new file mode 100644 index 0000000000..d3b1a5208e --- /dev/null +++ b/doc/source/getting_started/cli.rst @@ -0,0 +1,276 @@ + +.. _ref_cli: + +============================== +PyMAPDL command line interface +============================== + +For your convenience, PyMAPDL package includes a command line interface +which allows you to launch, stop and list local MAPDL instances. + + +Launch MAPDL instances +====================== + +To start MAPDL, just type on your activated virtual environment: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl + Success: Launched an MAPDL instance (PID=23644) at 127.0.0.1:50052 + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl + Success: Launched an MAPDL instance (PID=23644) at 127.0.0.1:50052 + +If you want to specify an argument, for instance the port, then you need to call +`launch_mapdl start`: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl start --port 50054 + Success: Launched an MAPDL instance (PID=18238) at 127.0.0.1:50054 + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl start --port 50054 + Success: Launched an MAPDL instance (PID=18238) at 127.0.0.1:50054 + + +This command `launch_mapdl start` aims to replicate the function +:func:`ansys.mapdl.core.launcher.launch_mapdl`, hence you can use +some of the arguments which this function allows. +For instance, you could specify the working directory: + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl start --run_location C:\Users\user\temp\ + Success: Launched an MAPDL instance (PID=32612) at 127.0.0.1:50052 + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl start --run_location /home/user/tmp + Success: Launched an MAPDL instance (PID=32612) at 127.0.0.1:50052 + + +For more information see :func:`ansys.mapdl.core.launcher.launch_mapdl`, +and :func:`ansys.mapdl.core.cli.launch_mapdl` + + +Stop MAPDL instances +==================== +MAPDL instances can be stopped by using `launch_mapdl stop` command in the following +way: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl stop + Success: Ansys instances running on port 50052 have been stopped. + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl stop + Success: Ansys instances running on port 50052 have been stopped. + + +By default, the instance running on the port `50052` is stopped. + +You can specify the instance running on a different port using `--port` argument: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl stop --port 50053 + Success: Ansys instances running on port 50053 have been stopped. + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl stop --port 50053 + Success: Ansys instances running on port 50053 have been stopped. + + +Or an instance with a given process id (PID): + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl stop --pid 40952 + Success: The process with PID 40952 and its children have been stopped. + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl stop --pid 40952 + Success: The process with PID 40952 and its children have been stopped. + + +Alternatively, you can stop all the running instances by using: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl stop --all + Success: Ansys instances have been stopped. + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl stop --all + Success: Ansys instances have been stopped. + + +List MAPDL instances and processes +================================== + +You can also list MAPDL instances and processes. +If you want to list MAPDL process, just use the following command: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl list + Name Is Instance Status gRPC port PID + ------------ ------------- -------- ----------- ----- + ANSYS.exe False running 50052 35360 + ANSYS.exe False running 50052 37116 + ANSYS222.exe True running 50052 41644 + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl list + Name Is Instance Status gRPC port PID + ------------ ------------- -------- ----------- ----- + ANSYS.exe False running 50052 35360 + ANSYS.exe False running 50052 37116 + ANSYS222.exe True running 50052 41644 + + +If you want, to just list the instances (avoiding listing children MAPDL +processes), just type: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl list -i + Name Status gRPC port PID + ------------ -------- ----------- ----- + ANSYS222.exe running 50052 41644 + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl list -i + Name Status gRPC port PID + ------------ -------- ----------- ----- + ANSYS222.exe running 50052 41644 + + +You can also print other fields like the working directory (using `--cwd`) +or the command line (using `-c`). +Additionally, you can also print all the available information by using the +argument `--long` or `-l`: + + +.. tab-set:: + + .. tab-item:: Windows + :sync: key1 + + .. code:: pwsh-session + + (.venv) PS C:\Users\user\pymapdl> launch_mapdl list -l + Name Is Instance Status gRPC port PID Command line Working directory + ------------ ------------- -------- ----------- ----- -------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------- + ANSYS.exe False running 50052 35360 C:\Program Files\ANSYS Inc\v222\ANSYS\bin\winx64\ANSYS.EXE -j file -b -i .__tmp__.inp -o .__tmp__.out -port 50052 -grpc C:\Users\User\AppData\Local\Temp\ansys_ahmfaliakp + ANSYS.exe False running 50052 37116 C:\Program Files\ANSYS Inc\v222\ANSYS\bin\winx64\ANSYS.EXE -j file -b -i .__tmp__.inp -o .__tmp__.out -port 50052 -grpc C:\Users\User\AppData\Local\Temp\ansys_ahmfaliakp + ANSYS222.exe True running 50052 41644 C:\Program Files\ANSYS Inc\v222\ansys\bin\winx64\ansys222.exe -j file -np 2 -b -i .__tmp__.inp -o .__tmp__.out -port 50052 -grpc C:\Users\User\AppData\Local\Temp\ansys_ahmfaliakp + + .. tab-item:: Linux + :sync: key1 + + .. code:: console + + (.venv) user@machine:~$ launch_mapdl list -l + Name Is Instance Status gRPC port PID Command line Working directory + ------------ ------------- -------- ----------- ----- ------------------------------------------------------------------------- -------------------------------- + ANSYS False running 50052 35360 /ansys_inc/v222/ansys/bin/linx64/ansys -j file -port 50052 -grpc /home/user/temp/ansys_ahmfaliakp + ANSYS False running 50052 37116 /ansys_inc/v222/ansys/bin/linx64/ansys -j file -port 50052 -grpc /home/user/temp/ansys_ahmfaliakp + ANSYS222 True running 50052 41644 /ansys_inc/v222/ansys/bin/linx64/ansys222 -j file -np 2 -port 50052 -grpc /home/user/temp/ansys_ahmfaliakp + + +The converter module has its own command line interface to convert +MAPDL files to PyMAPDL. For more information, see +:ref:`ref_cli_converter`. \ No newline at end of file diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index 5936e3dbd9..5eb934e06a 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -11,6 +11,7 @@ contribution faq versioning + cli docker macos wsl diff --git a/doc/source/getting_started/launcher.rst b/doc/source/getting_started/launcher.rst index df173b2433..0d8ae78403 100644 --- a/doc/source/getting_started/launcher.rst +++ b/doc/source/getting_started/launcher.rst @@ -90,6 +90,9 @@ port 50005 with this command: /usr/ansys_inc/v241/ansys/bin/ansys211 -port 50005 -grpc +From version v0.68, you can use a command line interface to launch, stop and list +local MAPDL instances. +For more information, see :ref:`ref_cli`. .. _connect_grpc_madpl_session: diff --git a/doc/source/user_guide/convert.rst b/doc/source/user_guide/convert.rst index d7d34a050e..b0155e2924 100644 --- a/doc/source/user_guide/convert.rst +++ b/doc/source/user_guide/convert.rst @@ -8,6 +8,8 @@ would take place within Python because APDL commands are less transparent and more difficult to debug. +.. _ref_cli_converter: + Command-line interface ---------------------- From 5ee4d227864439be4a2c095cc795c3c9e79295fc Mon Sep 17 00:00:00 2001 From: German Date: Tue, 26 Dec 2023 16:23:09 +0100 Subject: [PATCH 11/20] some codecov complains --- src/ansys/mapdl/core/cli.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index ebe9526009..78d6f0a3e5 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -481,15 +481,15 @@ def start( default=False, help="Kill all MAPDL instances", ) - def stop(port, pid, all): + def stop(port, pid, all_ports): if not pid and not port: port = 50052 - if port or all: + if port or all_ports: killed_ = False for proc in psutil.process_iter(): for conns in proc.connections(kind="inet"): - if (conns.laddr.port == port or all) and ( + if (conns.laddr.port == port or all_ports) and ( "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() ): killed_ = True @@ -498,22 +498,22 @@ def stop(port, pid, all): except psutil.NoSuchProcess: # Cases where the child process has already died. pass - if all: - str_ = f"" + if all_ports: + str_ = "" else: str_ = f" running on port {port}" if not killed_: click.echo( click.style("ERROR: ", fg="red") - + f"No Ansys instances" + + "No Ansys instances" + str_ + "have been found." ) else: click.echo( click.style("Success: ", fg="green") - + f"Ansys instances" + + "Ansys instances" + str_ + " have been stopped." ) From 78faa6e8da71bbf2d624ad3d476a562873ff28d2 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 26 Dec 2023 16:51:15 +0100 Subject: [PATCH 12/20] Using click pytest --- tests/test_launcher.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index ce50178662..511d41c3c6 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -2,7 +2,6 @@ import os import re -import subprocess import tempfile import psutil @@ -470,19 +469,20 @@ def test_launched(mapdl): @pytest.fixture def run_cli(): def do_run(arguments=""): + from click.testing import CliRunner + + from ansys.mapdl.core.cli import launch_mapdl + if arguments: - args = ["launch_mapdl"] + list(arguments.split(" ")) + args = list(arguments.split(" ")) else: - args = ["launch_mapdl"] - - proc = subprocess.Popen( - args, - shell=os.name != "nt", - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - return proc.stdout.read().decode() + args = [] + + runner = CliRunner() + result = runner.invoke(launch_mapdl, args) + + assert result.exit_code == 0 + return result.output return do_run From bceef0853ea837ae3db700385b9e7789e4e4cdf2 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 26 Dec 2023 17:35:16 +0100 Subject: [PATCH 13/20] skipping tests on student --- tests/test_launcher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 84122e9cdc..3ed6e0556f 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -501,6 +501,7 @@ def do_run(arguments=""): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli(run_cli): output = run_cli() @@ -522,6 +523,7 @@ def test_launch_mapdl_cli(run_cli): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli_config(run_cli): cmds_ = ["start", "--port 50090", "--jobname myjob"] cmd_warnings = [ @@ -570,6 +572,7 @@ def test_launch_mapdl_cli_config(run_cli): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli_list(run_cli): output = run_cli("list") assert "running" in output From e3f96470c4e3f6f9886b2f465462f5b74ba84fa2 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 2 Jan 2024 08:56:55 +0100 Subject: [PATCH 14/20] moving test to test_cli --- tests/conftest.py | 9 ++- tests/test_cli.py | 135 +++++++++++++++++++++++++++++++++++++++++ tests/test_launcher.py | 130 --------------------------------------- 3 files changed, 143 insertions(+), 131 deletions(-) create mode 100644 tests/test_cli.py diff --git a/tests/conftest.py b/tests/conftest.py index 7b6d00ac09..d7ccee7925 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ from collections import namedtuple -from importlib import import_module import os from pathlib import Path from sys import platform @@ -83,6 +82,14 @@ ) +def import_module(requirement): + from importlib import import_module + + if os.name == "nt": + requirement = requirement.replace("-", ".") + return import_module(requirement) + + def has_dependency(requirement): try: import_module(requirement) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000000..78161adc8b --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,135 @@ +import os +import re +import subprocess + +import psutil +import pytest + +from conftest import requires + + +@pytest.fixture +def run_cli(): + def do_run(arguments=""): + if arguments: + args = ["launch_mapdl"] + list(arguments.split(" ")) + else: + args = ["launch_mapdl"] + + proc = subprocess.Popen( + args, + shell=os.name != "nt", + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return proc.stdout.read().decode() + + return do_run + + +@requires("click") +@requires("local") +def test_launch_mapdl_cli(run_cli): + output = run_cli() + + # In local + assert "Success: Launched an MAPDL instance " in output + + # grab ips and port + pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) + + output = run_cli(f"stop --pid {pid}") + + try: + p = psutil.Process(pid) + assert not p.status() + except: + # An exception means the process is dead? + pass + + +@requires("click") +@requires("local") +def test_launch_mapdl_cli_config(run_cli): + cmds_ = ["start", "--port 50090", "--jobname myjob"] + cmd_warnings = [ + "ip", + "license_server_check", + "mode", + "loglevel", + "cleanup_on_exit", + "start_instance", + "clear_on_connect", + "log_apdl", + "remove_temp_files", + "remove_temp_dir_on_exit", + "verbose_mapdl", + "print_com", + "add_env_vars", + "replace_env_vars", + ] + + cmd = " ".join(cmds_) + cmd_warnings_ = ["--" + each + " True" for each in cmd_warnings] + + cmd = cmd + " " + " ".join(cmd_warnings_) + + output = run_cli(cmd) + + assert "Launched an MAPDL instance" in output + assert "50090" in output + + # assert warnings + for each in cmd_warnings: + assert ( + f"The following argument is not allowed in CLI: '{each}'" in output + ), f"Warning about '{each}' not printed" + + # grab ips and port + pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) + p = psutil.Process(pid) + cmdline = " ".join(p.cmdline()) + + assert "50090" in cmdline + assert "myjob" in cmdline + + run_cli(f"stop --pid {pid}") + + +@requires("click") +@requires("local") +def test_launch_mapdl_cli_list(run_cli): + output = run_cli("list") + assert "running" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -i") + assert "running" in output + assert "Is Instance" not in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -c") + assert "running" in output + assert "Command line" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -cwd") + assert "running" in output + assert "Command line" not in output + assert "Working directory" in output + assert "Is Instance" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() + + output = run_cli("list -l") + assert "running" in output + assert "Is Instance" in output + assert "Command line" in output + assert len(output.splitlines()) > 2 + assert "ansys" in output.lower() or "mapdl" in output.lower() diff --git a/tests/test_launcher.py b/tests/test_launcher.py index ce50178662..8bb17281f6 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -1,11 +1,8 @@ """Test the mapdl launcher""" import os -import re -import subprocess import tempfile -import psutil import pytest from ansys.mapdl import core as pymapdl @@ -465,130 +462,3 @@ def test_launched(mapdl): assert mapdl.launched else: assert not mapdl.launched - - -@pytest.fixture -def run_cli(): - def do_run(arguments=""): - if arguments: - args = ["launch_mapdl"] + list(arguments.split(" ")) - else: - args = ["launch_mapdl"] - - proc = subprocess.Popen( - args, - shell=os.name != "nt", - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - return proc.stdout.read().decode() - - return do_run - - -@requires("click") -@requires("local") -def test_launch_mapdl_cli(run_cli): - output = run_cli() - - # In local - assert "Success: Launched an MAPDL instance " in output - - # grab ips and port - pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) - - output = run_cli(f"stop --pid {pid}") - - try: - p = psutil.Process(pid) - assert not p.status() - except: - # An exception means the process is dead? - pass - - -@requires("click") -@requires("local") -def test_launch_mapdl_cli_config(run_cli): - cmds_ = ["start", "--port 50090", "--jobname myjob"] - cmd_warnings = [ - "ip", - "license_server_check", - "mode", - "loglevel", - "cleanup_on_exit", - "start_instance", - "clear_on_connect", - "log_apdl", - "remove_temp_files", - "remove_temp_dir_on_exit", - "verbose_mapdl", - "print_com", - "add_env_vars", - "replace_env_vars", - ] - - cmd = " ".join(cmds_) - cmd_warnings_ = ["--" + each + " True" for each in cmd_warnings] - - cmd = cmd + " " + " ".join(cmd_warnings_) - - output = run_cli(cmd) - - assert "Launched an MAPDL instance" in output - assert "50090" in output - - # assert warnings - for each in cmd_warnings: - assert ( - f"The following argument is not allowed in CLI: '{each}'" in output - ), f"Warning about '{each}' not printed" - - # grab ips and port - pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0]) - p = psutil.Process(pid) - cmdline = " ".join(p.cmdline()) - - assert "50090" in cmdline - assert "myjob" in cmdline - - run_cli(f"stop --pid {pid}") - - -@requires("click") -@requires("local") -def test_launch_mapdl_cli_list(run_cli): - output = run_cli("list") - assert "running" in output - assert "Is Instance" in output - assert len(output.splitlines()) > 2 - assert "ansys" in output.lower() or "mapdl" in output.lower() - - output = run_cli("list -i") - assert "running" in output - assert "Is Instance" not in output - assert len(output.splitlines()) > 2 - assert "ansys" in output.lower() or "mapdl" in output.lower() - - output = run_cli("list -c") - assert "running" in output - assert "Command line" in output - assert "Is Instance" in output - assert len(output.splitlines()) > 2 - assert "ansys" in output.lower() or "mapdl" in output.lower() - - output = run_cli("list -cwd") - assert "running" in output - assert "Command line" not in output - assert "Working directory" in output - assert "Is Instance" in output - assert len(output.splitlines()) > 2 - assert "ansys" in output.lower() or "mapdl" in output.lower() - - output = run_cli("list -l") - assert "running" in output - assert "Is Instance" in output - assert "Command line" in output - assert len(output.splitlines()) > 2 - assert "ansys" in output.lower() or "mapdl" in output.lower() From 256d0938d235475efc4daa542f7c1b3264993ba9 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 2 Jan 2024 09:28:25 +0100 Subject: [PATCH 15/20] Avoiding tests in nonstudent (not enough licenses) --- tests/test_cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index b3d0ceadf0..6ada1cc2ed 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,6 +8,7 @@ @pytest.fixture @requires("click") +@requires("nostudent") def run_cli(): def do_run(arguments=""): from click.testing import CliRunner @@ -30,6 +31,7 @@ def do_run(arguments=""): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli(run_cli): output = run_cli() @@ -51,6 +53,7 @@ def test_launch_mapdl_cli(run_cli): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli_config(run_cli): cmds_ = ["start", "--port 50090", "--jobname myjob"] cmd_warnings = [ @@ -99,6 +102,7 @@ def test_launch_mapdl_cli_config(run_cli): @requires("click") @requires("local") +@requires("nostudent") def test_launch_mapdl_cli_list(run_cli): output = run_cli("list") assert "running" in output From ae68fbcc1e3a26d77ac5c89bbb83e47960dddee3 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 2 Jan 2024 09:42:51 +0100 Subject: [PATCH 16/20] fixing tests --- src/ansys/mapdl/core/cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index 78d6f0a3e5..dda49ed6b1 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -481,15 +481,15 @@ def start( default=False, help="Kill all MAPDL instances", ) - def stop(port, pid, all_ports): + def stop(port, pid, all): if not pid and not port: port = 50052 - if port or all_ports: + if port or all: killed_ = False for proc in psutil.process_iter(): for conns in proc.connections(kind="inet"): - if (conns.laddr.port == port or all_ports) and ( + if (conns.laddr.port == port or all) and ( "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() ): killed_ = True @@ -498,7 +498,7 @@ def stop(port, pid, all_ports): except psutil.NoSuchProcess: # Cases where the child process has already died. pass - if all_ports: + if all: str_ = "" else: str_ = f" running on port {port}" From ce7fce3fd67ab5d35b63a9ebf02dc60cfc8ba183 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:50:12 +0000 Subject: [PATCH 17/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/ansys/mapdl/core/cli.py | 22 ++++++++++++++++++++++ tests/test_cli.py | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index dda49ed6b1..d32030c87a 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -1,3 +1,25 @@ +# Copyright (C) 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. + import os import psutil diff --git a/tests/test_cli.py b/tests/test_cli.py index 6ada1cc2ed..a60508e259 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,25 @@ +# Copyright (C) 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. + import re import psutil From a9045309bc76dbcd81723a8c22ec08caf5e28905 Mon Sep 17 00:00:00 2001 From: German Date: Thu, 4 Jan 2024 10:28:23 +0100 Subject: [PATCH 18/20] Making sure we kill processes even if they do not have connections going on. Also making sure we do not kill ansys processes without the '-grpc' flag. --- src/ansys/mapdl/core/cli.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index d32030c87a..f2d112d194 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -174,6 +174,11 @@ def convert( f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." ) + def is_ansys_process(proc): + return ( + "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() + ) and "-grpc" in proc.cmdline() + class MyGroup(click.Group): def invoke(self, ctx): ctx.obj = tuple(ctx.args) @@ -510,16 +515,17 @@ def stop(port, pid, all): if port or all: killed_ = False for proc in psutil.process_iter(): - for conns in proc.connections(kind="inet"): - if (conns.laddr.port == port or all) and ( - "ansys" in proc.name().lower() or "mapdl" in proc.name().lower() - ): + if is_ansys_process(proc): + # Killing "all" + if all: + proc.kill() killed_ = True - try: + + # Killing by ports + for conns in proc.connections(kind="inet"): + if conns.laddr.port == port: proc.kill() - except psutil.NoSuchProcess: - # Cases where the child process has already died. - pass + killed_ = True if all: str_ = "" else: @@ -530,7 +536,7 @@ def stop(port, pid, all): click.style("ERROR: ", fg="red") + "No Ansys instances" + str_ - + "have been found." + + " have been found." ) else: click.echo( From 2af43a09c3c5233b5faac64fdc8795abe145bf7b Mon Sep 17 00:00:00 2001 From: German Date: Thu, 4 Jan 2024 16:43:29 +0100 Subject: [PATCH 19/20] Adding back exception --- src/ansys/mapdl/core/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index f2d112d194..959691196e 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -518,8 +518,11 @@ def stop(port, pid, all): if is_ansys_process(proc): # Killing "all" if all: - proc.kill() - killed_ = True + try: + proc.kill() + killed_ = True + except psutil.NoSuchProcess: + pass # Killing by ports for conns in proc.connections(kind="inet"): From a6852aa9ac5a23c5c32e21e52e5252cdff79e980 Mon Sep 17 00:00:00 2001 From: German Date: Mon, 8 Jan 2024 11:03:39 +0100 Subject: [PATCH 20/20] fixing not being able to kill instance until port was already listening. --- src/ansys/mapdl/core/cli.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ansys/mapdl/core/cli.py b/src/ansys/mapdl/core/cli.py index 959691196e..04ae369e25 100644 --- a/src/ansys/mapdl/core/cli.py +++ b/src/ansys/mapdl/core/cli.py @@ -515,7 +515,7 @@ def stop(port, pid, all): if port or all: killed_ = False for proc in psutil.process_iter(): - if is_ansys_process(proc): + if psutil.pid_exists(proc.pid) and is_ansys_process(proc): # Killing "all" if all: try: @@ -524,11 +524,15 @@ def stop(port, pid, all): except psutil.NoSuchProcess: pass - # Killing by ports - for conns in proc.connections(kind="inet"): - if conns.laddr.port == port: - proc.kill() - killed_ = True + else: + # Killing by ports + if str(port) in proc.cmdline(): + try: + proc.kill() + killed_ = True + except psutil.NoSuchProcess: + pass + if all: str_ = "" else: @@ -539,7 +543,8 @@ def stop(port, pid, all): click.style("ERROR: ", fg="red") + "No Ansys instances" + str_ - + " have been found." + + " have been found.\n" + + "If you are sure there are MAPDL " ) else: click.echo(