@@ -1126,6 +1126,7 @@ def __init__(
11261126 kind : inspect ._ParameterKind ,
11271127 is_base_command : bool ,
11281128 is_block_field : bool = False ,
1129+ block_param_name : str | None = None ,
11291130 dest_override : str | None = None ,
11301131 ) -> None :
11311132 # signature-derived inputs (never emitted):
@@ -1134,6 +1135,7 @@ def __init__(
11341135 self .has_default = has_default
11351136 self .param_default = param_default # the function's own default, not the argparse `default` slot
11361137 self .is_block_field = is_block_field
1138+ self .block_param_name = block_param_name
11371139 self .dest_override = dest_override
11381140 self .is_kw_only = is_kw_only
11391141 self .is_variadic = is_variadic
@@ -2074,19 +2076,24 @@ def _shared_field_dest(dc_type: type, field_name: str) -> str:
20742076
20752077def _link_mutex_group_membership (
20762078 by_name : dict [str , _ArgparseArgument ],
2079+ by_block_param_name : dict [str , list [_ArgparseArgument ]],
20772080 mutually_exclusive_groups : tuple [Group , ...] | None ,
20782081) -> None :
20792082 """Append each mutex group's 1-based index to its member arguments' ``mutex_group_indices``.
20802083
20812084 This membership is the fact behind the required-member constraint row. Member references are
20822085 validated upstream by :func:`_validate_group_specs` before this runs, so every member name
2083- resolves to a built argument.
2086+ resolves to a built argument or block parameter .
20842087 """
20852088 if not mutually_exclusive_groups :
20862089 return
20872090 for index , spec in enumerate (mutually_exclusive_groups , start = 1 ):
20882091 for name in spec .members :
2089- by_name [name ].mutex_group_indices .append (index )
2092+ if name in by_block_param_name :
2093+ for arg in by_block_param_name [name ]:
2094+ arg .mutex_group_indices .append (index )
2095+ else :
2096+ by_name [name ].mutex_group_indices .append (index )
20902097
20912098
20922099def _resolve_func_hints (func : Callable [..., Any ], * , skip_params : frozenset [str ] = _SKIP_PARAMS ) -> dict [str , Any ]:
@@ -2184,6 +2191,7 @@ def _expand_dataclass_block(
21842191 * ,
21852192 func_qualname : str ,
21862193 base_command : bool ,
2194+ block_param_name : str ,
21872195 shared : bool = False ,
21882196) -> list [_ArgparseArgument ]:
21892197 """Expand a dataclass block's ``init`` fields into flat ``_ArgparseArgument`` builders.
@@ -2247,6 +2255,7 @@ def _expand_dataclass_block(
22472255 kind = inspect .Parameter .POSITIONAL_OR_KEYWORD ,
22482256 is_base_command = base_command ,
22492257 is_block_field = True ,
2258+ block_param_name = block_param_name ,
22502259 dest_override = _shared_field_dest (dc_type , f .name ) if shared else None ,
22512260 )
22522261 )
@@ -2413,7 +2422,11 @@ def _resolve_parameters(
24132422 # Expand first so its @dataclass-ness check runs before we read field names for the next guard.
24142423 # A cmd2_base_args block is shared down the chain: its fields use a type-qualified dest.
24152424 expanded = _expand_dataclass_block (
2416- block_hint , func_qualname = func .__qualname__ , base_command = base_command , shared = name == NS_ATTR_BASE_ARGS
2425+ block_hint ,
2426+ func_qualname = func .__qualname__ ,
2427+ base_command = base_command ,
2428+ block_param_name = name ,
2429+ shared = name == NS_ATTR_BASE_ARGS ,
24172430 )
24182431 _reject_field_shadowing_block_param (block_hint , name , func .__qualname__ )
24192432 resolved .extend (expanded )
@@ -2480,7 +2493,11 @@ def _resolve_parameters(
24802493 for arg in positionals [:- 1 ]: # every positional except the last has a following positional
24812494 arg .has_following_positional = True
24822495 by_name = {arg .name : arg for arg in resolved }
2483- _link_mutex_group_membership (by_name , mutually_exclusive_groups )
2496+ by_block_param_name : dict [str , list [_ArgparseArgument ]] = {}
2497+ for arg in resolved :
2498+ if arg .block_param_name is not None :
2499+ by_block_param_name .setdefault (arg .block_param_name , []).append (arg )
2500+ _link_mutex_group_membership (by_name , by_block_param_name , mutually_exclusive_groups )
24842501 for arg in resolved :
24852502 arg ._check_constraints ()
24862503 return resolved , base_args_types
@@ -2760,7 +2777,10 @@ def build_parser_from_function(
27602777
27612778 # Add each argument to its target (its group/mutex group if assigned, else the parser).
27622779 for arg in resolved :
2763- arg .add_to (target_for .get (arg .name , parser ))
2780+ target = target_for .get (arg .name )
2781+ if target is None and arg .block_param_name is not None :
2782+ target = target_for .get (arg .block_param_name )
2783+ arg .add_to (target if target is not None else parser )
27642784
27652785 # A cmd2_base_args block is inheritable: stamp a parse-time presence marker so a descendant's
27662786 # cmd2_parent_args can confirm an ancestor actually declared the block.
0 commit comments