GithubHelp home page GithubHelp logo

mkorpela / overrides Goto Github PK

View Code? Open in Web Editor NEW
261.0 261.0 31.0 270 KB

A decorator to automatically detect mismatch when overriding a method

License: Apache License 2.0

Python 99.74% Shell 0.26%

overrides's People

Contributors

ashwin153 avatar bhushan-mohanraj avatar brentyi avatar donpatrice avatar drorasaf avatar jayvdb avatar joelgrus avatar leeopop avatar lisyarus avatar mgorny avatar mkorpela avatar ngoodman90 avatar rkr-at-dbx avatar soulmerge avatar tjsmart avatar tyleryep avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

overrides's Issues

Option to defer validation to class instantiation

In my project, I have a class hierarchy that resembles the following.

from __future__ import annotations

import overrides

class Parent(overrides.EnforceOverrides):
  def child(self) -> Child:
    raise NotImplementedError

class Child(Parent):
  @overrides.overrides
  def child(self) -> Child:
    return self

Since overrides are validated on class creation and not instantiation (which is a good thing and should remain the default), the type Child is not yet defined when the type annotations of Child.children are evaluated so an error is raised. It would be cool if there was a way to opt-into deferred validated (e.g., @overrides.overrides(defer=True) to defer the validation at the cost of a small performance hit.

Class cannot subclass 'EnforceOverrides' (has type 'Any')

When I use mypy --strict, I get:

error: Class cannot subclass 'EnforceOverrides' (has type 'Any')

This error occurs because mypy --strict contains the --disallow-subclassing-any flag, which prevents subclasses that return Any from being subclassed with more specific type annotations, e.g.

class Player(EnforceOverrides):
    def __init__(self) -> None:
          pass

I've tried looking into this issue but I've gotten stuck. Maybe it has to do with adding type hints...

class decorator to check for @final

apparently I don't have enough SO reputation to just leave a comment there..

feature request:
have you thought about a class-level decorator that checks for @Final decorators in parent classes, for all methods, not just those marked with @OVERRIDES? i'm thinking it will be easier to remember a single class-level decorator than it would be to try and mark every method with @OVERRIDES and work backwards to resolve issues..

Allow for a way to have unimplemented functions in base class without requiring `check_signature=False`

In the latest overrides version (6.1.0), the following code snippet leads to errors:

class A:
    def generic_func(self, *args, **kwargs):
        raise NotImplementedError

class B(A):
     @overrides
    def generic_func(self, actual_arg, some_kwarg=1):
        .
        .
        .

It can be pain to require setting @overrides(check_signature=False) if several classes in the code inherit from a class like A. Is there a way to ignore the check for *args and **kwargs?

Override breaks when overriding a method with no type hints in third party library

Hi,

Starting v5.0.0 or greater, when you try to override a method which originally isn't supported for type hints, the overriding breaks. Caught in AllenNLP and Grammarly/Gector

Steps to reproduce:

from overrides import overrides
from typing import Any


class BaseClass:
    def do_something(self, arg1, arg2=None):
        pass

class ChildClass(BaseClass):

    @overrides
    def do_something(self, arg1:str, arg2:Any=None) -> Any:
        pass

throws the TypeError:

Traceback (most recent call last):
  File "main.py", line 9, in <module>
    class ChildClass(BaseClass):
  File "main.py", line 12, in ChildClass
    def do_something(self, arg1:str, arg2:Any=None) -> Any:
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 88, in overrides
    return _overrides(method, check_signature, check_at_runtime)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 114, in _overrides
    _validate_method(method, super_class, check_signature)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/overrides.py", line 135, in _validate_method
    ensure_signature_is_compatible(super_method, method, is_static)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/signature.py", line 86, in ensure_signature_is_compatible
    ensure_all_positional_args_defined_in_sub(
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/overrides/signature.py", line 212, in ensure_all_positional_args_defined_in_sub
    raise TypeError(
TypeError: `ChildClass.do_something: arg1 must be a supertype of `<class 'inspect._empty'>` but is `<class 'str'>`

Overrides breaks when using a parameter of Callable type

When overriding the following interface:

class MyInterface(ABC):
  @abstractmethod
  def run(self, callback: Callable[[str], None]):
    pass

overrides will fail with:

lib/python3.7/site-packages/overrides/overrides.py:88: in overrides
    return _overrides(method, check_signature, check_at_runtime)
lib/python3.7/site-packages/overrides/overrides.py:114: in _overrides
    _validate_method(method, super_class, check_signature)
lib/python3.7/site-packages/overrides/overrides.py:135: in _validate_method
    ensure_signature_is_compatible(super_method, method, is_static)
lib/python3.7/site-packages/overrides/signature.py:84: in ensure_signature_is_compatible
    super_sig, sub_sig, super_type_hints, sub_type_hints, is_static, method_name
lib/python3.7/site-packages/overrides/signature.py:138: in ensure_all_kwargs_defined_in_sub
    and not _issubtype(super_type_hints[name], sub_type_hints[name])
python3.7/site-packages/overrides/signature.py:41: in _issubtype
    return issubtype(left, right)
python3.7/site-packages/typing_utils/__init__.py:274: in issubtype
    return _is_normal_subtype(normalize(left), normalize(right), forward_refs)
python3.7/site-packages/typing_utils/__init__.py:177: in normalize
    args = tuple(normalize(a) for a in args)
python3.7/site-packages/typing_utils/__init__.py:177: in <genexpr>
    args = tuple(normalize(a) for a in args)
python3.7/site-packages/typing_utils/__init__.py:171: in normalize
    return NormalizedType(_ensure_builtin(tp))
python3.7/site-packages/typing_utils/__init__.py:49: in _ensure_builtin
    if tp in BUILTINS_MAPPING:
E   TypeError: unhashable type: 'list'

I believe this is an issue in the underlying typing-utils library that it doesn't properly handle the first parameter of Callable being a list (which is unhashable).

Signature validation fails when __future__.annotations is imported

When from __future__ import annotations is imported, all annotations are converted into strings. Currently, the code incorrectly assumes that type annotations are objects. A simple fix to this problem is to use the typing.get_type_hints function in the standard library to extract type hints instead of using inspect.signature. This will become the default behavior in Python 3.10, so we will have to do this eventually.

Move signature check to overrides decorator

Add signature checking to overrides decorator (not just Enforced case).
Also add an argument to decorator to disable it in cases where the check is invalid.

@overrides(ignore_signature=True)
def foobar(x=4, y:str="3", *argscheckignored):
    pass

import error in version 4.1.1

Hi, I encountered an import error after upgrading overrides to 4.1.1.

Here is what I got

Python 3.6.12 |Anaconda, Inc.| (default, Sep  8 2020, 17:50:39) 
[GCC Clang 10.0.0 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import overrides
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/__init__.py", line 1, in <module>
    from overrides.enforce import EnforceOverrides
  File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/enforce.py", line 169, in <module>
    class EnforceOverrides(metaclass=EnforceOverridesMeta):
  File "/opt/miniconda3/envs/PycharmProjects/lib/python3.6/site-packages/overrides/enforce.py", line 133, in __new__
    setattr(getattr(mcls, method), "__ignored__", True)
AttributeError: 'int' object has no attribute '__ignored__'

decorator order

not sure this is worth a true "fix", a note in the docs / example may be enough:

the order / interaction of (some) decorators seems to matter, e.g. (in python 2.7):

from overrides import overrides

class A:
    @property
    def foo( self ):
        pass
    
class B( A ):
    @overrides
    @property
    def foo( self ):
        pass

leads to "AttributeError: 'property' object has no attribute 'globals'", but

class B( A ):
    @property
    @overrides
    def foo( self ):
        pass

works exactly as expected; btw, declaring class A( object ): vs. class A: didn't make a difference

Detect violations of @final if subclass method does not use @overrides

If a method is marked as @final, an error should be risen if subclass tries to override it.
Currently it only works if the subclass's method is decorated with @overrides, if this decorator is omitted, no error is shown. (I have not tested it, but it seems to be so from looking at the code.)

I suggest to create a class StrictParent, from which other classes using the decorators could inherit from. StrictParent would have a metaclass, so it could detect the violations of @foo even if the subclass's method does not use @overrides.

This solution could be used to solve #12 as well.

Since one might need to use the @final and @overrides features in an ABC class, StrictParent's metaclass should inherit from ABCMeta in order to prevent metaclass conflicts.

Fails for Generic types

The following fails:

from typing import Generic, TypeVar

from overrides import overrides

TObject = TypeVar("TObject", bound="Foo")


class Foo(Generic[TObject]):
    """Foo class."""
    def foo_method(self):
        """Foo method."""
        pass


class Bar(Foo["Bar"]):
    """Bar class."""
    @overrides
    def foo_method(self):
        return 5


converter = Bar()
print(converter.foo_method())

with

Traceback (most recent call last):
  File "bla.py", line 15, in <module>
    class Bar(Foo["Bar"]):
  File "bla.py", line 17, in Bar
    @overrides
  File "/Users/patrik/.local/share/virtualenvs/dbbsim-j0PjGvEt/lib/python3.7/site-packages/overrides/overrides.py", line 70, in overrides
    method.__name__)
AssertionError: No super class method found for "foo_method"

Erroneously tries to validate class variables

from overrides import EnforceOverrides, overrides

class SuperClass(EnforceOverrides):
    someClassVariable = 'foo'

class SubClass(SuperClass):
    someClassVariable = 'bar'

Produces:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    class SubClass(SuperClass):
  File "/Users/wtregask/Documents/c2s_backend_6/build/c2s-backend/venv/lib/python3.7/site-packages/overrides/enforce.py", line 19, in __new__
    'Method %s overrides but does not have @overrides decorator' % (name)
AssertionError: Method someClassVariable overrides but does not have @overrides decorator

Unfortunately, there's nothing the programmer can do about that - class variables cannot be decorated.

Overrides should ignore any attributes of the class that aren't callable.

Failed on [email protected]

Here is the error from [email protected]:
@overrides File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 58, in overrides for super_class in _get_base_classes(sys._getframe(2), method.__globals__): File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 75, in _get_base_classes class_name_components in _get_base_class_names(frame)] File "/Users/abc/virtualenv/xyz/lib/python3.6/site-packages/overrides/overrides.py", line 96, in _get_base_class_names if type(co.co_consts[oparg]) == str: IndexError: tuple index out of range

I tried to fix it locally but had problem to run the tests. Note I just upgrade from [email protected] to @3.6.4 and use virtualenv for isolation. Let me know if there is anything I could help.

Thanks,
Leo

`sub_return` is required if `super_return` is specified

Currently, subclasses are required to have a return type-hint if the superclass defines one.

import overrides

class SuperClass(overrides.EnforceOverrides):
    def foo(self) -> int:
        return 1

class SubClass(SuperClass):
    @overrides.overrides
    def foo(self):
        return 2

# TypeError: SubClass.foo: return type `None` is not a `<class 'int'>`.

This is because and sub_return is not None is missing on the following line.

if not _issubtype(sub_return, super_return) and super_return is not None:
raise TypeError(
f"{method_name}: return type `{sub_return}` is not a `{super_return}`."

Classifiers are not enough to protect Python 2 users from latest version

Classifiers are correctly stated, but these are only for browsing Pypi and are not used during installation:

classifiers=[

Python3 should be declared as a python_requires:
https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires

Otherwise, Python2 installs 3.x.x, but receives a syntax error during import overrides:

def overrides(method: _WrappedMethod) -> _WrappedMethod:

Handle self of a subtype

self is a special param. Because of multiple inheritance self might not be able to actually obey normal requirements of subtype param to be contravariant wikipedia Inheritance.
I believe that self should be assumed and allowed to be bound to current class. It is also safer not to complain.
( see #60 (comment) )

import abc
from typing import TypeVar

from overrides import EnforceOverrides, final, overrides

BaseType = TypeVar("BaseType", bound="A")
SubType = TypeVar("SubType", bound="B")


class A(abc.ABC, EnforceOverrides):
    @abc.abstractmethod
    def copy(self: BaseType) -> BaseType:
        """Copy an object."""


class B(A):
    @final
    @overrides
    def copy(self: SubType) -> SubType:  # TypeError: `~SubType` is not a `~BaseType`.
        return self.helper()  # For this to be valid, `self` must be typed as a subclass of `B`

    @abc.abstractmethod
    def helper(self: SubType) -> SubType:
        """Helper for copy(), specific to subclasses of `B`."""


class C(B):
    def helper(self) -> "C":
        return C()


C().copy()

Creating a method with the same name as a method in `EnforceOverridesMeta` raises

Because register is defined in abc.ABCMeta and handle_special_value is defined in EnforceOverridesMeta, the code incorrectly determines that classes that redefine these methods are missing an @overrides decorator. Instead, EnforceOverridesMeta should ignore methods that are missing an @overrides decorator if they are defined on EnforceOverridesMeta.

class RegisterRepro(overrides.EnforceOverrides):
    def register(self):
        pass

# AssertionError: Method register overrides but does not have @overrides decorator
class HandleSpecialValueRepro(overrides.EnforceOverrides):
    def handle_special_value(self):
        pass

# AssertionError: Method handle_special_value overrides but does not have @overrides decorator

Release 5.0.0b0

  • Would it be possible to release a beta version of 0.5.0 to PyPI? I have a fairly large class hierarchy in my project that would exercise a lot of the new functionality (e.g., everything is type-hinted, there are self-types, there are static methods, etc.). Unfortunately, changing the build infrastructure to use git references instead of pip packages is a little hard in this project. Getting 0.5.0 to work on my project should give us additional confidence going into the 0.5.0 release.

Signature enforcement doesn't allow edge cases with **kwargs

I'm happy to see signature enforcement feature, cool stuff, but it broke how I was doing inheritance in my library. Maybe I'm wrong to do this, but it's valid python and it seems to be a missed edge case.

Here's a toy example that exposes the issue. In this design pattern the base class has a concrete set of optional parameters that I want to be able to add to in the future without updating every subclass to add the same optional arguments and defaults. The base class is the source of truth for this behavior.

Subclasses can add their own unique parameters to the signature, and pass the remainder on to the superclass without requiring knowledge of the signature or duplication of the default parameters.

from overrides import overrides, EnforceOverrides

class A(EnforceOverrides):

   def methoda(self, x=0):
       print(x)

class B(A):

   # this fails validation because x is missing
   @overrides
   def methoda(self, y=1, **kwargs):
       print(y)
       super().methoda(**kwargs)

However, this results in this stack trace:

  File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 155, in __new__
    ensure_signature_is_compatible(base_class_method, value)

  File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 34, in ensure_signature_is_compatible
    ensure_all_args_defined_in_sub(super_sig, sub_sig)

  File "C:\ProgramData\Anaconda\lib\site-packages\overrides\enforce.py", line 47, in ensure_all_args_defined_in_sub
    raise TypeError(f"`{name}` is not present.")

TypeError: `x` is not present.

As a side note, it took me a bit to track down the issue because the TypeError didn't specify which method was missing the parameter "x". That would be a nice enhancement to the message to include the function name.

I found I could get around the issue by changing the base class to use **kwargs, but I'd prefer not to have to do that for readability.

from overrides import overrides, EnforceOverrides


class A(EnforceOverrides):

    def methoda(self, **kwargs):
        x = kwargs.get("x", 0)
        print(x)

class B(A):

    @overrides
    def methoda(self, y=1, **kwargs):
        print(y)
        super().methoda(**kwargs)

Overrides has a hard error when string type hints fail to evaluate

For the following class:

from concurrent.futures import Future

class MyInterface(ABC):
  @abstractmethod
  def run(self) -> "Future[str]":
    pass

Overrides will fail with a hard error due to:

  File "lib/python3.6/site-packages/overrides/overrides.py", line 67, in overrides
    return _overrides(method, check_signature, check_at_runtime)
  File "lib/python3.6/site-packages/overrides/overrides.py", line 93, in _overrides
    _validate_method(method, super_class, check_signature)
  File "lib/python3.6/site-packages/overrides/overrides.py", line 114, in _validate_method
    ensure_signature_is_compatible(super_method, method, is_static)
  File "lib/python3.6/site-packages/overrides/signature.py", line 75, in ensure_signature_is_compatible
    super_type_hints = _get_type_hints(super_callable)
  File "lib/python3.6/site-packages/overrides/signature.py", line 46, in _get_type_hints
    return get_type_hints(callable)
  File "lib/python3.6/typing.py", line 1543, in get_type_hints
    value = _eval_type(value, globalns, localns)
  File "lib/python3.6/typing.py", line 350, in _eval_type
    return t._eval_type(globalns, localns)
  File "lib/python3.6/typing.py", line 245, in _eval_type
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
TypeError: 'type' object is not subscriptable

Hard failure may not be the correct answer here: IMO the unevaluatable type hint should be ignored.

EnforceOverrides doesn't work for magic methods

I tried marking an __enter__ method as final, but this doesn't work:

class MultiResourceManager(ExitStack, EnforceOverrides):
    @overrides
    @final
    def __enter__(self):
        with ExitStack() as stack:
            stack.push(self)
            self.acquire_resources()
            stack.pop_all()
        return self

    @abstractmethod
    def acquire_resources(self) -> None:
        pass

class OverridesEnter(MultiResourceManager):
    def __enter__(self):
        # this should fail!
        pass

The EnforceOverridesMeta class explicitly excludes names that start with double underscores (see enforce.py lines 10-11). I suspect the intention of this is to exclude names like __module__ or __qualname__. However, those could be more appropriately filtered by checking callable(value) instead.

The updated code would be:

class EnforceOverridesMeta(ABCMeta):
    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        for name, value in namespace.items():
            # Actually checking the direct parent should be enough,
            # otherwise the error would have emerged during the parent class checking
            if not callable(value): # <--- this line is different
                continue
            value = mcls.handle_special_value(value)
            is_override = getattr(value, '__override__', False)
            for base in bases:
                base_class_method = getattr(base, name, False)
                if not base_class_method or not callable(base_class_method):
                    continue
                assert is_override, \
                    'Method %s overrides but does not have @overrides decorator' % (name)
                # `__finalized__` is added by `@final` decorator
                assert not getattr(base_class_method, '__finalized__', False), \
                    'Method %s is finalized in %s, it cannot be overridden' % (base_class_method, base)
        return cls
    
    @staticmethod
    def handle_special_value(value):
        if isinstance(value, classmethod):
            value = value.__get__(None, dict)
        elif isinstance(value, property):
            value = value.fget
        return value

Relevant scenarios:

  • Like above: preventing override of a magic method such as __enter__ to ensure a behavior / safety by marking it final.
  • If __init__ is part of the interface (e.g. for factory functions operating on types), marking it as final.
  • Enforcing @overrides markers on magic functions to make it explicit when such functions are being overriden.

Note that the change as proposed above is not backwards compatible. To maintain backwards compatibility the existing class could be maintained and an additional class could be added with the new behavior (e.g. EnforceOverridesAll).

EnforceOverrides fails on nested classes

If you run this in a REPL it fails (Python 3.8). I've stared at this for an hour and I have no idea why it fails. Do you guys have any ideas?

import overrides

class Foo:
    class Bar(overrides.EnforceOverrides):
        def to_dict(self): 
            pass
    class Car(Bar):
        @overrides.overrides
        def to_dict(self):
            pass

To be clear, this works fine.

class Foo:
    class Bar:
        def to_dict(self): 
            pass
    class Car(Bar):
        def to_dict(self):
            pass

Feature request: merge docstrings

It'd be great if there were a way for overridden methods to amend the parent docstring in some way - even if just through some kind of dumb string concatenation. Sometimes the overriding method has more useful information to add - e.g. subclass-specific details or behaviours - but having to copy-paste the entire parent docstring creates a maintenance burden, since now the docstrings must be carefully, manually kept synchronised.

Even better would be if common docstring formats were understood, and the merge done more intelligently than just a string concatenation - e.g. param / return values in the override get individually appended to the corresponding lines from the parent.

The exact behavour desired - re. append vs merge vs replace - could be controlled via an optional argument to @overrides.

Strange error if using a class decorator with a dynamic parameter in a function

I encountered a strange error that I managed to reproduce with the following example:

from typing import Callable, Type
from overrides import overrides


class SuperClass(object):
    def method(self) -> int:
        """This is the doc for a method and will be shown in subclass method too!"""
        return 2


def my_decorator(name: str) -> Callable:
    def func(cls: Type) -> Type:
        return cls

    return func


class MyClass:
    def __init__(self, name: str):
        self.my_name: str = name


def my_func() -> None:
    my_object = MyClass("Slartibartfast")

    @my_decorator(my_object.my_name)
    class SubClass(SuperClass):
        @overrides
        def method(self) -> int:
            return 1


my_func()

The resulting error message:

Traceback (most recent call last):
  File "repro.py", line 33, in <module>
    my_func()
  File "repro.py", line 27, in my_func
    class SubClass(SuperClass):
  File "repro.py", line 29, in SubClass
    def method(self) -> int:
  File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 59, in overrides
    for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
  File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 76, in _get_base_classes
    class_name_components in _get_base_class_names(frame)]
  File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 76, in <listcomp>
    class_name_components in _get_base_class_names(frame)]
  File "/home/username/.local/lib/python3.6/site-packages/overrides/overrides.py", line 173, in _get_base_class
    obj = getattr(obj, component)
AttributeError: 'function' object has no attribute 'my_name'

Error with Python 3.6

Hi,
I just ran this program:

from overrides import overrides

class SuperClass(object):
    def method(self): return 2

class SubClass(SuperClass):
    @overrides
    def method(self): return 1

And got an error message.

Traceback (most recent call last):
  File "C:\Users\admin\Test.py", line 9, in <module>
    class SubClass(SuperClass):
  File "C:\Users\admin\Test.py", line 11, in SubClass
    @overrides
  File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 58, in overrides
    for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
  File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 75, in _get_base_classes
    class_name_components in _get_base_class_names(frame)]
  File "C:\Users\admin\AppData\Local\Programs\Python\Python36\lib\site-packages\overrides\overrides.py", line 96, in _get_base_class_names
    if type(co.co_consts[oparg]) == str:
IndexError: tuple index out of range

It seems to me, as if this was caused by updating from Python 3.5 to 3.6.

Best,
r0f1

@staticmethod doesn't work with @overrides

@staticmethod
@overrides
def get_all_statements(player_index: int) -> List[Statement]:

AssertionError: Method get_all_statements overrides but does not have @OVERRIDES decorator

@overrides
@staticmethod
def get_all_statements(player_index: int) -> List[Statement]:

@staticmethod
File "/Users/tyleryep/miniconda3/lib/python3.7/site-packages/overrides/overrides.py", line 57, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.globals):
AttributeError: 'staticmethod' object has no attribute 'globals'

Overriding ClassMethod Fails

When overriding a classmethod, the override decorator isn't recognised.

@classmethod
@abstractmethod
def method(cls):
    pass

@classmethod
@overrides
def method(cls):
    return 1

AssertionError: Method method overrides but does not have @OVERRIDES decorator

Pip fails to install overrides

Python 3.4.0, pip 7.0.1

Command pip install overrides fails with

Collecting overrides
  Using cached overrides-0.4.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "/tmp/pip-build-evd5k9b1/overrides/setup.py", line 5, in <module>
        from overrides import __VERSION__
      File "/tmp/pip-build-evd5k9b1/overrides/overrides/__init__.py", line 2, in <module>
        from overrides import __VERSION__
    ImportError: cannot import name '__VERSION__'

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-evd5k9b1/overrides

Missing type annotations

The final and overrides decorators do not contain type annotations. As a result, adding these decorators to functions breaks mypy type checking (the annotations are 'forgotten').

Adding the following type stubs would fix this:

from typing import Any, Callable, TypeVar

_FuncT = TypeVar('_FuncT', bound=Callable[..., Any])

def final(method: _FuncT) -> _FuncT:
    ...
def overrides(method: _FuncT) -> _FuncT:
    ...

Note that there is a separate issue of a module and decorator sharing a name (overrides). Some care must be taken to avoid mypy getting confused by this. By putting the above stubs in __init__.pyi in an overrides-stubs package (installed with pip), this potential confusion was avoided.

Arguments validations II

I'll add this new issue as a discussion opener. Thanks for great work @ashwin153 !

**kwargs and *args are a bit special and seems that current implementation does not handle them yet correctly.
Basically both need to be also present in sub method if present in the super method. BUT
one can always detach elements from there.

I assume the logic would be the following (please correct me if wrong):

  1. **kwargs might be needed to detached as keyword only args in overriding method.
  2. *args might be needed to detached as positional only args in overriding method.

Still have to triple check this from https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Inheritance_in_object-oriented_languages

I would also want this to eventually go to the plane @overrides decorator but that might be also done later.

Return signature check corner case for generic types

Hello,

A lot of the code I'm currently writing relies pretty heavily on TypeVar definitions. Woke up this morning and found out that some of it stopped running!

It seems like the newest release of overrides adds some stricter signature checking, which throws an error when my parent class's function signatures rely on TypeVars:

import abc
from typing import TypeVar

from overrides import EnforceOverrides, overrides

T = TypeVar("T", bound="ParentClass")


class ParentClass(abc.ABC, EnforceOverrides):
    @abc.abstractmethod
    def copy(self: T) -> T:  # Specify that the output should have the same type as `self`
        """Return a copy of myself."""


class Subclass(ParentClass):
    @overrides
    def copy(self) -> "Subclass":  # TypeError: `Subclass` is not a `~T`.
        return Subclass()

I get a TypeError: 'Subclass' is not a '~T'. message, because typing_utils.issubtype doesn't consider Subclass here to be a subtype of TypeVar("T", bound="ParentClass").

Is this expected behavior? The setup is a little niche, but in my experience it's at least well-supported by mypy.

Thanks!!

pycharm: 'overrides' is not callable

Hello,
I don't think that this is a problem with your code, but perhaps you could help me to solve this.

PyCharm offers a method to insect the code of a project.
If I am using your override annotation (thanks a lot), I get the following warning:

Problem synopsis
      'overrides' is not callable (at line ...)

One warning for each usage of the decorator.

I am also using "@abc.abstractmethod" decorators but there is no problem detected.

So, could you help me, why "abs" decorations are working without "problems" and your "overrides" raise such a warning?

I could ignore this IDE problem, as it is working, but it would be nice to get such stuff suppressed.

Error on previously working code

Hi, I updated to version 2.1 and start getting AssertonError:

    def count_vocab_items(self, token: Token, counter: Dict[str, Dict[str, int]]):
  File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 58, in overrides
    for super_class in _get_base_classes(sys._getframe(2), method.__globals__):
  File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 75, in _get_base_classes
    class_name_components in _get_base_class_names(frame)]
  File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 75, in <listcomp>
    class_name_components in _get_base_class_names(frame)]
  File "/home/generall/sources/Sci/ml/word_gan/venv/lib/python3.6/site-packages/overrides/overrides.py", line 167, in _get_base_class
    obj = getattr(namespace["__builtins__"], components[0])
AttributeError: 'dict' object has no attribute 'int'

Feature request: check that method parameters match

It'd be fantastic if the checks performed by @OverRide could be extended to include validation of the parameters, to ensure they are the same (with certain exceptions such as the use of *args & **kwargs) and have the same type information (where present).

Properly support Python 2

There are commits indicating Python 2 support, but it's not in the Trove classifiers nor the tox.ini, so it will suffer bit-rot easily.

This is obviously mainly for Python 2.7, but Python 2.6 would probably not be a significant extra burden and apparently still sees significant use.

Suggestion: enforce using @overrides

So, the decorator you've created is pretty cool. However, it doesn't enforce usage.

What I'd like to see, as a slight variation on your example, kinda like the reverse of what @abstractmethod in abstract superclass will do if not implemented in subclass

from overrides import overrides

class SuperClass(EnforceOverrides):

    def method(self):
        """This is the doc for method and will be shown in subclass method too!"""
        return 2

class SubClass(SuperClass):

# error, because the @overrides annotation is missing
    def method(self):
        return 1

I'm not 100% sure this is possible in Python (might really require a linting plugin or metaclass?), but it would be really useful for enforcing good code documentation.

Performance impact

Is the check done every method call or only at class creation? What is the performance impact? It would be nice to know this README.

@overrides issue w/ typing.Generic and built-in types

Toy example that triggers the problem (tested in Python 3.7):

from typing import Generic, TypeVar

from overrides import overrides

class Foo:
    pass

T1 = TypeVar('T1')

class SuperClass(Generic[T1]):
    pass

# this class causes error, but ONLY w/ the @overrides annotation
class SubClass1(SuperClass[float]):
    @overrides  # gives wrong error message, even if defined in SuperClass
    def foo(self):
        pass

class SubClass2(SuperClass[Foo]):
    @overrides  # behaves as expected
    def foo(self):
        pass

This triggers the following:

Traceback (most recent call last):
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 165, in _get_base_class
obj = namespace[components[0]]
KeyError: 'float'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:/Users/Dylan/.PyCharmCE2019.2/config/scratches/scratch_1.py", line 14, in
class SubClass1(SuperClass[float]):
File "C:/Users/Dylan/.PyCharmCE2019.2/config/scratches/scratch_1.py", line 15, in SubClass1
@OVERRIDES # gives wrong error message, even if defined in SuperClass
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 58, in overrides
for super_class in _get_base_classes(sys._getframe(2), method.globals):
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 75, in _get_base_classes
class_name_components in _get_base_class_names(frame)]
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 75, in
class_name_components in _get_base_class_names(frame)]
File "C:\Users\Dylan\Miniconda3\envs\test\lib\site-packages\overrides\overrides.py", line 167, in _get_base_class
obj = namespace["builtins"][components[0]]
TypeError: 'module' object is not subscriptable

Process finished with exit code 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.