Custom types and validation

argparse · Advanced · 10 min

Validating at parse time gives users immediate, specific errors instead of failures deep in your code.

type= accepts any callable taking one string. That makes it the natural place for validation and conversion beyond int and float:

def port(s):
    n = int(s)
    if not 1 <= n <= 65535:
        raise argparse.ArgumentTypeError(f"{n} is not a valid port")
    return n

How errors are reported:

  • argparse.ArgumentTypeError("msg"): your message is shown verbatim: argument --port: 70000 is not a valid port.
  • ValueError or TypeError: a generic message using the function's name: invalid port value: 'x'. Name your functions well.

Good candidates: positive integers, durations like "5m", comma-separated lists, datetime.date.fromisoformat, key=value pairs, existing paths. Standard-library constructors often work directly: type=date.fromisoformat, type=Decimal.

Keep type functions pure: don't open network connections or files at parse time. Checking that a path exists is fine, but opening it is better left to your main code inside a with block.

The choices check runs after type, so a custom type that normalises case (str.lower) pairs nicely with lowercase choices.

Example

import argparse
from datetime import date

def positive_int(s):
    n = int(s)                                  # ValueError -> generic message
    if n <= 0:
        raise argparse.ArgumentTypeError(f"must be > 0, got {n}")
    return n

def csv_list(s):
    return [part.strip() for part in s.split(",") if part.strip()]

p = argparse.ArgumentParser(prog="report")
p.add_argument("--since", type=date.fromisoformat)
p.add_argument("--top", type=positive_int, default=10)
p.add_argument("--tags", type=csv_list, default=[])
p.add_argument("--fmt", type=str.lower, choices=["md", "html"], default="md")

a = p.parse_args(["--since", "2026-01-31", "--tags", "a, b,,c", "--fmt", "HTML"])
print(a.since.month, a.top, a.tags, a.fmt)   # 1 10 ['a', 'b', 'c'] html
for bad in (["--top", "0"], ["--top", "x"]):
    try: p.parse_args(bad)
    except SystemExit: pass
# report: error: argument --top: must be > 0, got 0
# report: error: argument --top: invalid positive_int value: 'x'

Remember

  • type= takes any str -> value callable
  • ArgumentTypeError shows your message; ValueError shows a generic one
  • type runs before choices, so normalise there

Watch out for

  • Opening files or network resources inside a type function, leaking them if parsing later fails.

Check yourself

  1. A type function raises ValueError for input 'abc'. What error does the user see?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/argparse.html#type