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

plumbum.cli doesn't play nicely with decorators and __future__.annotations #675

Open
danieleades opened this issue Feb 11, 2024 · 1 comment

Comments

@danieleades
Copy link

the copier project is using a decorator to wrap error handling around some plumbum methods, like so-

see https://github.com/copier-org/copier/blob/5e98d21f35767de527c64da371045f4f018fa619/copier/cli.py

from decorator import decorator


@decorator
def handle_exceptions(method, *args, **kwargs):
    """Handle keyboard interruption while running a method."""
    try:
        try:
            return method(*args, **kwargs)
        except KeyboardInterrupt:
            raise UserMessageError("Execution stopped by user")
    except UserMessageError as error:
        print(colors.red | "\n".join(error.args), file=sys.stderr)
        return 1
    except UnsafeTemplateError as error:
        print(colors.red | "\n".join(error.args), file=sys.stderr)
        # DOCS https://github.com/copier-org/copier/issues/1328#issuecomment-1723214165
        return 0b100

this decorator is then applied to plumbum.cli subcommand methods.

This works fine- the @decorator decorator ensures the wrapped method's signature doesn't change, so plumbum.cli's introspection still works. There are two downsides

  1. @decorator is untyped
  2. @decorator breaks if you add from __future__ import annotations to the file

I tried a different approach using a native decorator:

def _handle_exceptions(method: Callable[P, int]) -> Callable[P, int]:
    @functools.wraps(method)
    def inner(*args: P.args, **kwargs: P.kwargs) -> int:
        try:
            try:
                return method(*args, **kwargs)
            except KeyboardInterrupt:
                raise UserMessageError("Execution stopped by user")
        except UserMessageError as error:
            print(colors.red | "\n".join(error.args), file=sys.stderr)
            return 1
        except UnsafeTemplateError as error:
            print(colors.red | "\n".join(error.args), file=sys.stderr)
            # DOCS https://github.com/copier-org/copier/issues/1328#issuecomment-1723214165
            return 0b100

    return inner

but this fails, presumably because plumbum.cli is relying on some element of the method signature that functools.wraps fails to forward.

I tried instead-

# ...
inner.__signature = inspect.signature(method)

return inner

that works, unless you add from __future__ import annotations, because that import changes the behaviour of inspect.signature (see https://bugs.python.org/issue43355)

i'm guessing that's the exact issue that was causing me problems at the beginning of this journey...

How should I proceed?

@danieleades
Copy link
Author

ultimately i found a way around by avoiding decorators entirely, but it would be nice if there was some further consideration given to the interaction between plumbum.cli, decorators, and from __future__ import annotations.

see copier-org/copier#1513

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