Skip to content

FEAT: add remote_method #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
name: Build package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ env.MAIN_PYTHON_VERSION }}
- name: Install build requirements
Expand All @@ -37,7 +37,7 @@ jobs:
python -c "import ansys.api.mechanical; print('Successfully imported ansys.api.mechanical')"
python -c "from ansys.api.mechanical import __version__; print(__version__)"
- name: Upload packages
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ansys-api-mechanical-packages
path: dist/
Expand All @@ -49,11 +49,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ env.MAIN_PYTHON_VERSION }}

- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4

- name: Display structure of downloaded files
run: ls -R
Expand All @@ -68,7 +68,7 @@ jobs:
TWINE_PASSWORD: ${{ secrets.ANSYS_API_MECHANICAL_PYPI_TOKEN }}

- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ __pycache__/
*$py.class

# Virtual environment
venv
.venv/
.env/
.venv/
env/

# Distribution / packaging
.Python
Expand Down
2 changes: 2 additions & 0 deletions src/ansys/api/mechanical/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@

with open(pathlib.Path(__file__).parent / "VERSION", encoding="utf-8") as f:
__version__ = f.read().strip()

from .misc import remote_method
99 changes: 99 additions & 0 deletions src/ansys/api/mechanical/misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import typing

class remote_method:
"""Decorator for passing remote methods."""
Comment on lines +3 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A class is not a decorator

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also... this naming convention is definitely not PEP-8


def __init__(self, func):
"""Initialize with the given function."""
self._func = func

def __call__(self, *args, **kwargs):
"""Call the stored function with provided arguments."""
return self._func(*args, **kwargs)

def __call_method__(self, instance, *args, **kwargs):
"""Call the stored function with the instance and provided arguments."""
return self._func(instance, *args, **kwargs)

def __get__(self, obj, objtype):
"""Return a partially applied method."""
from functools import partial

func = partial(self.__call_method__, obj)
func._is_remote = True
func.__name__ = self._func.__name__
func._owner = obj
return func


class MethodType:
"""Enum for method or property types."""

METHOD = 0
PROP = 1


def try_get_remote_method(
methodname: str, obj: typing.Any
) -> typing.Tuple[str, typing.Callable]:
"""Try to get a remote method."""
method = getattr(obj, methodname)
if not callable(method):
return None
if hasattr(method, "_is_remote") and method._is_remote is True:
return (methodname, method)


def try_get_remote_property(
attrname: str, obj: typing.Any
) -> typing.Tuple[str, property]:
"""Try to get a remote property."""
objclass: typing.Type = obj.__class__
class_attribute = getattr(objclass, attrname)
getmethod = None
setmethod = None

if class_attribute.fget:
if isinstance(class_attribute.fget, remote_method):
getmethod = class_attribute.fget
getmethod._owner = obj
if class_attribute.fset:
if isinstance(class_attribute.fset, remote_method):
setmethod = class_attribute.fset
setmethod._owner = obj

return (attrname, property(getmethod, setmethod))


def get_remote_methods(
obj,
) -> typing.Generator[typing.Tuple[str, typing.Callable, MethodType], None, None]:
"""Yield names and methods of an object's remote methods.

A remote method is identified by the presence of an attribute `_is_remote` set to `True`.

Parameters
----------
obj: Any
The object to inspect for remote methods.

Yields
------
Generator[Tuple[str, Callable], None, None]
A tuple containing the method name and the method itself
for each remote method found in the object
"""
objclass = obj.__class__
for attrname in dir(obj):
if attrname.startswith("__"):
continue
if hasattr(objclass, attrname):
class_attribute = getattr(objclass, attrname)
if isinstance(class_attribute, property):
attrname, prop = try_get_remote_property(attrname, obj)
yield attrname, prop, MethodType.PROP
continue
result = try_get_remote_method(attrname, obj)
if result != None:
attrname, method = result
yield attrname, method, MethodType.METHOD