Git Product home page Git Product logo

smarie / python-autoclass Goto Github PK

View Code? Open in Web Editor NEW
36.0 4.0 2.0 2.92 MB

A python 3 library providing functions and decorators to automatically generate class code, such as constructor body or properties getters/setters along with optional support of validation contracts on the generated setters. Its objective is to reduce the amount of copy/paste code in your classes - and hence to help reducing human mistakes :).

Home Page: https://smarie.github.io/python-autoclass/

License: BSD 3-Clause "New" or "Revised" License

Python 99.58% Shell 0.42%
code-generator constructor autoargs autoprops contracts enforce boilerplate pep484 type-hints runtime-typechecking

python-autoclass's Issues

Inheritance of @autoclass tagged classes (@autodict inheritance issues)

As of today inheritance is not intuitive:

from autoclass import autoclass

@autoclass
class Foo:
    def __init__(self, foo):
        pass

@autoclass
class Bar(Foo):
    def __init__(self, bar):
        pass

yields

> ValueError: @autodict can not be set on classes that are already subclasses of Mapping, and therefore already behave like dict

In order for this to work, you have to use @autoclass(autodict=False) in the parent class. But this is not user-friendly. The proposal would be to remove this exception, and make all dict implemented methods call the corresponding super() method where appropriate

Improve default string representation in `@autodict`

By default @autodict creates a string representation that is not very user-friendly (House({'name': 'mine', 'nb_floors': 100})).

Let's propose by default a better representation : House(name='mine', nb_floors=100)

Of course we should provide an option to fallback to legacy behaviour if users need it

Enforce + Autoclass: AttributeError: 'functools.partial' object has no attribute '__enforcer__'

This works:

from autoclass import autoclass, setter_override
from numbers import Real
from enforce import runtime_validation, config
config(dict(mode='covariant'))  # to accept subclasses in validation

@runtime_validation
@autoclass
class HouseConfiguration(object):
    def __init__(self, surface: Real):
        pass

    # -- overriden setter for surface
    @setter_override
    def surface(self, surface):
        print('Set surface to {}'.format(surface))
        self._surface = surface

t = HouseConfiguration(12)

While this throws an exception AttributeError: 'functools.partial' object has no attribute '__enforcer__':

from autoclass import autoclass, setter_override
from numbers import Real
from enforce import runtime_validation, config
config(dict(mode='covariant'))  # to accept subclasses in validation

@autoclass
@runtime_validation
class HouseConfiguration(object):
    def __init__(self, surface: Real):
        pass

    # -- overriden setter for surface
    @setter_override
    def surface(self, surface):
        print('Set surface to {}'.format(surface))
        self._surface = surface

t = HouseConfiguration(12)

The only difference is the order of the annotations on class HouseConfiguration.

Setter is called twice for default values

When an argument is declared as optional with default value in the constructor, everytime it is set by @autoargs the setter is called twice (once with the default value and once with the value provided by the user)

global counter
counter = 0

class Home(object):
    @autoargs
    def __init__(self, foo, bar=False):
        pass

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, value):
        global counter
        counter += 1
        self._bar = value

Home(None, bar=True)
assert counter == 1  # AssertionError : counter is 2 !

Exclude argument does not work properly with autohash

Suppose I want to exclude a couple fields from the hash because for example they are not hashable.

@autoclass(autohash=False)
@autohash(exclude='bar')
class Foo:
    def __init__(self, foo: str, bar: Dict[str, str]):
        pass

The above does not work:

> a = Foo('hello', dict())
> hash(a)  # TypeError: unhashable type: 'dict'

The only workaround as of today is to use the private name associated with the attribute:

@autoclass(autohash=False)
@autohash(exclude='_bar')  # <- here use the private name
class Foo:
    def __init__(self, foo: str, bar: Dict[str, str]):
        pass

But it is not satisfying.

Note that there is probably a similar behaviour in @autodict with non-None exclude/include.

Support `pyfields` as an alternate way to define attributes

As of today, all decorators provided in autoclass source their initial attribute list on the constructor (__init__) signature.

Alternately one could wish to define the fields using pyfields:

  • @autodict and @autohash would work independently
  • @autoclass would also work: it would apply only @autodict and @autohash (and not @autoargs nor @autoprops as they are not anymore relevant in this context)
from autoclass import autoclass
from pyfields import field

@autoclass
class A(object):
    a = field()

`@autorepr`

As of now string representation is done by @autodict and relies on the .items() view to create the representation. It therefore can not be used independently. It would be good to propose an independent decorator for this.

Can python-autoclass be used with dataclasses/datafiles?

I'm looking at datafiles

I like the ORM mapping of yaml that dataclasses/datafiles provides but I also love the features that python-autoclass provides, however, I can't tell if they can be used together, if they would be redundant, complementary, or if one would interfere with the other. If they can work together, would the order that the annotations are specified matter?

Edit: changed link to the actual module I would like to use.

KeyError: '@autodict generated dict view - args is a constructor parameter but is not a field (was the constructor called ?)'

This happens when I create a simple class and use repr on the object:

from autoclass import autoclass

@autoclass
class City:
    name: Optional[str]
    buildings: List[str] = []
>>> City("mine")

yields

  File "C:\_dev\python_ws\_Libs_OpenSource\python-autoclass\autoclass\autodict_.py", line 456, in __getitem__
    'field (was the constructor called ?)'.format(key))
KeyError: '@autodict generated dict view - args is a constructor parameter but is not a field (was the constructor called ?)'

autodict alternative generating a `to_dict()` or `as_dict()` method

@autodict currently makes the class behave as a dict so that all atributes can also be accessed as dict items.

  • side remark: current implementation is maybe (?) overkill for non-slots classes where there is already a __dict__ inside. This is not really an issue for now
  • some (most?) users might prefer to have a as_dict or to_dict method to convert the object

Type error when using the generated `from_dict` method with pytypes

When using pytypes type checker with the from_dict method generated by @autoclass/@autodict:

from pytypes import typechecked
from autoclass import autoclass

@typechecked
@autoclass
class Foo:
    def __init__(self, foo):
         pass

Foo.from_dict(dict(foo=1))

returns

pytypes.exceptions.InputTypeError: 
  classmethod autoclass.autodict.Foo.from_dict
  called with incompatible types:
Expected: Tuple[Dict]
Received: Tuple[Dict[str, int]]

IDE Static attribute checking shows warning

@smarie Not sure if there is a way around this: In IDE (I'm using pyCharm), when using autoargs the class attribute shows up as undefined during the static checking of the IDE. It looks like this:

screenshot 2017-09-04 05 58 34

It would be great if there is a way to get around this issue.

@autoprops: Add default value annotation in setters for optional constructor arguments

Let's consider type checking with enforce with implicit optional arguments. Both of these statements succeed.

from enforce import runtime_validation

@runtime_validation
def foo(mandatory_arg: str, optional_arg: str = None):
    pass
foo('t')

@runtime_validation
class Foo:
    def __init__(self, mandatory_field: str, optional_field: str = None):
        pass
Foo('t')

However, the following fails in autoclass:

@runtime_validation
@autoclass
class Foo:
    def __init__(self, mandatory_field: str, optional_field: str = None):
        pass
a = Foo('t')
a.optional_field = None  # raises RuntimeTypeError: Argument 'val' was not of type <class 'str'>. Actual type was NoneType.

This is due to the fact that

  • the PEP484 notation for Optionality has not been explicitly used (no Optional[str]), so optionality is implicitly detected by checking if there is a None default value
  • however in the property setter generated by @autoprops there is no such default value >> None is therefore not considered valid

Fixing this is relatively easy: @autoprops should correctly set the __defaults__ annotation on the generated property setter.

@autohash

This would generate a def __hash__ based on the tuple of included/excluded attributes

Improve __init__.py

It seems that all should also contain the names of all symbols exported by each module. Let's try to automatically generate this from public names.

Besides, it seems that the symbols imported by the package are re-exported (or at least pycharm thinks so). For example typing symbols, etc. Let's dig this topic too...

Wrong function name in validation error message of field setter

from autoclass import autoclass
from pytypes import typechecked
from valid8 import validate_arg, is_multiple_of
from mini_lambda import s, x, Len

@typechecked
@autoclass
class House:
    @validate_arg('name', Len(s) > 0)
    @validate_arg('surface', (x >= 0) & (x < 10000), is_multiple_of(100))
    def __init__(self, name: str, surface: int = 100):
        pass

If we create an object and then set a wrong value for the field, the exception contains a wrong reference to __init__ function:

o = House('helo')
o.surface = 150

Being able to customize all or a subset of generated getters and setters at once

New annotations @getters_wrapper(include, exclude) and @setters_wrapper(include, exclude), that would use @contextmanager or directly extend GeneratorContextManager in order to help users add functionality to all or some of the generated getters/setters with a function containing yield. For example in order to add a log message whenever properties are changed, the user would add a single log_property_change method to the class, decorated with @setters_wrapper.

error with python 3.5.2

Hi,

I'm using python 3.5.2
The import of autoclass.autoprops raise the following error:
(ps. I updated the module typing)

Traceback (most recent call last):
  File "C:/Users/~/Desktop/test_autoclass.py", line 1, in <module>
    from autoclass import autoargs, autoprops, setter_override
  File "C:\Program Files\Anaconda3\lib\site-packages\autoclass\__init__.py", line 2, in <module>
    from autoclass.autoprops import *
  File "C:\Program Files\Anaconda3\lib\site-packages\autoclass\autoprops.py", line 169, in <module>
    def _get_getter_fun(object_type: Type, property_name: str, property_type: Optional[Type], private_property_name: str):
  File "C:\Program Files\Anaconda3\lib\typing.py", line 649, in __getitem__
    return Union[arg, type(None)]
  File "C:\Program Files\Anaconda3\lib\typing.py", line 552, in __getitem__
    dict(self.__dict__), parameters, _root=True)
  File "C:\Program Files\Anaconda3\lib\typing.py", line 512, in __new__
    for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
  File "C:\Program Files\Anaconda3\lib\typing.py", line 512, in <genexpr>
    for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
  File "C:\Program Files\Anaconda3\lib\typing.py", line 1077, in __subclasscheck__
    if super().__subclasscheck__(cls):
  File "C:\Program Files\Anaconda3\lib\abc.py", line 225, in __subclasscheck__
    for scls in cls.__subclasses__():
TypeError: descriptor '__subclasses__' of 'type' object needs an argument

independent @autoeq with different behaviour than the one included in @autodict

Currently, only @autodict (or @autoclass with default option autodict=True) generates a __eq__ implementation, which is based on dictionary equality. It would be better to

  • have an independent way of generating the equality method (@autoeq),
  • and to change @autodict behaviour: rather than implementing a dictionary directly on the object, it could instead provide a as_dict() method which would be that view.

@autoprops argument name in setter is not correct

The setter function generated by @autoprops has always the same argument name : 'val'.
This was primarily done so that there is no dynamic compilation, but it leads to confusing error messages when used in combination with PEP484 type checkers:

from autoclass import autoclass
from numbers import Integral

# we use enforce runtime checker for this example
from enforce import runtime_validation, config
config(dict(mode='covariant'))  # to accept subclasses in validation

@runtime_validation
@autoclass
class House:
    def __init__(self, name: str, nb_floors: Integral = 1):
        pass

obj = House('my_house', 'red')

Leads to the following error message:

enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
       Argument 'val' was not of type <class 'numbers.Integral'>. Actual type was str.

The argument name is val in the error message, not nb_floors.

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.