Fix pickling for instances of classes created using make_class() (#282)
This commit is contained in:
parent
1bb8fbd5ef
commit
c1adf5cfc6
|
@ -0,0 +1 @@
|
|||
Instances of classes created using ``attr.make_class()`` can now be pickled.
|
|
@ -601,7 +601,7 @@ You can still have power over the attributes if you pass a dictionary of name: `
|
|||
... repr=False)
|
||||
>>> i = C()
|
||||
>>> i # no repr added!
|
||||
<attr._make.C object at ...>
|
||||
<__main__.C object at ...>
|
||||
>>> i.x
|
||||
42
|
||||
>>> i.y
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
import hashlib
|
||||
import linecache
|
||||
import sys
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
|
@ -1237,13 +1238,22 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
|
|||
raise TypeError("attrs argument must be a dict or a list.")
|
||||
|
||||
post_init = cls_dict.pop("__attrs_post_init__", None)
|
||||
return _attrs(
|
||||
these=cls_dict, **attributes_arguments
|
||||
)(type(
|
||||
type_ = type(
|
||||
name,
|
||||
bases,
|
||||
{} if post_init is None else {"__attrs_post_init__": post_init}
|
||||
))
|
||||
)
|
||||
# For pickling to work, the __module__ variable needs to be set to the
|
||||
# frame where the class is created. Bypass this step in environments where
|
||||
# sys._getframe is not defined (Jython for example) or sys._getframe is not
|
||||
# defined for arguments greater than 0 (IronPython)
|
||||
try:
|
||||
type_.__module__ = sys._getframe(1).f_globals.get('__name__',
|
||||
'__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
|
||||
return _attrs(these=cls_dict, **attributes_arguments)(type_)
|
||||
|
||||
|
||||
# These are required by within this module so we define them here and merely
|
||||
|
|
|
@ -103,6 +103,9 @@ class WithMetaSlots(object):
|
|||
pass
|
||||
|
||||
|
||||
FromMakeClass = attr.make_class('FromMakeClass', ['x'])
|
||||
|
||||
|
||||
class TestDarkMagic(object):
|
||||
"""
|
||||
Integration tests.
|
||||
|
@ -218,7 +221,8 @@ class TestDarkMagic(object):
|
|||
|
||||
@pytest.mark.parametrize("cls",
|
||||
[C1, C1Slots, C2, C2Slots, Super, SuperSlots,
|
||||
Sub, SubSlots, Frozen, FrozenNoSlots])
|
||||
Sub, SubSlots, Frozen, FrozenNoSlots,
|
||||
FromMakeClass])
|
||||
@pytest.mark.parametrize("protocol",
|
||||
range(2, pickle.HIGHEST_PROTOCOL + 1))
|
||||
def test_pickle_attributes(self, cls, protocol):
|
||||
|
@ -230,7 +234,8 @@ class TestDarkMagic(object):
|
|||
|
||||
@pytest.mark.parametrize("cls",
|
||||
[C1, C1Slots, C2, C2Slots, Super, SuperSlots,
|
||||
Sub, SubSlots, Frozen, FrozenNoSlots])
|
||||
Sub, SubSlots, Frozen, FrozenNoSlots,
|
||||
FromMakeClass])
|
||||
@pytest.mark.parametrize("protocol",
|
||||
range(2, pickle.HIGHEST_PROTOCOL + 1))
|
||||
def test_pickle_object(self, cls, protocol):
|
||||
|
|
|
@ -5,6 +5,7 @@ Tests for `attr._make`.
|
|||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
|
@ -456,7 +457,7 @@ class TestMakeClass(object):
|
|||
attributes_arguments are passed to attributes
|
||||
"""
|
||||
C = make_class("C", ["x"], repr=False)
|
||||
assert repr(C(1)).startswith("<attr._make.C object at 0x")
|
||||
assert repr(C(1)).startswith("<tests.test_make.C object at 0x")
|
||||
|
||||
def test_catches_wrong_attrs_type(self):
|
||||
"""
|
||||
|
@ -494,6 +495,14 @@ class TestMakeClass(object):
|
|||
|
||||
assert not isinstance(x, _CountingAttr)
|
||||
|
||||
def test_missing_sys_getframe(self, monkeypatch):
|
||||
"""
|
||||
`make_class()` does not fail when `sys._getframe()` is not available.
|
||||
"""
|
||||
monkeypatch.delattr(sys, '_getframe')
|
||||
C = make_class("C", ["x"])
|
||||
assert C.__attrs_attrs__
|
||||
|
||||
|
||||
class TestFields(object):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue