Skip to content

Commit 0294c73

Browse files
committed
more docs
1 parent 6831e77 commit 0294c73

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,41 @@ except SystemExit as exc:
257257
assert exc.code == 0
258258
```
259259

260+
The same passthrough lets you ship custom `argparse.Action` subclasses
261+
that take their own constructor parameters — for example, a
262+
`--check-updates` flag that queries PyPI:
263+
264+
```python
265+
import argparse, json, urllib.request
266+
from importlib.metadata import version as get_version
267+
268+
class CheckPyPIUpdate(argparse.Action):
269+
def __init__(self, option_strings, dest, package_name, **kwargs):
270+
kwargs.setdefault("nargs", 0)
271+
kwargs.setdefault("default", argparse.SUPPRESS)
272+
self.package_name = package_name
273+
super().__init__(option_strings, dest, **kwargs)
274+
275+
def __call__(self, parser, namespace, values, option_string=None):
276+
url = f"https://pypi.org/pypi/{self.package_name}/json"
277+
with urllib.request.urlopen(url, timeout=5) as r:
278+
latest = json.load(r)["info"]["version"]
279+
current = get_version(self.package_name)
280+
if current == latest:
281+
parser.exit(0, f"{self.package_name} {current} is up to date\n")
282+
parser.exit(0, f"Update available: {current} -> {latest}\n")
283+
284+
class CLI(argclass.Parser):
285+
# --check-updates is auto-derived from the attribute name
286+
check_updates = argclass.Argument(
287+
action=CheckPyPIUpdate,
288+
package_name="argclass", # passthrough kwarg
289+
)
290+
```
291+
292+
See [Argparse Passthrough Kwargs](https://docs.argclass.com/arguments.html#argparse-passthrough-kwargs)
293+
for the full pattern.
294+
260295
### Interactive Examples
261296

262297
Run `python -m argclass` to explore all features interactively.

docs/arguments.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,90 @@ automatically strips the `type` parameter for `VERSION`, `HELP`, `STORE_TRUE`,
596596
Extra kwargs are stored as an immutable `MappingProxyType` on the resulting
597597
argument and merged into the kwargs passed to `add_argument()` at parser
598598
construction time.
599+
600+
### Custom Actions with passthrough kwargs
601+
602+
The passthrough mechanism is what lets you ship custom `argparse.Action`
603+
subclasses that take their own constructor parameters. The action receives
604+
whatever extra kwargs you pass through `Argument(...)` directly in its
605+
`__init__`. Here is a self-contained example: a `--check-updates` flag that
606+
queries PyPI for the latest version of a configurable package and prints
607+
whether an update is available, then exits.
608+
609+
<!--- name: test_args_custom_action_pypi_update --->
610+
```python
611+
import argparse
612+
import json
613+
import urllib.request
614+
from importlib.metadata import PackageNotFoundError
615+
from importlib.metadata import version as get_installed_version
616+
617+
import argclass
618+
619+
620+
class CheckPyPIUpdateAction(argparse.Action):
621+
"""Query PyPI for the latest version of ``package_name`` and exit.
622+
623+
``package_name`` is supplied by argclass via passthrough kwargs.
624+
The action behaves like ``--version`` — flag-style (``nargs=0``),
625+
suppressed from the parsed namespace, and exits the process.
626+
"""
627+
628+
def __init__(self, option_strings, dest, package_name, **kwargs):
629+
kwargs.setdefault("nargs", 0)
630+
kwargs.setdefault("default", argparse.SUPPRESS)
631+
kwargs.setdefault(
632+
"help",
633+
f"Check PyPI for updates to {package_name} and exit",
634+
)
635+
self.package_name = package_name
636+
super().__init__(option_strings, dest, **kwargs)
637+
638+
def __call__(self, parser, namespace, values, option_string=None):
639+
try:
640+
current = get_installed_version(self.package_name)
641+
except PackageNotFoundError:
642+
current = None
643+
url = f"https://pypi.org/pypi/{self.package_name}/json"
644+
try:
645+
with urllib.request.urlopen(url, timeout=5) as resp:
646+
latest = json.load(resp)["info"]["version"]
647+
except Exception as exc:
648+
parser.exit(2, f"PyPI check failed: {exc}\n")
649+
if current is None:
650+
parser.exit(
651+
0,
652+
f"{self.package_name} {latest} available "
653+
"(not installed locally)\n",
654+
)
655+
if current == latest:
656+
parser.exit(
657+
0,
658+
f"{self.package_name} {current} is up to date\n",
659+
)
660+
parser.exit(
661+
0,
662+
f"Update available: {self.package_name} "
663+
f"{current} -> {latest}\n",
664+
)
665+
666+
667+
class CLI(argclass.Parser):
668+
# --check-updates is auto-derived from the attribute name
669+
check_updates = argclass.Argument(
670+
action=CheckPyPIUpdateAction,
671+
package_name="argclass", # passthrough kwarg
672+
)
673+
674+
675+
# The class definition wires the action up; running --check-updates
676+
# would hit PyPI and exit. Here we just verify the parser builds.
677+
parser = CLI()
678+
parser.parse_args([])
679+
```
680+
681+
Run `python myapp.py --check-updates` to see the live PyPI check.
682+
683+
The pattern generalises to any custom action: declare your own constructor
684+
parameters, consume them in `__init__` before calling `super().__init__`,
685+
and pass them through `argclass.Argument(action=YourAction, your_param=...)`.

docs/llms.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,33 @@ except SystemExit:
185185
```
186186

187187
argclass automatically strips `type=` for `VERSION`, `HELP`, `STORE_TRUE`, `STORE_FALSE`, and `COUNT` actions (argparse rejects it). Extra kwargs are frozen as an immutable `MappingProxyType` and merged into the dict passed to `add_argument()`.
188+
189+
The same passthrough enables custom `argparse.Action` subclasses with their own constructor parameters — they read those parameters in `__init__` before delegating to `super().__init__`. Example: a `--check-updates` flag that queries PyPI for the latest version of a configurable package and exits:
190+
191+
```python
192+
import argparse, json, urllib.request
193+
from importlib.metadata import version as get_version
194+
195+
class CheckPyPIUpdate(argparse.Action):
196+
def __init__(self, option_strings, dest, package_name, **kwargs):
197+
kwargs.setdefault("nargs", 0)
198+
kwargs.setdefault("default", argparse.SUPPRESS)
199+
self.package_name = package_name
200+
super().__init__(option_strings, dest, **kwargs)
201+
202+
def __call__(self, parser, namespace, values, option_string=None):
203+
url = f"https://pypi.org/pypi/{self.package_name}/json"
204+
with urllib.request.urlopen(url, timeout=5) as r:
205+
latest = json.load(r)["info"]["version"]
206+
current = get_version(self.package_name)
207+
if current == latest:
208+
parser.exit(0, f"{self.package_name} {current} is up to date\n")
209+
parser.exit(0, f"Update available: {current} -> {latest}\n")
210+
211+
class CLI(argclass.Parser):
212+
# --check-updates is auto-derived from the attribute name
213+
check_updates = argclass.Argument(
214+
action=CheckPyPIUpdate,
215+
package_name="argclass", # passthrough kwarg
216+
)
217+
```

0 commit comments

Comments
 (0)