Skip to content
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

@rpyc.service and subclasses #566

Open
random-developer opened this issue Dec 1, 2024 · 0 comments
Open

@rpyc.service and subclasses #566

random-developer opened this issue Dec 1, 2024 · 0 comments

Comments

@random-developer
Copy link

It seems that @rpyc.service is not smart enough to detect that a method has already been exposed. For example:

import rpyc
@rpyc.service
class BaseService(rpyc.Service):
    @rpyc.exposed
    def foo(self): ...

@rpyc.service
class ExtendedService(BaseService):
    @rpyc.exposed
    def bar(self): ...

service = ExtendedService()
for attr in dir(service):
    if attr.startswith('exposed_'):
        print(attr)

The output of this is:

exposed_bar
exposed_exposed_foo
exposed_foo
exposed_get_service_aliases
exposed_get_service_name

Notice the ExtendedService's foo method is exposed twice.

Clearly I can avoid this by removing @rpyc.service from either class, but that doesn't seem to be the right solution. If I remove it from ExtendedService then bar disappears from exposure. If I remove it from BaseService then I can no longer use BaseService without having some @rpyc.service extend it.

I don't know if this will have negative side effects, but I find that if I change @rpyc.service to the following, this problem is resolved:

def service(cls):
    """find and rename exposed decorated attributes"""
    # NOTE: inspect.getmembers invokes getattr for each attribute-name. Descriptors may raise AttributeError.
    # Only the AttributeError exception is caught when raised. This decorator will fail if a descriptor raises
    # any exception other than AttributeError when getattr is called.

    # Track already exposed attributes from base classes
    inherited_exposed = {
        attr_name
        for base in cls.__bases__
        for attr_name, attr_obj in inspect.getmembers(base)
        if getattr(attr_obj, '__exposed__', False)
    }

    for attr_name, attr_obj in inspect.getmembers(cls):  # rebind exposed decorated attributes
        exposed_prefix = getattr(attr_obj, '__exposed__', False)
        if exposed_prefix and not inspect.iscode(attr_obj):  # exclude the implementation
            # Skip if the attribute is already exposed in a parent class
            if attr_name in inherited_exposed:
                continue
            renamed = exposed_prefix + attr_name
            if inspect.isclass(attr_obj):  # recurse exposed objects such as a class
                attr_obj = service(attr_obj)
            setattr(cls, attr_name, attr_obj)
            setattr(cls, renamed, attr_obj)
    return cls
    ```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant