@@ -167,11 +167,11 @@ def _check_generate_dataclass_init(node: nodes.ClassDef) -> bool:
167
167
168
168
169
169
def _find_arguments_from_base_classes (
170
- node : nodes .ClassDef , skippable_names : set [ str ]
171
- ) -> tuple [str , str ]:
172
- """Iterate through all bases and add them to the list of arguments to add to the
173
- init.
174
- """
170
+ node : nodes .ClassDef ,
171
+ ) -> tuple [
172
+ dict [ str , tuple [ str | None , str | None ]], dict [ str , tuple [ str | None , str | None ]]
173
+ ]:
174
+ """Iterate through all bases and get their typing and defaults."""
175
175
pos_only_store : dict [str , tuple [str | None , str | None ]] = {}
176
176
kw_only_store : dict [str , tuple [str | None , str | None ]] = {}
177
177
# See TODO down below
@@ -187,8 +187,6 @@ def _find_arguments_from_base_classes(
187
187
188
188
pos_only , kw_only = base_init .args ._get_arguments_data ()
189
189
for posarg , data in pos_only .items ():
190
- if posarg in skippable_names :
191
- continue
192
190
# if data[1] is None:
193
191
# if all_have_defaults and pos_only_store:
194
192
# # TODO: This should return an Uninferable as this would raise
@@ -199,10 +197,15 @@ def _find_arguments_from_base_classes(
199
197
pos_only_store [posarg ] = data
200
198
201
199
for kwarg , data in kw_only .items ():
202
- if kwarg in skippable_names :
203
- continue
204
200
kw_only_store [kwarg ] = data
201
+ return pos_only_store , kw_only_store
202
+
205
203
204
+ def _parse_arguments_into_strings (
205
+ pos_only_store : dict [str , tuple [str | None , str | None ]],
206
+ kw_only_store : dict [str , tuple [str | None , str | None ]],
207
+ ) -> tuple [str , str ]:
208
+ """Parse positional and keyword arguments into strings for an __init__ method."""
206
209
pos_only , kw_only = "" , ""
207
210
for pos_arg , data in pos_only_store .items ():
208
211
pos_only += pos_arg
@@ -248,11 +251,11 @@ def _generate_dataclass_init( # pylint: disable=too-many-locals
248
251
params : list [str ] = []
249
252
kw_only_params : list [str ] = []
250
253
assignments : list [str ] = []
251
- assign_names : list [str ] = []
254
+
255
+ prev_pos_only_store , prev_kw_only_store = _find_arguments_from_base_classes (node )
252
256
253
257
for assign in assigns :
254
258
name , annotation , value = assign .target .name , assign .annotation , assign .value
255
- assign_names .append (name )
256
259
257
260
# Check whether this assign is overriden by a property assignment
258
261
property_node : nodes .FunctionDef | None = None
@@ -275,6 +278,9 @@ def _generate_dataclass_init( # pylint: disable=too-many-locals
275
278
keyword .arg == "init" and not keyword .value .bool_value ()
276
279
for keyword in value .keywords # type: ignore[union-attr] # value is never None
277
280
):
281
+ # Also remove the name from the previous arguments to be inserted later
282
+ prev_pos_only_store .pop (name , None )
283
+ prev_kw_only_store .pop (name , None )
278
284
continue
279
285
280
286
if _is_init_var (annotation ): # type: ignore[arg-type] # annotation is never None
@@ -289,32 +295,32 @@ def _generate_dataclass_init( # pylint: disable=too-many-locals
289
295
init_var = False
290
296
assignment_str = f"self.{ name } = { name } "
291
297
298
+ ann_str , default_str = None , None
292
299
if annotation is not None :
293
- param_str = f"{ name } : { annotation .as_string ()} "
294
- else :
295
- param_str = name
300
+ ann_str = annotation .as_string ()
296
301
297
302
if value :
298
303
if is_field :
299
304
result = _get_field_default (value ) # type: ignore[arg-type]
300
305
if result :
301
306
default_type , default_node = result
302
307
if default_type == "default" :
303
- param_str += f" = { default_node .as_string ()} "
308
+ default_str = default_node .as_string ()
304
309
elif default_type == "default_factory" :
305
- param_str += f" = { DEFAULT_FACTORY } "
310
+ default_str = DEFAULT_FACTORY
306
311
assignment_str = (
307
312
f"self.{ name } = { default_node .as_string ()} "
308
313
f"if { name } is { DEFAULT_FACTORY } else { name } "
309
314
)
310
315
else :
311
- param_str += f" = { value .as_string ()} "
316
+ default_str = value .as_string ()
312
317
elif property_node :
313
318
# We set the result of the property call as default
314
319
# This hides the fact that this would normally be a 'property object'
315
320
# But we can't represent those as string
316
321
try :
317
- param_str += f" = { next (property_node .infer_call_result ()).as_string ()} "
322
+ # Call str to make sure also Uninferable gets stringified
323
+ default_str = str (next (property_node .infer_call_result ()).as_string ())
318
324
except (InferenceError , StopIteration ):
319
325
pass
320
326
else :
@@ -323,7 +329,14 @@ def _generate_dataclass_init( # pylint: disable=too-many-locals
323
329
# (self, a: str = 1) -> None
324
330
previous_default = _get_previous_field_default (node , name )
325
331
if previous_default :
326
- param_str += f" = { previous_default .as_string ()} "
332
+ default_str = previous_default .as_string ()
333
+
334
+ # Construct the param string to add to the init if necessary
335
+ param_str = name
336
+ if ann_str is not None :
337
+ param_str += f": { ann_str } "
338
+ if default_str is not None :
339
+ param_str += f" = { default_str } "
327
340
328
341
# If the field is a kw_only field, we need to add it to the kw_only_params
329
342
# This overwrites whether or not the class is kw_only decorated
@@ -337,21 +350,33 @@ def _generate_dataclass_init( # pylint: disable=too-many-locals
337
350
continue
338
351
# If kw_only decorated, we need to add all parameters to the kw_only_params
339
352
if kw_only_decorated :
340
- kw_only_params .append (param_str )
353
+ if name in prev_kw_only_store :
354
+ prev_kw_only_store [name ] = (ann_str , default_str )
355
+ else :
356
+ kw_only_params .append (param_str )
341
357
else :
342
- params .append (param_str )
358
+ # If the name was previously seen, overwrite that data
359
+ # pylint: disable-next=else-if-used
360
+ if name in prev_pos_only_store :
361
+ prev_pos_only_store [name ] = (ann_str , default_str )
362
+ elif name in prev_kw_only_store :
363
+ params = [name ] + params
364
+ prev_kw_only_store .pop (name )
365
+ else :
366
+ params .append (param_str )
343
367
344
368
if not init_var :
345
369
assignments .append (assignment_str )
346
370
347
- prev_pos_only , prev_kw_only = _find_arguments_from_base_classes (
348
- node , set ( assign_names + [ "self" ])
371
+ prev_pos_only , prev_kw_only = _parse_arguments_into_strings (
372
+ prev_pos_only_store , prev_kw_only_store
349
373
)
350
374
351
375
# Construct the new init method paramter string
352
376
# First we do the positional only parameters, making sure to add the
353
377
# the self parameter and the comma to allow adding keyword only parameters
354
- params_string = f"self, { prev_pos_only } { ', ' .join (params )} "
378
+ params_string = "" if "self" in prev_pos_only else "self, "
379
+ params_string += prev_pos_only + ", " .join (params )
355
380
if not params_string .endswith (", " ):
356
381
params_string += ", "
357
382
0 commit comments