Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ jobs:
run: |
pytest

# - uses: codecov/codecov-action@v5
# name: 'Upload coverage to CodeCov'
# with:
# token: ${{ secrets.CODECOV_TOKEN }}

doc-build:
name: Build documentation
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,6 @@ cython_debug/
.vscode
._build

doc/source/api
doc/source/api
.cov
not_a_dir
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ Releases = "https://github.com/ansys/ansys-tools-common/releases/"
[tool.flit.module]
name = "ansys.tools.common"

[tool.pytest.ini_options]
addopts = "-ra --cov=ansys.tools.common --cov-report html:.cov/html --cov-report xml:.cov/xml --cov-report term --capture=sys -vv"


[tool.ruff]
line-length = 120
fix = true
Expand Down
2 changes: 1 addition & 1 deletion src/ansys/tools/common/abstractions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
"""Init module for abstractions."""

from .connection import AbstractGRPCConnection # noqa F401
from .launcher import AbstractServiceLauncher # noqa F401
from .launcher import LauncherProtocol # noqa F401
26 changes: 18 additions & 8 deletions src/ansys/tools/common/abstractions/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

try:
import grpc
except ImportError:
except ImportError: # pragma: no cover
import warnings

warnings.warn(
Expand All @@ -47,33 +47,43 @@ class AbstractGRPCConnection(ABC):
@abstractmethod
def __init__(self, host: str, port: str) -> None:
"""Initialize the gRPC connection with host and port."""
pass
pass # pragma: no cover

@abstractmethod
def connect(self) -> None:
"""Establish a connection to the gRPC server."""
pass
pass # pragma: no cover

@abstractmethod
def close(self) -> None:
"""Disconnect from the gRPC server."""
pass
pass # pragma: no cover

@abstractmethod
@property
@abstractmethod
def service(self):
"""Return the gRPC stub for making requests."""
pass
pass # pragma: no cover

@property
def _host(self) -> str:
"""Return the host for the gRPC connection."""
return self._host
return self.__host

@_host.setter
def _host(self, value: str) -> None:
"""Set the host for the gRPC connection."""
self.__host = value

@property
def _port(self) -> str:
"""Return the port for the gRPC connection."""
return self._port
return self.__port

@_port.setter
def _port(self, value: str) -> None:
"""Set the port for the gRPC connection."""
self.__port = value

@property
def _channel(self, options: list = None) -> grpc.Channel:
Expand Down
2 changes: 1 addition & 1 deletion src/ansys/tools/common/abstractions/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class LauncherProtocol(Protocol[LAUNCHER_CONFIG_T]):
"""

def __init__(self, *, config: LAUNCHER_CONFIG_T):
pass
pass # pragma: no cover

def start(self) -> None:
"""Start the product instance."""
Expand Down
12 changes: 7 additions & 5 deletions src/ansys/tools/common/example_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ def download_file(
destination_path = Path(destination) if destination is not None else None

# If destination is not a dir, create it
if destination_path is not None and not destination_path.is_dir():
if destination_path is not None and not destination_path.exists():
destination_path.mkdir(parents=True, exist_ok=True)

# Check if it was able to create the dir
# Check if it was able to create the dir, very rare case
if destination_path is not None and not destination_path.is_dir():
raise ValueError("Destination directory provided does not exist")
raise ValueError("Destination directory provided does not exist") # pragma: no cover

url = self._get_filepath_on_default_server(filename, directory)
local_path = self._retrieve_data(url, filename, dest=destination, force=force)
Expand All @@ -125,7 +125,8 @@ def _add_file(self, file_path: str):
file_path : str
Local path of the downloaded example file.
"""
self._downloads_list.append(file_path)
if file_path not in self._downloads_list:
self._downloads_list.append(file_path)

def _joinurl(self, base: str, directory: str) -> str:
"""Join multiple paths to a base URL.
Expand Down Expand Up @@ -185,10 +186,11 @@ def _retrieve_data(self, url: str, filename: str, dest: str = None, force: bool
str
The local path where the file was saved.
"""
local_path = ""
if dest is None:
dest = tempfile.gettempdir() # Use system temp directory if no destination is provided
local_path = Path(dest) / Path(filename).name
if not force and Path.is_file(local_path):
if not force and Path(local_path).is_file():
return local_path
try:
local_path, _ = urllib.request.urlretrieve(url, filename=local_path)
Expand Down
2 changes: 1 addition & 1 deletion src/ansys/tools/common/launcher/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class LauncherProtocol(Protocol[LAUNCHER_CONFIG_T]):
"""

def __init__(self, *, config: LAUNCHER_CONFIG_T):
pass
pass # pragma: no cover

def start(self) -> None:
"""Start the product instance."""
Expand Down
4 changes: 0 additions & 4 deletions src/ansys/tools/common/versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,6 @@ def version_string_as_tuple(version_string):
# Check version string numbers are numeric by converting to integers
version_tuple = tuple(map(VersionNumber, version_string.split(".")))

# Check version numbers are positive integers
if not all(num >= VersionNumber(0) for num in version_tuple):
raise ValueError

except ValueError:
raise VersionSyntaxError(
"Version string can only contain positive integers following <MAJOR>.<MINOR>.<PATCH> versioning."
Expand Down
89 changes: 89 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Module for testing gRPC connection abstraction."""

from unittest.mock import MagicMock

import pytest

from ansys.tools.common.abstractions.connection import AbstractGRPCConnection


class MockGRPCConnection(AbstractGRPCConnection):
"""Mock implementation of AbstractGRPCConnection for testing."""

def __init__(self, host: str, port: str) -> None:
"""Initialize the mock gRPC connection."""
self._host = host
self._port = port
self._connected = False

def connect(self) -> None:
"""Connect to the mock gRPC server."""
self._connected = True

def close(self) -> None:
"""Close the mock gRPC connection."""
self._connected = False

@property
def service(self):
"""Service property that returns a mock gRPC stub."""
return MagicMock()


@pytest.fixture
def mock_connection():
"""Fixture for creating a mock gRPC connection."""
return MockGRPCConnection(host="localhost", port="50051")


def test_initialization(mock_connection):
"""Test initialization of the connection."""
assert mock_connection._host == "localhost"
assert mock_connection._port == "50051"
assert mock_connection.is_closed


def test_connect(mock_connection):
"""Test connecting to the gRPC server."""
mock_connection.connect()


def test_close(mock_connection):
"""Test disconnecting from the gRPC server."""
mock_connection.connect()
mock_connection.close()
assert mock_connection.is_closed


def test_service_property(mock_connection):
"""Test the service property."""
service = mock_connection.service
assert service is not None
assert isinstance(service, MagicMock)


def test_is_closed_property(mock_connection):
"""Test the is_closed property."""
assert mock_connection.is_closed
35 changes: 35 additions & 0 deletions tests/test_example_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def test_download():

assert Path.is_file(local_path)

# Check that file is cached
local_path2 = download_manager.download_file(filename, directory)

assert local_path2 == local_path

download_manager.clear_download_cache()

assert not Path.is_file(local_path)
Expand All @@ -51,3 +56,33 @@ def test_non_existent_file():
# Attempt to download the non-existent file
with pytest.raises(FileNotFoundError):
download_manager.download_file(filename, directory)


def test_get_filepath():
"""Test getting the file path of a downloaded file."""
filename = "11_blades_mode_1_ND_0.csv"
directory = "pymapdl/cfx_mapping"

# Get the file path
filepath = download_manager._get_filepath_on_default_server(filename, directory)

assert filepath == "https://github.com/ansys/example-data/raw/main/pymapdl/cfx_mapping/11_blades_mode_1_ND_0.csv"

directory += "/"
filepath = download_manager._get_filepath_on_default_server(filename, directory)

assert filepath == "https://github.com/ansys/example-data/raw/main/pymapdl/cfx_mapping/11_blades_mode_1_ND_0.csv"

filepath = download_manager._get_filepath_on_default_server(filename)

assert filepath == "https://github.com/ansys/example-data/raw/main/11_blades_mode_1_ND_0.csv"


def test_destination_directory():
"""Test getting the destination directory for a downloaded file."""
filename = "11_blades_mode_1_ND_0.csv"
directory = "pymapdl/cfx_mapping"

# Test directory gets created
result = download_manager.download_file(filename, directory, destination="not_a_dir")
assert result is not None
Loading
Loading