Skip to content

Usage details

1. Fields

If you use another library to define your class fields (pyfields is the only one supported as of 2.1.0), you can skip this section and jump directly to section 2.. Otherwise, these two decorators can be useful to do something equivalent.

@autoargs

Automatically affects the contents of a function to self. Initial code and test examples from this answer from utnubu.

A few illustrative examples can be found below.

  • Basic functionality, no customization - all constructor arguments are auto-assigned:
from autoclass import autoargs

class A(object):
    @autoargs
    def __init__(self, foo, path, debug=False):
        pass

# Test : 
# -- create an instance
a = A('rhubarb', 'pie', debug=True)

# -- check that the fields exist and have the correct value
assert a.foo == 'rhubarb'
assert a.path == 'pie'
assert a.debug == True
  • Basic functionality, with special case of variable arguments *args. Note that the variable arguments are stored in a single attribute:
class B(object):
    @autoargs
    def __init__(self, foo, path, debug=False, *args):
        pass

# Test : 
# -- create an instance
a = B('rhubarb', 'pie', True, 100, 101)
# -- check that the fields exist and have the correct value
assert a.foo == 'rhubarb'
assert a.path == 'pie'
assert a.debug == True
# -- *args is in a single attribute
assert a.args == (100, 101)
  • Basic functionality, with special case of variable arguments *args and keyword arguments **kw. Note that *args are stored in a single attribute and now **kw are, too (for consistency reasons this changed in 1.10.0).
class C(object):
    @autoargs
    def __init__(self, foo, path, debug=False, *args, **kw):
        pass

# Test : 
# -- create an instance
a = C('rhubarb', 'pie', True, 100, 101, verbose=True, bar='bar')
# -- check that the fields exist and have the correct value
assert a.foo == 'rhubarb'
assert a.path == 'pie'
assert a.debug == True
# -- *args is in a single attribute
assert a.args == (100, 101)
# -- **kw is in a single attribute too
assert a.kw == dict(verbose=True, bar='bar')
  • Explicit tuple or list of names to include:
class C(object):
    @autoargs(include=['bar', 'baz', 'verbose'])
    def __init__(self, foo, bar, baz, verbose=False):
        pass

# Test : 
# -- create an instance
a = C('rhubarb', 'pie', 1)
# -- check that the fields exist and have the correct value
assert a.bar == 'pie'
assert a.baz == 1
assert a.verbose == False
# -- check that a non-included field does not exist
print(a.foo)# raises AttributeError
  • Explicit tuple or list of names to exclude:
class C(object):
    @autoargs(exclude=('bar', 'baz', 'verbose'))
    def __init__(self, foo, bar, baz, verbose=False):
        pass

# Test : 
# -- create an instance
a = C('rhubarb', 'pie', 1)
# -- check that the fields exist and have the correct value
assert a.foo == 'rhubarb'
# -- check that the non-included fields do not exist
print(a.bar)  # raises AttributeError
print(a.baz)  # raises AttributeError
print(a.verbose)  # raises AttributeError

Finally note that @autoargs is automatically applied when you decorate the whole class with @autoclass, see below.

@autoprops

Automatically generates all properties getters and setters from the class constructor.

  • Basic functionality, no customization - all constructor arguments become properties:
@autoprops
class FooConfigA(object):

    @autoargs
    def __init__(self, a: str, b: List[str]):
        pass

t = FooConfigA('rhubarb', ['pie', 'pie2'])

# there are no contracts on the generated setters
t.a=''
t.b=['r','']
# check that the generated getters work
assert t.a == ''
assert t.b[0] == 'r'
  • You can include or exclude some properties in the list of those generated with :
@autoprops(include=('a', 'b'))
class Foo(object):
    ...

@autoprops(exclude=('b'))
class Bar(object):
    ...
  • if a PyContracts @contract annotation exist on the __init__ method, mentioning a contract for a given parameter, the parameter contract will be added on the generated setter method:
from contracts import ContractNotRespected, contract

@autoprops
class FooConfigB(object):

    @autoargs
    @contract(a='str[>0]', b='list[>0](str[>0])')
    def __init__(self, a: str, b: List[str]):
        pass

t = FooConfigB('rhubarb', ['pie', 'pie2'])

# check that the generated getters work
t.b=['r']
assert t.b[0] == 'r'

# check that there are contracts on the generated setters
t.a = ''  # raises ContractNotRespected
t.b = ['r','']  # raises ContractNotRespected
  • if a @validate annotation (from valid8 library) exist on the __init__ method, mentioning a contract for a given parameter, the parameter contract will be added on the generated setter method:
# we use valid8 as the value validator
from valid8 import validate

@autoprops
class FooConfigC(object):

    @autoargs
    @validate(a=minlens(0))
    def __init__(self, a: str):
        pass

t = FooConfigC('rhubarb')

# check that the generated getters work
t.a='r'
assert t.a == 'r'

# check that there are validators on the generated setters
t.a = ''  # raises ValidationError
  • The user may override the generated getter and/or setter by creating them explicitly in the class and annotating them with @getter_override or @setter_override. Note that the contract will still be dynamically added on the setter, even if the setter already has one (in such case a UserWarning will be issued)
@autoprops
class FooConfigD(object):

    @autoargs
    @contract(a='str[>0]', b='list[>0](str[>0])')
    def __init__(self, a: str, b: List[str]):
        pass

    @getter_override
    def a(self):
        # in addition to getting the fields we'd like to print something
        print('a is being read. Its value is \'' + str(self._a) + '\'')
        return self._a

    @setter_override(attribute='b')
    def another_name(self, toto: List[str]):
        # in addition to setting the fields we'd like to print something
        print('Property \'b\' was set to \'' + str(toto) + '\'')
        self._b = toto

t = FooConfigD('rhubarb', ['pie', 'pie2'])

# check that we can still read a's value
assert t.a == 'rhubarb'

# check that 'b' still has a getter generated
t.b = ['eh', 'oh']
assert t.b == ['eh', 'oh']

# check that 'a' still has a contract on its setter
t.a = ''  # raises ContractNotRespected

# check that 'b' still has a contract on its setter
t.b=['']  # raises ContractNotRespected
  • Note: you may also perform the same action without decorator, using autoprops_decorate(cls).
# we don't use @autoprops here
class FooConfigD(object):
    @autoargs
    @contract(a='str[>0]', b='list[>0](str[>0])')
    def __init__(self, a: str, b: List[str]):
        pass

# we execute it here
autoprops_decorate(FooConfigD)

t = FooConfigD('rhubarb', ['pie', 'pie2'])

# check that the generated getters work
t.b = ['r']
assert t.b[0] == 'r'

# check that there are contracts on the generated setters
t.a = ''  # raises ContractNotRespected
t.b = ['r','']  # raises ContractNotRespected

Finally note that @autoprops is automatically applied when you decorate the whole class with @autoclass, see below.

2. Facets

These facets all support two ways of defining the fields:

  • from pyfields
  • or from the constructor signature (just like @autoprops does)

@autodict

Automatically generates a read-only dictionary view on top of the object. It does several things:

  • it adds collections.Mapping to the list of parent classes (i.e. to the class' __bases__)
  • it generates __len__, __iter__ and __getitem__ in order for the appropriate fields to be exposed in the dict view. Parameters allow to customize the list of fields that will be visible. Note that any methods with the same name will be overridden.
  • if only_known_fields is True (default), it generates a static from_dict method in the class corresponding to a call to the constructor with the unfolded dict. Note that this method may be overridden by the user.
  • if __eq__ is not implemented on this class, it generates a version that handles the case self == other where other is of the same type. In that case the dictionary equality is used. Other equality tests remain unchanged.
  • if __str__ and __repr__ are not implemented on this class, it generates them too.

Examples:

  • Basic functionality, no customization - all constructor arguments can be viewed in the dict:
@autodict
class A(object):
    def __init__(self, a: int, b: str):
        self.a = a
        self.b = b

o = A(1, 'r')
# o behaves like a read-only dict
assert o == dict(o)
assert o == {'a': 1, 'b': 'r'}

# you can create an object from a dict too, thanks to the generated class function
p = A.from_dict({'a': 1, 'b': 'r'})
assert p == o

# str and repr methods show interesting stuff
str(p)  # "A({'a': 1, 'b': 'r'})"
repr(p)  # "A({'a': 1, 'b': 'r'})"
  • You can obviously combine it with @autoargs:
@autodict
class B(object):
    @autoargs
    def __init__(self, a: int, b: str):
        pass

o = B(1, 'r')
# same results
assert o == {'a': 1, 'b': 'r'}
p = B.from_dict({'a': 1, 'b': 'r'})
assert p == o
  • Note that by default only fields with the same name than constructor arguments are visible:
@autodict
class C(object):
    @autoargs
    def __init__(self, a: str, b: List[str]):
        self.non_constructor_arg = 't'
        self._private = 1
        self.__class_private = 't'

o = C(1, 'r')
# only fields corresponding to constructor arguments are visible
assert o == {'a': 1, 'b': 'r'}
  • You can decide to open to all object fields, including or excluding (default) the fields that are not arguments of the constructor, and including or excluding (default) the class-private ones. Note that class-private attributes will be visible with their usual scrambled name:
@autodict(only_known_fields=False, only_public_fields=False)
class D(object):
    @autoargs
    def __init__(self, a: str, b: List[str]):
        self.non_constructor_arg = 'b'
        self._private = 1
        self.__class_private = 't'

o = D(1, 'r')
# o behaves like a read-only dict, all fields are now visible
assert o == dict(o)
assert o == {'a': 1, 'b': 'r',
             'non_constructor_arg': 'b',
             '_private': 1,
             '_D__class_private': 't'}  # notice the name
  • In addition, you can include or exclude some names in the list of visible fields with one of include or exclude:
@autodict(include=['a', 'b'], ...)
class Foo(object):
    ...

@autodict(exclude=['b'], ...)
class Bar(object):
    ...

Finally note that @autodict is automatically applied when you decorate the whole class with @autoclass, see below.

@autohash

A decorator to makes objects of the class implement hash, so that they can be used correctly for example in sets. Parameters allow to customize the list of attributes that are taken into account in the hash.

Examples:

  • Basic functionality, no customization - all object fields are used in the hash:
@autohash
class A(object):
    def __init__(self, a: int, b: str):
        self.a = a
        self.b = b

o = A(1, 'r')
o._test = 2

# o is hashable
assert hash(o) == hash((1, 'r', 2))

p = A(1, 'r')
p._test = 2
# o and p have identical hash
assert hash(o) == hash(p)

# dynamic and private fields are taken into account by default
p._test = 3
assert hash(o) != hash(p)
  • You can decide to restrict the hash to only the fields that are constructor arguments, or to only the fields that are public:
from random import random

@autohash(only_known_fields=True, only_public_fields=True)
class D(object):
    @autoargs
    def __init__(self, a: str, _b: str):
        self.non_constructor_arg = random()
        self._private = random()
        self.__class_private = random()

o = D(1, 'r')
p = D(1, 'r')

# o and p have the same hash because only the constructor arguments are taken into account
assert hash(o) == hash(p)
assert hash(o) == hash((1, 'r'))
  • In addition, you can include or exclude some names in the list of visible fields with one of include or exclude:
@autohash(include=['a', 'b'], ...)
class Foo(object):
    ...

@autohash(exclude=['b'], ...)
class Bar(object):
    ...

Finally note that @autohash is automatically applied when you decorate the whole class with @autoclass, see below.

@autorepr

This decorator is useful if you wish to add a string representation to your class but you do not wish to use the entire @autodict. It just creates the __str__ and __repr__ methods.

@autoeq

This decorator is useful if you wish to add an equality method to your class but you do not wish to use the entire @autodict. It just creates the __eq__ method.

@autoslots

Automatically create slots for each attribute. Parameters allow to customize the list of attributes that are taken into account.

Examples:

  • Basic functionality, no customization - all object fields are used in the slots, and a __weakref__ is automatically added:
from autoclass import autoslots

@autoslots
class Foo(object):
    def __init__(self, foo1, foo2=0):
        self.foo1 = foo1
        self.foo2 = foo2

f = Foo(1)
assert not hasattr(f, '__dict__')
assert f.foo1 == 1
assert f.foo2 == 0
  • private slot names can be created instead of public ones:
from autoclass import autoslots, autoargs, autoprops

@autoprops
@autoslots(use_public_names=False)
class Foo(object):
    @autoargs
    def __init__(self, foo1, foo2=0):
        pass

f = Foo(1)
assert not hasattr(f, '__dict__')
assert f.foo1 == 1
assert f.foo2 == 0
  • In addition, you can include or exclude some names in the list of fields with one of include or exclude:
@autoslots(include=['a', 'b'], ...)
class Foo(object):
    ...

@autoslots(exclude=['b'], ...)
class Bar(object):
    ...

Finally note that @autoslots is not automatically applied when you decorate the whole class with @autoclass, you have to use @autoclass(autoslots=True) see below.

@autoclass

Applies all or part of the above decorators at once. Useful if you want to make the most from this library.

  • Basic functionality, no customization - all constructor arguments become properties that are auto-assigned in constructor, and the object behaves like a dict and can be created from a dict:
from numbers import Integral
from typing import Optional

# we will use enforce as the runtime checker
import enforce as en
from enforce import runtime_validation
en.config(dict(mode='covariant'))  # allow subclasses when validating types

# we use valid8 as the value validator
from valid8 import validate

# class definition
@runtime_validation
@autoclass
class AllOfTheAbove:
    @validate(a=gt(1), c=minlen(1))
    def __init__(self, a: Integral, b: Boolean, c: Optional[List[str]] = None):
        pass

# instance creation
o = AllOfTheAbove(a=2, b=True)

# @autoargs works
assert o.a == 2

# @autoprops works, in combination with any runtime checker (here demonstrated with enforce)
o.b = 1  # !RuntimeTypeError Argument 'b' was not of type Boolean. Actual type was int.

# @autodict works
assert o == {'a': 2, 'b': True, 'c': None}
assert AllOfTheAbove.from_dict(o) == o
assert dict(**o) == o
  • you can also disable part of the features :
@autoclass(autodict=False)
class PartsOfTheAbove:
    @validate(a=gt(1), c=minlen(1))
    def __init__(self, a: Integral, b: Boolean, c: Optional[List[str]] = None):
        pass

# instance creation
o = PartsOfTheAbove(a=2, b=True)
print(o)  # works: autorepr is automatically enabled when autodict=False

assert o == {'a': 2, 'b': True, 'c': None}  # AssertionError
assert o == PartsOfTheAbove(a=2, b=True)  # works: autoeq is automatically enabled 
assert PartsOfTheAbove.from_dict(o) == o  # AttributeError: 'PartsOfTheAbove' has no attribute 'from_dict'
assert dict(**o) == o  # TypeError: argument after ** must be a mapping

Alternative to decorators: manual function wrappers

Equivalent manual wrapper methods are provided for all decorators in this library: autoargs_decorate(init_func, include, exclude), autoprops_decorate(cls, include, exclude), autoprops_override_decorate(func, attribute, is_getter), autodict_decorate(cls, include, exclude, only_known_fields, only_public_fields), autoclass_decorate(cls, include, exclude, autoargs, autoprops, autodict), autoslots_decorate(cls, include, exclude, use_public_names, add_weakref_slot)

Therefore you can do:

from autoclass import autoclass_decorate

class A:
    ...

A = autoclass_decorate(A)