Imported from lljbash/argparse-type-helper (
AGENTS.md). Install upstream withnpx skills add lljbash/argparse-type-helper. Copyright stays with the author.
Agent Instructions for argparse-type-helper
This repository contains a lightweight Python library that provides type-hinted argument parsing for argparse. The project is in Beta stage and uses modern Python tooling with uv for dependency management.
Important Principles
- Ask for clarification when uncertain - When facing design choices, feature scope, or multiple implementation options, use the ask_user tool to inquire rather than making assumptions.
- When encountering difficulties, ask how to proceed - If execution becomes blocked or unclear, use the ask_user tool to ask what to do rather than stopping work.
- Update README and AGENTS.md - After making changes to the codebase, always update the README.md and this AGENTS.md file to reflect your work. Keep these files in sync with actual implementation.
- Communicate in the user's language - Respond and communicate in the same language the user is using. If they write in Chinese, respond in Chinese. If they write in English, respond in English.
- Always use
uv run- Never use barepython3orpip. Always useuv run python,uv run pytest, etc. for all Python commands.
Build, Test, and Lint Commands
Setup
uv sync --all-extras --dev
Type Checking (basedpyright)
uv run basedpyright **/*.py
Code Formatting
# Format with Black
uv run black argparse_type_helper tests
# Sort imports with isort
uv run isort argparse_type_helper tests
Running Tests
# Run all tests
uv run pytest tests/
# Run specific test file
uv run pytest tests/test_basic.py -v
# Run with verbose output
uv run pytest tests/ -v --tb=short
Manual Testing
Manual testing can be done by running the example scripts:
uv run python tests/example.py
uv run python tests/example_groups.py
uv run python tests/example_subcommands.py
Auto-Documentation
The example scripts are synced to the README.md using MARKDOWN-AUTO-DOCS. After updating examples, run the following command to update the README:
npm i -g markdown-autodocs # Install globally if not already installed
markdown-autodocs -c code-block -o ./README.md
High-Level Architecture
Core Problem: argparse doesn't integrate well with type hints. This library bridges that gap by allowing you to define arguments using dataclass-like syntax with full type support, while remaining compatible with standard argparse.
Design Pattern — Six Internal Modules:
All internal modules use _ prefix to signal they are not public API. All public symbols are exported via __init__.py.
-
_types.py— Core types and argument definitionName,Flag: Marker classes for positional vs optional argumentsTArg: Dataclass holding all argument metadata, with__set_name__descriptor protocolUnset: Sentinel type for unset optional valuestarg(): Function that createsTArgconfiguration objectspost_init: Decorator for post-extraction validation hooksget_targs(),check_and_maybe_init_targs_class(): Internal helpers shared by decorators and registry- Detection helpers:
is_tgroup_class(),is_texclusive_class(),is_tsubcommands_class(),is_tsubcommand_class(),is_group_like()— shared by_decoratorsand_registry
-
_decorators.py— Class decorators@targs: Transforms a class into a typed arguments container (generates__init__,__repr__)@tgroup: Marks a class as an argument group (title/description from docstring or params)@texclusive: Marks a class as a mutually exclusive group@tsubcommands: Marks a class as a subcommands base@tsubcommand(name=..., aliases=[...]): Marks a class as a named subcommand (must inherit from a@tsubcommandsbase);nameis required,aliasesis optional_scan_special_attrs(): Discovers group/subcommand references via type annotations
-
_registry.py— Parser registration and extractionregister_targs(parser, cls): Wires up all arguments on anArgumentParser_register_groups(container, cls): Recursively registers argument groups and exclusive groups on a container; supports@texclusivenested inside@tgroupextract_targs(args, cls): Reconstructs a typed instance from parsedNamespacecreate_parser(cls, ...): Creates anArgumentParserand registers in one step
-
_inference.py— Type inferenceinfer_type_from_hint(): Inferstype=from type hints (X | None,list[X], bare types, etc.)_get_union_args(): Helper to extract union member types
-
_docstring.py— Docstring parsingDocString: Frozen dataclass withtitle,description,fullproperty, andparse()classmethod- Splitting rule: first paragraph → title, rest → description
-
_utils.py— Shared utilitiesSentry: Pattern where a value can be either a type class or an instancelogger: Module-level loggercopy_signature(): Signature-preserving decorator helperget_attr_docstrings(): AST-based attribute docstring extraction
Key Technical Details:
- Sentry Pattern (
_utils.py): A clever pattern where a value can be either a type class OR an instance. Used for marking unset optional values without requiring special sentinels. - Descriptor Protocol (
TArg.__set_name__): Auto-registers argument configs when the class is created, no manual tracking needed. - Post-init Hooks (
@post_initdecorator): Allows validation logic after argument extraction. - Docstring Extraction (
_utils.py): Uses AST parsing withtextwrap.dedentto extract docstrings from attributes. Works for both module-level and function-scoped (indented) classes. - DocString Splitting (
_docstring.py): Usesinspect.cleandoc()then splits on"\n\n"(first blank line). First paragraph = title, rest = description. Applied consistently to@targs,@tgroup,@tsubcommands, and subcommand classes. - Mutable Default Safety:
list,dict, andsetdefaults are shallow-copied in the generated__init__to prevent sharing across instances. - Decorator Consistency: All three decorators (
@tgroup,@texclusive,@tsubcommands) support dual calling styles: bare@decoratorand parameterized@decorator(...). Title can be passed as the first positional argument or via keyword. Note:@texclusivedoes not supporttitle/description— this is a limitation ofargparse.MutuallyExclusiveGroup.@tsubcommandalways requiresname— no bare form. - Group Nesting Rules:
@texclusivecan be nested inside@tgroup— the exclusive group is created on the argument group's container. Other nesting combinations are rejected at decoration time:@tgroupinside@tgroup,@tgroupinside@texclusive, and@texclusiveinside@texclusiveall raiseTypeError.
Key Conventions
Naming Rules
- Underscores to Dashes: When using
Flagfor optional arguments, underscores in the class attribute name are automatically converted to dashes in the CLI (e.g.,optional_dashbecomes--optional-dash). - Dest Mapping: The
get_dest()method handles converting flag names back to attribute names when extracting fromNamespace. - Subcommand Names: Subcommand classes must use
@tsubcommand(name="...", aliases=[...])— thenameparameter is the CLI token. There is no default name; it must always be explicitly provided.aliasesis optional and provides alternative names. Bare@targson a@tsubcommandssubclass raisesTypeErrorduring registration.
Type Hint Requirements
- All
targ()fields must have a type hint. - Type inference rules (in
infer_type_from_hint()):- User explicit
type=→ always used (highest priority) - Has
action→ skip inference (actions handle their own types) bool/bool | None→ skip inference (require explicitaction="store_true/store_false")X | None/Optional[X]where X is a non-bool callable → use Xlist[X]/Sequence[X]etc. withnargsset → use element type X (Sequenceis recommended overlist)- Bare callable (
int,str,float, …) excludingbool→ use itself - Anything else (
int | str, etc.) → skip inference
- User explicit
- Group references use type annotations:
db: DbOptionswhereDbOptionsis a@tgroupclass. - Subcommand references use type annotations:
command: CommandswhereCommandsis a@tsubcommandsclass. - Type checking is strict (see
pyproject.toml:typeCheckingMode = "strict").
Argument Configuration
- All parameters accepted by
argparse.add_argument()are supported (action,nargs,choices,default,help, etc.) Namemarks a positional argument (required)Flagor custom string/tuple marks an optional argument- Can mix class-based definitions with native
parser.add_argument()calls
Code Style
- Python 3.12+ (see
pyproject.toml:requires-python = ">=3.12") - Black 26+ for formatting (target: py312)
- isort with Black profile for import sorting
- basedpyright strict mode for type checking
Module Organization
__init__.py: Public API exports (Name, Flag, DocString, targ, targs, tgroup, texclusive, tsubcommands, tsubcommand, post_init, register_targs, extract_targs, create_parser)_types.py: Core types, TArg, targ(), post_init, constants_decorators.py: All class decorators (@targs, @tgroup, @texclusive, @tsubcommands, @tsubcommand)_registry.py: register_targs, extract_targs, create_parser_inference.py: Type inference logic_docstring.py: DocString dataclass with parsing logic_utils.py: Sentry pattern, logger, copy_signature, attribute docstring extraction
Internal Attributes
_targs/_targs_flag: Per-class targs dict and initialization marker_targs_groups: Dict mapping attribute name → group class (set by@targs)_targs_subcommands: Dict mapping attribute name → subcommands base class (set by@targs)_tgroup_flag/_tgroup_title/_tgroup_description: Group metadata (set by@tgroup)_texclusive_flag/_texclusive_required: Exclusive group metadata (set by@texclusive)_tsubcommands_flag/_tsubcommands_title/_tsubcommands_description/_tsubcommands_required: Subcommand base metadata (set by@tsubcommands)_tsubcommand_flag/_tsubcommand_name/_tsubcommand_aliases: Individual subcommand metadata (set by@tsubcommand)
Testing
- Located in
tests/directory test_basic.py: Core functionality (targ, Name, Flag, register, extract, subclass, post_init, docstrings, all action types, custom Action, nargs, required, metavar, dest)test_groups.py: Argument groups, mutually exclusive groups,@texclusivenested inside@tgroup, invalid nesting rejectiontest_subcommands.py: Subcommands with inheritance, groups inside subcommands, pattern matchingtest_type_inference.py: Type inference expansion (X | None, Optional[X], list[X]+nargs, bool protection)test_docstring.py: DocString.parse() unit tests (single/multi/empty/whitespace edge cases)test_create_parser.py: create_parser, docstring→title/desc split, mutable defaults, robustness- Example files:
example.py,example_groups.py,example_subcommands.py(synced to README via MARKDOWN-AUTO-DOCS)