Skip to content

Commit 9030d1e

Browse files
committed
wip version of __attrs_init_(). tests pass for py38
1 parent 612700c commit 9030d1e

File tree

3 files changed

+70
-35
lines changed

3 files changed

+70
-35
lines changed

src/attr/_make.py

+67-33
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
# Unique object for unequivocal getattr() defaults.
4747
_sentinel = object()
4848

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

5054
class _Nothing(object):
5155
"""
@@ -848,22 +852,38 @@ def add_hash(self):
848852

849853
return self
850854

851-
def add_init(self):
852-
self._cls_dict["__init__"] = self._add_method_dunders(
853-
_make_init(
854-
self._cls,
855-
self._attrs,
856-
self._has_post_init,
857-
self._frozen,
858-
self._slots,
859-
self._cache_hash,
860-
self._base_attr_map,
861-
self._is_exc,
855+
def add_init(self, init, auto_detect):
856+
should_implement_init = _determine_whether_to_implement(
857+
self._cls, init, auto_detect, ("__init__",)
858+
)
859+
attrs_init_method, init_method = _make_init(
860+
self._cls,
861+
self._attrs,
862+
self._has_post_init,
863+
self._frozen,
864+
self._slots,
865+
self._cache_hash,
866+
self._base_attr_map,
867+
self._is_exc,
868+
(
862869
self._on_setattr is not None
863-
and self._on_setattr is not setters.NO_OP,
864-
)
870+
and self._on_setattr is not setters.NO_OP
871+
),
872+
should_implement_init,
865873
)
866874

875+
self._cls_dict["__attrs_init__"] = self._add_method_dunders(
876+
attrs_init_method
877+
)
878+
if init_method is not None:
879+
self._cls_dict["__init__"] = self._add_method_dunders(init_method)
880+
else:
881+
if self._cache_hash:
882+
raise TypeError(
883+
"Invalid value for cache_hash. To use hash caching,"
884+
" init must be True."
885+
)
886+
867887
return self
868888

869889
def add_eq(self):
@@ -1367,17 +1387,7 @@ def wrap(cls):
13671387
)
13681388
builder.make_unhashable()
13691389

1370-
if _determine_whether_to_implement(
1371-
cls, init, auto_detect, ("__init__",)
1372-
):
1373-
builder.add_init()
1374-
else:
1375-
if cache_hash:
1376-
raise TypeError(
1377-
"Invalid value for cache_hash. To use hash caching,"
1378-
" init must be True."
1379-
)
1380-
1390+
builder.add_init(init, auto_detect)
13811391
return builder.build_class()
13821392

13831393
# maybe_cls's type depends on the usage of the decorator. It's a class
@@ -1836,6 +1846,7 @@ def _make_init(
18361846
base_attr_map,
18371847
is_exc,
18381848
has_global_on_setattr,
1849+
should_implement_init,
18391850
):
18401851
if frozen and has_global_on_setattr:
18411852
raise ValueError("Frozen classes can't use on_setattr.")
@@ -1872,6 +1883,7 @@ def _make_init(
18721883
is_exc,
18731884
needs_cached_setattr,
18741885
has_global_on_setattr,
1886+
should_implement_init,
18751887
)
18761888
locs = {}
18771889
bytecode = compile(script, unique_filename, "exec")
@@ -1893,10 +1905,16 @@ def _make_init(
18931905
unique_filename,
18941906
)
18951907

1896-
__init__ = locs["__init__"]
1897-
__init__.__annotations__ = annotations
1908+
__attrs_init__ = locs["__attrs_init__"]
1909+
__attrs_init__.__annotations__ = annotations
1910+
1911+
if "__init__" in locs:
1912+
__init__ = locs["__init__"]
1913+
__init__.__annotations__ = annotations
1914+
else:
1915+
__init__ = None
18981916

1899-
return __init__
1917+
return __attrs_init__, __init__
19001918

19011919

19021920
def _setattr(attr_name, value_var, has_on_setattr):
@@ -2011,6 +2029,7 @@ def _attrs_to_init_script(
20112029
is_exc,
20122030
needs_cached_setattr,
20132031
has_global_on_setattr,
2032+
should_implement_init,
20142033
):
20152034
"""
20162035
Return a script of an initializer for *attrs* and a dict of globals.
@@ -2084,7 +2103,7 @@ def fmt_setter_with_converter(
20842103
)
20852104
arg_name = a.name.lstrip("_")
20862105

2087-
has_factory = isinstance(a.default, Factory)
2106+
has_factory = Factory is not None and isinstance(a.default, Factory)
20882107
if has_factory and a.default.takes_self:
20892108
maybe_self = "self"
20902109
else:
@@ -2225,9 +2244,6 @@ def fmt_setter_with_converter(
22252244
names_for_globals[val_name] = a.validator
22262245
names_for_globals[attr_name] = a
22272246

2228-
if post_init:
2229-
lines.append("self.__attrs_post_init__()")
2230-
22312247
# because this is set only after __attrs_post_init is called, a crash
22322248
# will result if post-init tries to access the hash code. This seemed
22332249
# preferable to setting this beforehand, in which case alteration to
@@ -2263,12 +2279,30 @@ def fmt_setter_with_converter(
22632279
", " if args else "", # leading comma
22642280
", ".join(kw_only_args), # kw_only args
22652281
)
2282+
2283+
init_lines = ""
2284+
if should_implement_init:
2285+
init_lines = """\
2286+
2287+
def __init__(self, {args}):
2288+
args = locals()
2289+
del args['self']
2290+
self.__attrs_init__(**args)
2291+
{post_init_call}
2292+
""".format(
2293+
args=args,
2294+
post_init_call="self.__attrs_post_init__()" if post_init else "",
2295+
)
2296+
22662297
return (
22672298
"""\
2268-
def __init__(self, {args}):
2299+
def __attrs_init__(self, {args}):
22692300
{lines}
2301+
{init_lines}
22702302
""".format(
2271-
args=args, lines="\n ".join(lines) if lines else "pass"
2303+
args=args,
2304+
lines="\n ".join(lines) if lines else "pass",
2305+
init_lines=init_lines,
22722306
),
22732307
names_for_globals,
22742308
annotations,

tests/test_dunders.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def _add_init(cls, frozen):
5959
This function used to be part of _make. It wasn't used anymore however
6060
the tests for it are still useful to test the behavior of _make_init.
6161
"""
62-
cls.__init__ = _make_init(
62+
cls.__attrs_init__, cls.__init__ = _make_init(
6363
cls,
6464
cls.__attrs_attrs__,
6565
getattr(cls, "__attrs_post_init__", False),
@@ -69,6 +69,7 @@ def _add_init(cls, frozen):
6969
base_attr_map={},
7070
is_exc=False,
7171
has_global_on_setattr=False,
72+
should_implement_init=True,
7273
)
7374
return cls
7475

tests/test_make.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1526,7 +1526,7 @@ class C(object):
15261526
b.add_eq()
15271527
.add_order()
15281528
.add_hash()
1529-
.add_init()
1529+
.add_init(True, True)
15301530
.add_repr("ns")
15311531
.add_str()
15321532
.build_class()

0 commit comments

Comments
 (0)