Skip to content

Commit

Permalink
Add bases argument to make_class (#152)
Browse files Browse the repository at this point in the history
Fixes #152
  • Loading branch information
brainysmurf authored and hynek committed Mar 4, 2017
1 parent f232cc8 commit d86e28b
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The third digit is only for regressions.
17.1.0 (UNRELEASED)
-------------------


Backward-incompatible changes:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -49,6 +50,8 @@ Changes:
- Validators can now be defined conveniently inline by using the attribute as a decorator.
Check out the `examples <https://attrs.readthedocs.io/en/stable/examples.html#validators>`_ to see it in action!
`#143 <https://github.com/python-attrs/attrs/issues/143>`_
- ``attr.make_class`` now accepts the new keyword argument ``bases`` which allows for subclassing.
`#152 <https://github.com/python-attrs/attrs/pull/152>`_


----
Expand Down
11 changes: 11 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,17 @@ You can still have power over the attributes if you pass a dictionary of name: `
>>> i.y
[]

If you need to dynamically make a class with :func:`attr.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument:

.. doctest::

>>> class D(object):
... def __eq__(self, other):
... return True # arbitrary example
>>> C = attr.make_class("C", {}, bases=(D,), cmp=False)
>>> isinstance(C(), D)
True

Sometimes, you want to have your class's ``__init__`` method do more than just
the initialization, validation, etc. that gets done for you automatically when
using ``@attr.s``.
Expand Down
8 changes: 6 additions & 2 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ class Factory(object):
factory = attr()


def make_class(name, attrs, **attributes_arguments):
def make_class(name, attrs, bases=(object,), **attributes_arguments):
"""
A quick way to create a new class called *name* with *attrs*.
Expand All @@ -956,10 +956,14 @@ def make_class(name, attrs, **attributes_arguments):
attributes.
:type attrs: :class:`list` or :class:`dict`
:param tuple bases: Classes that the new class will subclass.
:param attributes_arguments: Passed unmodified to :func:`attr.s`.
:return: A new class with *attrs*.
:rtype: type
.. versionadded:: 17.1.0 *bases*
"""
if isinstance(attrs, dict):
cls_dict = attrs
Expand All @@ -968,4 +972,4 @@ def make_class(name, attrs, **attributes_arguments):
else:
raise TypeError("attrs argument must be a dict or a list.")

return attributes(**attributes_arguments)(type(name, (object,), cls_dict))
return attributes(**attributes_arguments)(type(name, bases, cls_dict))
14 changes: 14 additions & 0 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,20 @@ def test_catches_wrong_attrs_type(self):
"attrs argument must be a dict or a list.",
) == e.value.args

def test_bases(self):
"""
Parameter bases default to (object,) and subclasses correctly
"""
class D(object):
pass

cls = make_class("C", {})
assert cls.__mro__[-1] == object

cls = make_class("C", {}, bases=(D,))
assert D in cls.__mro__
assert isinstance(cls(), D)


class TestFields(object):
"""
Expand Down

0 comments on commit d86e28b

Please sign in to comment.