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

pass bases keyword argument to make_class #152

Closed
4 changes: 4 additions & 0 deletions CHANGELOG.rst
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,9 @@ 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>`_
- Now ``attr.make_class`` accepts new keyword argument ``bases``.
Previously, if you were creating a class dynamically the call to ``type(n, b, d)`` was hard-coded to be ``(object,)``. By exposing this keyword it's possible to dynamically create subclassed classes. Use cases for this may be, for example, class decorators.
`#152 <https://github.com/python-attrs/attrs/pull/152>`_


----
Expand Down
11 changes: 11 additions & 0 deletions docs/examples.rst
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.mak_class` and it needs to be a subclass of D, use the ``bases`` argument, which is just passed onto ``type(name, bases, dict)``:

.. doctest::

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

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
7 changes: 5 additions & 2 deletions src/attr/_make.py
Expand Up @@ -945,13 +945,16 @@ 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*.

:param name: The name for the new class.
:type name: str

:param bases: Passed on as bases arg in type(n, b, d)
:type bases: :class:`tuple`

:param attrs: A list of names or a dictionary of mappings of names to
attributes.
:type attrs: :class:`list` or :class:`dict`
Expand All @@ -968,4 +971,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))
13 changes: 13 additions & 0 deletions tests/test_make.py
Expand Up @@ -432,6 +432,19 @@ 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__


class TestFields(object):
"""
Expand Down