Skip to content

Commit

Permalink
feat: ansys-mechanical-ideconfig command (#935)
Browse files Browse the repository at this point in the history
Co-authored-by: pyansys-ci-bot <[email protected]>
  • Loading branch information
klmcadams and pyansys-ci-bot authored Oct 10, 2024
1 parent a4537bf commit f4ee162
Show file tree
Hide file tree
Showing 4 changed files with 333 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/changelog.d/935.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`ansys-mechanical-ideconfig` command
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ viz = [

[project.scripts]
ansys-mechanical = "ansys.mechanical.core.run:cli"
ansys-mechanical-ideconfig = "ansys.mechanical.core.ide_config:cli"

[tool.flit.module]
name = "ansys.mechanical.core"
Expand Down
164 changes: 164 additions & 0 deletions src/ansys/mechanical/core/ide_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Copyright (C) 2022 - 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.

"""Convenience CLI to run mechanical."""

import json
import os
from pathlib import Path
import sys
import sysconfig

import ansys.tools.path as atp
import click


def _vscode_impl(
target: str = "user",
revision: int = None,
):
"""Get the IDE configuration for autocomplete in VS Code.
Parameters
----------
target: str
The type of settings to update. Either "user" or "workspace" in VS Code.
By default, it's ``user``.
revision: int
The Mechanical revision number. For example, "242".
If unspecified, it finds the default Mechanical version from ansys-tools-path.
"""
# Update the user or workspace settings
if target == "user":
# Get the path to the user's settings.json file depending on the platform
if "win" in sys.platform:
settings_json = (
Path(os.environ.get("APPDATA")) / "Code" / "User" / "settings.json"
) # pragma: no cover
elif "lin" in sys.platform:
settings_json = (
Path(os.environ.get("HOME")) / ".config" / "Code" / "User" / "settings.json"
)
elif target == "workspace":
# Get the current working directory
current_dir = Path.cwd()
# Get the path to the settings.json file based on the git root & .vscode folder
settings_json = current_dir / ".vscode" / "settings.json"

# Location where the stubs are installed -> .venv/Lib/site-packages, for example
stubs_location = (
Path(sysconfig.get_paths()["purelib"]) / "ansys" / "mechanical" / "stubs" / f"v{revision}"
)

# The settings to add to settings.json for autocomplete to work
settings_json_data = {
"python.autoComplete.extraPaths": [str(stubs_location)],
"python.analysis.extraPaths": [str(stubs_location)],
}
# Pretty print dictionary
pretty_dict = json.dumps(settings_json_data, indent=4)

print(f"Update {settings_json} with the following information:\n")

if target == "workspace":
print(
"Note: Please ensure the .vscode folder is in the root of your project or repository.\n"
)

print(pretty_dict)


def _cli_impl(
ide: str = "vscode",
target: str = "user",
revision: int = None,
):
"""Provide the user with the path to the settings.json file and IDE settings.
Parameters
----------
ide: str
The IDE to set up autocomplete settings. By default, it's ``vscode``.
target: str
The type of settings to update. Either "user" or "workspace" in VS Code.
By default, it's ``user``.
revision: int
The Mechanical revision number. For example, "242".
If unspecified, it finds the default Mechanical version from ansys-tools-path.
"""
# Check the IDE and raise an exception if it's not VS Code
if ide == "vscode":
return _vscode_impl(target, revision)
else:
raise Exception(f"{ide} is not supported at the moment.")


@click.command()
@click.help_option("--help", "-h")
@click.option(
"--ide",
default="vscode",
type=str,
help="The IDE being used.",
)
@click.option(
"--target",
default="user",
type=str,
help="The type of settings to update - either ``user`` or ``workspace`` settings.",
)
@click.option(
"--revision",
default=None,
type=int,
help='The Mechanical revision number, e.g. "242" or "241". If unspecified,\
it finds and uses the default version from ansys-tools-path.',
)
def cli(ide: str, target: str, revision: int) -> None:
"""CLI tool to update settings.json files for autocomplete with ansys-mechanical-stubs.
Parameters
----------
ide: str
The IDE to set up autocomplete settings. By default, it's ``vscode``.
target: str
The type of settings to update. Either "user" or "workspace" in VS Code.
By default, it's ``user``.
revision: int
The Mechanical revision number. For example, "242".
If unspecified, it finds the default Mechanical version from ansys-tools-path.
Usage
-----
The following example demonstrates the main use of this tool:
$ ansys-mechanical-ideconfig --ide vscode --location user --revision 242
"""
exe = atp.get_mechanical_path(allow_input=False, version=revision)
version = atp.version_from_path("mechanical", exe)

return _cli_impl(
ide,
target,
version,
)
167 changes: 167 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@
# SOFTWARE.

import os
from pathlib import Path
import subprocess
import sys
import sysconfig

import pytest

from ansys.mechanical.core.ide_config import _cli_impl as ideconfig_cli_impl
from ansys.mechanical.core.run import _cli_impl


Expand Down Expand Up @@ -229,3 +234,165 @@ def test_cli_batch_required_args(disable_cli):
_cli_impl(exe="AnsysWBU.exe", version=241, port=11)
except Exception as e:
assert False, f"cli raised an exception: {e}"


def get_settings_location() -> str:
"""Get the location of settings.json for user settings.
Returns
-------
str
The path to the settings.json file for users on Windows and Linux.
"""
if "win" in sys.platform:
settings_json = Path(os.environ.get("APPDATA")) / "Code" / "User" / "settings.json"
elif "lin" in sys.platform:
settings_json = Path(os.environ.get("HOME")) / ".config" / "Code" / "User" / "settings.json"

return settings_json


def get_stubs_location(revision: int) -> Path:
"""Get the ansys-mechanical-stubs location with specified revision.
Parameters
----------
revision: int
The Mechanical revision number. For example, "242".
Returns
-------
pathlib.Path
The path to the ansys-mechanical-stubs installation.
"""
return (
Path(sysconfig.get_paths()["purelib"]) / "ansys" / "mechanical" / "stubs" / f"v{revision}"
)


@pytest.mark.cli
def test_ideconfig_cli_ide_exception(capfd):
"""Test IDE configuration raises an exception for anything but vscode."""
with pytest.raises(Exception):
ideconfig_cli_impl(
ide="pycharm",
target="user",
revision=242,
)


@pytest.mark.cli
def test_ideconfig_cli_user_settings(capfd):
"""Test the IDE configuration prints correct information for user settings."""
# Set the revision number
revision = 242

# Run the IDE configuration command for the user settings type
ideconfig_cli_impl(
ide="vscode",
target="user",
revision=revision,
)

# Get output of the IDE configuration command
out, err = capfd.readouterr()
out = out.replace("\\\\", "\\")

# Get the path to the settings.json file based on operating system env vars
settings_json = get_settings_location()
stubs_location = get_stubs_location(revision)

assert f"Update {settings_json} with the following information" in out
assert str(stubs_location) in out


@pytest.mark.cli
def test_ideconfig_cli_workspace_settings(capfd):
"""Test the IDE configuration prints correct information for workplace settings."""
# Set the revision number
revision = 241

# Run the IDE configuration command
ideconfig_cli_impl(
ide="vscode",
target="workspace",
revision=revision,
)

# Get output of the IDE configuration command
out, err = capfd.readouterr()
out = out.replace("\\\\", "\\")

# Get the path to the settings.json file based on the current directory & .vscode folder
settings_json = Path.cwd() / ".vscode" / "settings.json"
stubs_location = get_stubs_location(revision)

# Assert the correct settings.json file and stubs location is in the output
assert f"Update {settings_json} with the following information" in out
assert str(stubs_location) in out
assert "Please ensure the .vscode folder is in the root of your project or repository" in out


@pytest.mark.cli
@pytest.mark.python_env
def test_ideconfig_venv(test_env, run_subprocess, rootdir):
"""Test the IDE configuration location when a virtual environment is active."""
# Set the revision number
revision = 242

# Install pymechanical
subprocess.check_call(
[test_env.python, "-m", "pip", "install", "-e", "."],
cwd=rootdir,
env=test_env.env,
)

# Run ansys-mechanical-ideconfig in the test virtual environment
process, stdout, stderr = run_subprocess(
[
"ansys-mechanical-ideconfig",
"--ide",
"vscode",
"--target",
"user",
"--revision",
str(revision),
],
env=test_env.env,
)
# Decode stdout and fix extra backslashes in paths
stdout = stdout.decode().replace("\\\\", "\\")

# Assert virtual environment is in the stdout
assert ".test_env" in stdout

Check failure on line 367 in tests/test_cli.py

View workflow job for this annotation

GitHub Actions / Test Report 3.11

test_cli.test_ideconfig_venv

AssertionError: assert '.test_env' in ''
Raw output
test_env = <conftest.test_env.<locals>.TestEnv object at 0x7f7576ca3a50>
run_subprocess = <function run_subprocess.<locals>.func at 0x7f7576c96f20>
rootdir = PosixPath('/__w/pymechanical/pymechanical')

    @pytest.mark.cli
    @pytest.mark.python_env
    def test_ideconfig_venv(test_env, run_subprocess, rootdir):
        """Test the IDE configuration location when a virtual environment is active."""
        # Set the revision number
        revision = 242
    
        # Install pymechanical
        subprocess.check_call(
            [test_env.python, "-m", "pip", "install", "-e", "."],
            cwd=rootdir,
            env=test_env.env,
        )
    
        # Run ansys-mechanical-ideconfig in the test virtual environment
        process, stdout, stderr = run_subprocess(
            [
                "ansys-mechanical-ideconfig",
                "--ide",
                "vscode",
                "--target",
                "user",
                "--revision",
                str(revision),
            ],
            env=test_env.env,
        )
        # Decode stdout and fix extra backslashes in paths
        stdout = stdout.decode().replace("\\\\", "\\")
    
        # Assert virtual environment is in the stdout
>       assert ".test_env" in stdout
E       AssertionError: assert '.test_env' in ''

tests/test_cli.py:367: AssertionError

Check failure on line 367 in tests/test_cli.py

View workflow job for this annotation

GitHub Actions / Test Report 3.10

test_cli.test_ideconfig_venv

AssertionError: assert '.test_env' in ''
Raw output
test_env = <conftest.test_env.<locals>.TestEnv object at 0x7f73af72ce80>
run_subprocess = <function run_subprocess.<locals>.func at 0x7f73af75c670>
rootdir = PosixPath('/__w/pymechanical/pymechanical')

    @pytest.mark.cli
    @pytest.mark.python_env
    def test_ideconfig_venv(test_env, run_subprocess, rootdir):
        """Test the IDE configuration location when a virtual environment is active."""
        # Set the revision number
        revision = 242
    
        # Install pymechanical
        subprocess.check_call(
            [test_env.python, "-m", "pip", "install", "-e", "."],
            cwd=rootdir,
            env=test_env.env,
        )
    
        # Run ansys-mechanical-ideconfig in the test virtual environment
        process, stdout, stderr = run_subprocess(
            [
                "ansys-mechanical-ideconfig",
                "--ide",
                "vscode",
                "--target",
                "user",
                "--revision",
                str(revision),
            ],
            env=test_env.env,
        )
        # Decode stdout and fix extra backslashes in paths
        stdout = stdout.decode().replace("\\\\", "\\")
    
        # Assert virtual environment is in the stdout
>       assert ".test_env" in stdout
E       AssertionError: assert '.test_env' in ''

tests/test_cli.py:367: AssertionError

Check failure on line 367 in tests/test_cli.py

View workflow job for this annotation

GitHub Actions / Test Report 3.12

test_cli.test_ideconfig_venv

AssertionError: assert '.test_env' in ''
Raw output
test_env = <conftest.test_env.<locals>.TestEnv object at 0x7fdde400b860>
run_subprocess = <function run_subprocess.<locals>.func at 0x7fdde403dee0>
rootdir = PosixPath('/__w/pymechanical/pymechanical')

    @pytest.mark.cli
    @pytest.mark.python_env
    def test_ideconfig_venv(test_env, run_subprocess, rootdir):
        """Test the IDE configuration location when a virtual environment is active."""
        # Set the revision number
        revision = 242
    
        # Install pymechanical
        subprocess.check_call(
            [test_env.python, "-m", "pip", "install", "-e", "."],
            cwd=rootdir,
            env=test_env.env,
        )
    
        # Run ansys-mechanical-ideconfig in the test virtual environment
        process, stdout, stderr = run_subprocess(
            [
                "ansys-mechanical-ideconfig",
                "--ide",
                "vscode",
                "--target",
                "user",
                "--revision",
                str(revision),
            ],
            env=test_env.env,
        )
        # Decode stdout and fix extra backslashes in paths
        stdout = stdout.decode().replace("\\\\", "\\")
    
        # Assert virtual environment is in the stdout
>       assert ".test_env" in stdout
E       AssertionError: assert '.test_env' in ''

tests/test_cli.py:367: AssertionError


@pytest.mark.cli
@pytest.mark.python_env
def test_ideconfig_default(test_env, run_subprocess, rootdir, pytestconfig):
"""Test the IDE configuration location when no arguments are supplied."""
# Get the revision number
revision = pytestconfig.getoption("ansys_version")
# Set part of the settings.json path
settings_json_fragment = Path("Code") / "User" / "settings.json"

# Install pymechanical
subprocess.check_call(
[test_env.python, "-m", "pip", "install", "-e", "."],
cwd=rootdir,
env=test_env.env,
)

# Run ansys-mechanical-ideconfig in the test virtual environment
process, stdout, stderr = run_subprocess(
[
"ansys-mechanical-ideconfig",
],
env=test_env.env,
)
# Decode stdout and fix extra backslashes in paths
stdout = stdout.decode().replace("\\\\", "\\")

assert revision in stdout
assert str(settings_json_fragment) in stdout
assert ".test_env" in stdout

0 comments on commit f4ee162

Please sign in to comment.