Skip to content

Commit

Permalink
wip version of __attrs_init_(). tests pass for py38
Browse files Browse the repository at this point in the history
  • Loading branch information
indigoviolet committed Dec 13, 2020
1 parent 612700c commit 9030d1e
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 35 deletions.
100 changes: 67 additions & 33 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
# Unique object for unequivocal getattr() defaults.
_sentinel = object()

# Need a forward definition of Factory because _make_init() refers to
# it, but since Factory is an attrs class, it calls _make_init()
Factory = None


class _Nothing(object):
"""
Expand Down Expand Up @@ -848,22 +852,38 @@ def add_hash(self):

return self

def add_init(self):
self._cls_dict["__init__"] = self._add_method_dunders(
_make_init(
self._cls,
self._attrs,
self._has_post_init,
self._frozen,
self._slots,
self._cache_hash,
self._base_attr_map,
self._is_exc,
def add_init(self, init, auto_detect):
should_implement_init = _determine_whether_to_implement(
self._cls, init, auto_detect, ("__init__",)
)
attrs_init_method, init_method = _make_init(
self._cls,
self._attrs,
self._has_post_init,
self._frozen,
self._slots,
self._cache_hash,
self._base_attr_map,
self._is_exc,
(
self._on_setattr is not None
and self._on_setattr is not setters.NO_OP,
)
and self._on_setattr is not setters.NO_OP
),
should_implement_init,
)

self._cls_dict["__attrs_init__"] = self._add_method_dunders(
attrs_init_method
)
if init_method is not None:
self._cls_dict["__init__"] = self._add_method_dunders(init_method)
else:
if self._cache_hash:
raise TypeError(
"Invalid value for cache_hash. To use hash caching,"
" init must be True."
)

return self

def add_eq(self):
Expand Down Expand Up @@ -1367,17 +1387,7 @@ def wrap(cls):
)
builder.make_unhashable()

if _determine_whether_to_implement(
cls, init, auto_detect, ("__init__",)
):
builder.add_init()
else:
if cache_hash:
raise TypeError(
"Invalid value for cache_hash. To use hash caching,"
" init must be True."
)

builder.add_init(init, auto_detect)
return builder.build_class()

# maybe_cls's type depends on the usage of the decorator. It's a class
Expand Down Expand Up @@ -1836,6 +1846,7 @@ def _make_init(
base_attr_map,
is_exc,
has_global_on_setattr,
should_implement_init,
):
if frozen and has_global_on_setattr:
raise ValueError("Frozen classes can't use on_setattr.")
Expand Down Expand Up @@ -1872,6 +1883,7 @@ def _make_init(
is_exc,
needs_cached_setattr,
has_global_on_setattr,
should_implement_init,
)
locs = {}
bytecode = compile(script, unique_filename, "exec")
Expand All @@ -1893,10 +1905,16 @@ def _make_init(
unique_filename,
)

__init__ = locs["__init__"]
__init__.__annotations__ = annotations
__attrs_init__ = locs["__attrs_init__"]
__attrs_init__.__annotations__ = annotations

if "__init__" in locs:
__init__ = locs["__init__"]
__init__.__annotations__ = annotations
else:
__init__ = None

return __init__
return __attrs_init__, __init__


def _setattr(attr_name, value_var, has_on_setattr):
Expand Down Expand Up @@ -2011,6 +2029,7 @@ def _attrs_to_init_script(
is_exc,
needs_cached_setattr,
has_global_on_setattr,
should_implement_init,
):
"""
Return a script of an initializer for *attrs* and a dict of globals.
Expand Down Expand Up @@ -2084,7 +2103,7 @@ def fmt_setter_with_converter(
)
arg_name = a.name.lstrip("_")

has_factory = isinstance(a.default, Factory)
has_factory = Factory is not None and isinstance(a.default, Factory)
if has_factory and a.default.takes_self:
maybe_self = "self"
else:
Expand Down Expand Up @@ -2225,9 +2244,6 @@ def fmt_setter_with_converter(
names_for_globals[val_name] = a.validator
names_for_globals[attr_name] = a

if post_init:
lines.append("self.__attrs_post_init__()")

# because this is set only after __attrs_post_init is called, a crash
# will result if post-init tries to access the hash code. This seemed
# preferable to setting this beforehand, in which case alteration to
Expand Down Expand Up @@ -2263,12 +2279,30 @@ def fmt_setter_with_converter(
", " if args else "", # leading comma
", ".join(kw_only_args), # kw_only args
)

init_lines = ""
if should_implement_init:
init_lines = """\
def __init__(self, {args}):
args = locals()
del args['self']
self.__attrs_init__(**args)
{post_init_call}
""".format(
args=args,
post_init_call="self.__attrs_post_init__()" if post_init else "",
)

return (
"""\
def __init__(self, {args}):
def __attrs_init__(self, {args}):
{lines}
{init_lines}
""".format(
args=args, lines="\n ".join(lines) if lines else "pass"
args=args,
lines="\n ".join(lines) if lines else "pass",
init_lines=init_lines,
),
names_for_globals,
annotations,
Expand Down
3 changes: 2 additions & 1 deletion tests/test_dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _add_init(cls, frozen):
This function used to be part of _make. It wasn't used anymore however
the tests for it are still useful to test the behavior of _make_init.
"""
cls.__init__ = _make_init(
cls.__attrs_init__, cls.__init__ = _make_init(
cls,
cls.__attrs_attrs__,
getattr(cls, "__attrs_post_init__", False),
Expand All @@ -69,6 +69,7 @@ def _add_init(cls, frozen):
base_attr_map={},
is_exc=False,
has_global_on_setattr=False,
should_implement_init=True,
)
return cls

Expand Down
2 changes: 1 addition & 1 deletion tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,7 @@ class C(object):
b.add_eq()
.add_order()
.add_hash()
.add_init()
.add_init(True, True)
.add_repr("ns")
.add_str()
.build_class()
Expand Down

0 comments on commit 9030d1e

Please sign in to comment.