@@ -61,7 +61,7 @@ def __hash__(self):
61
61
62
62
def attrib (default = NOTHING , validator = None ,
63
63
repr = True , cmp = True , hash = None , init = True ,
64
- convert = None , metadata = {}, type = None ):
64
+ convert = None , metadata = {}, type = None , kwonly = False ):
65
65
"""
66
66
Create a new attribute on a class.
67
67
@@ -130,11 +130,15 @@ def attrib(default=NOTHING, validator=None,
130
130
This argument is provided for backward compatibility.
131
131
Regardless of the approach used, the type will be stored on
132
132
``Attribute.type``.
133
+ :param kwonly: Make this attribute keyword-only (Python 3+)
134
+ in the generated ``__init__`` (if ``init`` is ``False``, this
135
+ parameter is simply ignored).
133
136
134
137
.. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
135
138
.. versionchanged:: 17.1.0
136
139
*hash* is ``None`` and therefore mirrors *cmp* by default .
137
140
.. versionadded:: 17.3.0 *type*
141
+ .. versionadded:: 17.3.0 *kwonly*
138
142
"""
139
143
if hash is not None and hash is not True and hash is not False :
140
144
raise TypeError (
@@ -150,6 +154,7 @@ def attrib(default=NOTHING, validator=None,
150
154
convert = convert ,
151
155
metadata = metadata ,
152
156
type = type ,
157
+ kwonly = kwonly ,
153
158
)
154
159
155
160
@@ -257,17 +262,31 @@ def _transform_attrs(cls, these):
257
262
)
258
263
259
264
had_default = False
265
+ was_kwonly = False
260
266
for a in attrs :
261
- if had_default is True and a .default is NOTHING and a .init is True :
267
+ if (was_kwonly is False and had_default is True and
268
+ a .default is NOTHING and a .init is True and
269
+ a .kwonly is False ):
262
270
raise ValueError (
263
271
"No mandatory attributes allowed after an attribute with a "
264
272
"default value or factory. Attribute in question: {a!r}"
265
273
.format (a = a )
266
274
)
267
- elif had_default is False and \
268
- a .default is not NOTHING and \
269
- a .init is not False :
275
+ elif (had_default is False and
276
+ a .default is not NOTHING and
277
+ a .init is not False and
278
+ # Keyword-only attributes can be specified after keyword-only
279
+ # attributes with default values.
280
+ a .kwonly is False ):
270
281
had_default = True
282
+ if was_kwonly is True and a .kwonly is False :
283
+ raise ValueError (
284
+ "Non keyword-only attributes are not allowed after a "
285
+ "keyword-only attribute. Attribute in question: {a!r}"
286
+ .format (a = a )
287
+ )
288
+ if was_kwonly is False and a .init is True and a .kwonly is True :
289
+ was_kwonly = True
271
290
272
291
return _Attributes ((attrs , super_attrs ))
273
292
@@ -913,6 +932,7 @@ def fmt_setter_with_converter(attr_name, value_var):
913
932
}
914
933
915
934
args = []
935
+ kwonly_args = []
916
936
attrs_to_validate = []
917
937
918
938
# This is a dictionary of names to validator and converter callables.
@@ -960,19 +980,25 @@ def fmt_setter_with_converter(attr_name, value_var):
960
980
.format (attr_name = attr_name )
961
981
))
962
982
elif a .default is not NOTHING and not has_factory :
963
- args .append (
964
- "{arg_name}=attr_dict['{attr_name}'].default" .format (
965
- arg_name = arg_name ,
966
- attr_name = attr_name ,
967
- )
983
+ arg = "{arg_name}=attr_dict['{attr_name}'].default" .format (
984
+ arg_name = arg_name ,
985
+ attr_name = attr_name ,
968
986
)
987
+ if a .kwonly :
988
+ kwonly_args .append (arg )
989
+ else :
990
+ args .append (arg )
969
991
if a .convert is not None :
970
992
lines .append (fmt_setter_with_converter (attr_name , arg_name ))
971
993
names_for_globals [_init_convert_pat .format (a .name )] = a .convert
972
994
else :
973
995
lines .append (fmt_setter (attr_name , arg_name ))
974
996
elif has_factory :
975
- args .append ("{arg_name}=NOTHING" .format (arg_name = arg_name ))
997
+ arg = "{arg_name}=NOTHING" .format (arg_name = arg_name )
998
+ if a .kwonly :
999
+ kwonly_args .append (arg )
1000
+ else :
1001
+ args .append (arg )
976
1002
lines .append ("if {arg_name} is not NOTHING:"
977
1003
.format (arg_name = arg_name ))
978
1004
init_factory_name = _init_factory_pat .format (a .name )
@@ -994,7 +1020,10 @@ def fmt_setter_with_converter(attr_name, value_var):
994
1020
))
995
1021
names_for_globals [init_factory_name ] = a .default .factory
996
1022
else :
997
- args .append (arg_name )
1023
+ if a .kwonly :
1024
+ kwonly_args .append (arg_name )
1025
+ else :
1026
+ args .append (arg_name )
998
1027
if a .convert is not None :
999
1028
lines .append (fmt_setter_with_converter (attr_name , arg_name ))
1000
1029
names_for_globals [_init_convert_pat .format (a .name )] = a .convert
@@ -1014,11 +1043,17 @@ def fmt_setter_with_converter(attr_name, value_var):
1014
1043
if post_init :
1015
1044
lines .append ("self.__attrs_post_init__()" )
1016
1045
1046
+ args = ", " .join (args )
1047
+ if kwonly_args :
1048
+ args += "{leading_comma}*, {kwonly_args}" .format (
1049
+ leading_comma = ", " if args else "" ,
1050
+ kwonly_args = ", " .join (kwonly_args )
1051
+ )
1017
1052
return """\
1018
1053
def __init__(self, {args}):
1019
1054
{lines}
1020
1055
""" .format (
1021
- args = ", " . join ( args ) ,
1056
+ args = args ,
1022
1057
lines = "\n " .join (lines ) if lines else "pass" ,
1023
1058
), names_for_globals
1024
1059
@@ -1033,11 +1068,11 @@ class Attribute(object):
1033
1068
"""
1034
1069
__slots__ = (
1035
1070
"name" , "default" , "validator" , "repr" , "cmp" , "hash" , "init" ,
1036
- "convert" , "metadata" , "type"
1071
+ "convert" , "metadata" , "type" , "kwonly"
1037
1072
)
1038
1073
1039
1074
def __init__ (self , name , default , validator , repr , cmp , hash , init ,
1040
- convert = None , metadata = None , type = None ):
1075
+ convert = None , metadata = None , type = None , kwonly = False ):
1041
1076
# Cache this descriptor here to speed things up later.
1042
1077
bound_setattr = _obj_setattr .__get__ (self , Attribute )
1043
1078
@@ -1052,6 +1087,7 @@ def __init__(self, name, default, validator, repr, cmp, hash, init,
1052
1087
bound_setattr ("metadata" , (metadata_proxy (metadata ) if metadata
1053
1088
else _empty_metadata_singleton ))
1054
1089
bound_setattr ("type" , type )
1090
+ bound_setattr ("kwonly" , kwonly )
1055
1091
1056
1092
def __setattr__ (self , name , value ):
1057
1093
raise FrozenInstanceError ()
@@ -1117,20 +1153,20 @@ class _CountingAttr(object):
1117
1153
likely the result of a bug like a forgotten `@attr.s` decorator.
1118
1154
"""
1119
1155
__slots__ = ("counter" , "_default" , "repr" , "cmp" , "hash" , "init" ,
1120
- "metadata" , "_validator" , "convert" , "type" )
1156
+ "metadata" , "_validator" , "convert" , "type" , "kwonly" )
1121
1157
__attrs_attrs__ = tuple (
1122
1158
Attribute (name = name , default = NOTHING , validator = None ,
1123
- repr = True , cmp = True , hash = True , init = True )
1159
+ repr = True , cmp = True , hash = True , init = True , kwonly = False )
1124
1160
for name
1125
1161
in ("counter" , "_default" , "repr" , "cmp" , "hash" , "init" ,)
1126
1162
) + (
1127
1163
Attribute (name = "metadata" , default = None , validator = None ,
1128
- repr = True , cmp = True , hash = False , init = True ),
1164
+ repr = True , cmp = True , hash = False , init = True , kwonly = False ),
1129
1165
)
1130
1166
cls_counter = 0
1131
1167
1132
1168
def __init__ (self , default , validator , repr , cmp , hash , init , convert ,
1133
- metadata , type ):
1169
+ metadata , type , kwonly ):
1134
1170
_CountingAttr .cls_counter += 1
1135
1171
self .counter = _CountingAttr .cls_counter
1136
1172
self ._default = default
@@ -1146,6 +1182,7 @@ def __init__(self, default, validator, repr, cmp, hash, init, convert,
1146
1182
self .convert = convert
1147
1183
self .metadata = metadata
1148
1184
self .type = type
1185
+ self .kwonly = kwonly
1149
1186
1150
1187
def validator (self , meth ):
1151
1188
"""
0 commit comments