Skip to main content

docopt2
Typed successor to docopt. The usage message is the parser spec.
A drop-in replacement for docopt - a superset, not a rewrite.

CI Coverage PyPI version Python Downloads
zero runtime dependencies, pydantic support optional the typed API is checked by ty, mypy --strict, pyright and pyrefly Documentation OpenSSF Scorecard

What is docopt2?

docopt2 is a command-line argument parser for Python. Most CLI libraries have you build the parser in code and generate a --help. docopt2 inverts that: you write the usage message, and that message is the parser.

Put the Usage: and Options: text in your docstring - the help you would write anyway - and docopt2 parses the command line against it. The help users read and the parser that runs are the same, and cannot drift.

The full documentation walks through Getting started and the guides.

Features

Typed results
Pass a schema, get a typed result back - fields coerced to their types, never a dict[str, Any].
Rustc-style diagnostics
Errors point at the mistake: colored carets in your argv and the usage, plus the closest usage line.
Subcommand dispatch
Route each subcommand to its own handler - no if args[...] ladder.
Shell completion
Tab-completion that knows your grammar: bash, zsh, fish, PowerShell, nushell.
Schema codegen
Never hand-write the schema - docopt2 stub generates it from your usage, in three styles.
Layered fallback
Resolve an option from [env: VAR] or [config: key] - CLI, then env, then config, then default.
Self-documenting --help
Opt into a colored, scoped help that shows where each value resolves from - env, config, default.
Example generation
Sample the argvs your usage accepts - for drift detection, fuzzing, and a Hypothesis strategy.
Static usage linter
Catch a broken usage before it ships - docopt2 check flags dead defaults and unusable options.
Usage formatter
Keep the Options: block aligned - docopt2 fmt reformats it, the format half to check's lint.
Round-trip codec
Turn a parsed result back into an argv that parses to it - format_argv, the inverse of docopt.
Compatibility checking
docopt2 compat old new reports the changes that would break callers - a breaking-change detector for your CLI.

Quick start

pip install docopt2  # just change the import
"""Naval Fate.

Usage:
  naval ship new <name>...
  naval ship <name> move <x> <y> [--speed=<kn>]
  naval --help

Options:
  --speed=<kn>  Speed in knots [default: 10].
"""
from docopt2 import docopt

args = docopt(__doc__)
# args is a dict: {"ship": True, "<name>": ["titanic"], "move": True, "--speed": "10", ...}

docopt2 parses the same usage grammar, so switching over is a one-line import change and everything else is opt-in.

Usage syntax

docopt2 reads the same usage DSL as docopt - the Usage: and Options: blocks are the spec.

SyntaxMeaning
commandA literal (sub)command, matched as-is.
<arg>, ARGA positional argument.
-o, --optionAn option (flag).
--option=<val>An option that takes a value.
[ ]Optional element(s).
( )Required group.
a | bMutually exclusive: choose one.
element...Repeatable: one or more.
[options]Stands in for every option listed under Options:.
--Ends option parsing. The rest is positional.
[default: <val>]An option's default value, declared under Options:.
[env: <var>]An option's environment-variable fallback.
[config: <key>]An option's config-file fallback (CLI > env > config > default).

The legend covers the essentials. Precedence and edge cases are in the full usage grammar.

Why typed docopt?

docopt hands you a dict of strings - no autocomplete, no static types, coercion by hand at every call site:

args = docopt("Usage: app <host> <port> [--verbose]")
host = args["<host>"]        # unchecked, a bare string
port = int(args["<port>"])   # coerce by hand, at every call site

docopt2 takes a schema and gives you a typed result back:

@dataclasses.dataclass
class Args:
    host: str
    port: int                  # coerced from the parsed string
    verbose: bool

args = docopt("Usage: app <host> <port> [--verbose]", schema=Args)
args.port                      # statically an int, not a string

A dataclass, a TypedDict, the Cli base class, or a pydantic model all work as schema= -
and you don't hand-write it, docopt2 stub generates it from the usage.

Validated choices. A Literal or Enum field becomes a closed set - a bad value is rejected with the valid set listed and the closest match suggested.

Diagnostics that point at the problem

When the arguments don't match, docopt reprints the usage and leaves you to find the mistake:

Usage:
  git commit [--message=<msg>] [--amend]
  git push [--force] <remote>

docopt2 points at it - in the argv and the usage that rejected it. A mistyped option or command also gets a "did you mean" hint.

A docopt2 error: 'unknown option --forcce' with a caret under the token in the argument vector and a second caret under --force in the usage, plus a 'did you mean --force?' hint

The same two carets flag a malformed usage at import time - a broken spec fails loudly, not silently - and a value that will not coerce to its typed field:

A docopt2 error: 'invalid value for --port' with a caret under abc in the argument vector and a second caret under --port=<n> in the usage, plus 'note: abc is not a valid int'

Closest usage line. docopt2 finds the usage line you got furthest into and carets the single element it still needs, rather than reprinting the whole usage. It needs a matched command or option to go on: an argv that matches no line at all falls back to the plain mismatch message.

Subcommand dispatch

Dispatch routes each command to its own handler - no if args["..."] ladder:

from docopt2 import Dispatch

app = Dispatch("""Usage:
  git add <path>...
  git commit --message=<msg>
""")

@app.on("add")
def add(args):
    print(f"adding {args['<path>']}")

@app.on("commit")
def commit(args):
    print(f"committing {args['--message']!r}")

app.run()   # parse argv, call the matched command's handler

app.check() lints the table before any run.

Shell completion

bash zsh fish PowerShell nushell

Generate the completion script for your shell. Tab then narrows to exactly what is valid at the cursor,
straight from the usage:

$ naval <TAB>
--help  --speed  ship
$ naval ship <TAB>
--speed  new
$ naval ship titanic move 1 2 <TAB>
--speed

With a schema, Tab also completes the values of a Literal or an Enum field and the file names of a Path field.

A program prints its own script: eval "$(_DOCOPT2_COMPLETE=bash naval)".

Generate the schema from the usage

Don't hand-write the schema - docopt2 stub generates it from your usage (a module docstring, a text file, or stdin):

$ docopt2 stub naval.py
@dataclasses.dataclass
class Args:
    ship: bool
    new: bool
    name: list[str]
    move: bool
    x: str | None
    y: str | None
    speed: str
    help: bool

Add --style=typeddict or --style=cli for the other shapes.
Widen a field by hand (speed: int) and the coercion is automatic.

Layered value resolution

Declare an option's fallback sources in the usage with [env: VAR] and [config: key]. docopt2 resolves each -
the command-line argument first, then the environment, then a config mapping you pass, then the [default: ...]:

doc = "Usage: prog [--port=<n>]\n\nOptions:\n  --port=<n>  Port [default: 80] [env: APP_PORT] [config: server.port]."

docopt(doc, "", config={"server": {"port": 8080}})             # {'--port': '8080'} - config
os.environ["APP_PORT"] = "7000"
docopt(doc, "", config={"server": {"port": 8080}})             # {'--port': '7000'} - env wins
docopt(doc, "--port=9000", config={"server": {"port": 8080}})  # {'--port': '9000'} - CLI wins

docopt2 never reads the file - you load it however you like (TOML, JSON, a [tool.<prog>] table) and pass the mapping, so you are never tied to one format.

Know where a value came from. args.source(name) reports the layer that actually won - so you can log or branch on provenance instead of guessing:

docopt(doc, "--port=9000", config={"server": {"port": 8080}}).source("--port")   # Source.CLI

Scaffold the config file from your usage. docopt2 config-template writes a ready-to-fill TOML skeleton - every [config: key], seeded with its default:

$ docopt2 config-template app.py
[server]
port = 80  # --port, env APP_PORT

Self-documenting --help

--help prints your usage verbatim by default. Opt into help_style="rich" for an aligned, colored screen that documents where each value resolves from, reading the [env, config, default] chain straight out of the usage you already wrote:

A docopt2 rich --help: a bold Usage, then Options with green option names, aligned descriptions, and a dimmed source chain per option - '[env: PORT, config: server.port, default: 8080]' - documenting the resolution order

Because the sources are declared right in the usage text, the help writes itself. It also scopes to the subcommand the user typed - git commit --help shows only commit.

Sample the invocations your usage accepts

docopt2 examples (or generate_examples) walks the usage grammar into concrete invocations it accepts -
derived from the spec, so they cannot drift from what docopt parses:

$ docopt2 examples naval.py --count=4 --seed=5
--help
ship v1 move v2 v3
ship new v4 v5
ship new v6

Golden-file them to catch grammar drift, fuzz your parser with the accepted set, or add --invalid for the reject-set.

Property-test with Hypothesis. The same sampler is a shrinking Hypothesis strategy - every draw is an argv your usage accepts.

Lint the usage before it ships

docopt2 check (or docopt2.check(doc) in code) lints the usage grammar itself -
catching defects the parser would otherwise accept in silence:

A docopt2 check warning: option --verbose is declared but never used, with a caret under its declaration in the options section and a help line on how to fix it

It flags dead [default: ...] values, options declared but never usable,
ambiguous variadic positionals, and redundant alternatives.

A usage that does not parse is reported as the error docopt() would raise, and docopt2 check exits 1.

With check(doc, schema=Args) it also reports where the schema disagrees with the usage.

Format the usage

docopt2 fmt (or format_usage) reformats the Options: block - aligning every description into one column,
normalizing each spec, stripping trailing space - the format half to check's lint. Take a drifted block:

Options:
  --port=<n>  Port [default: 8080].
  --host=<h>   Interface [default: 127.0.0.1].
  -v, --verbose  Be loud.

docopt2 fmt lines it up, without changing what the usage parses to and leaving the Usage: patterns untouched:

Options:
  --port=<n>    Port [default: 8080].
  --host=<h>    Interface [default: 127.0.0.1].
  -v --verbose  Be loud.

The change is layout-only and idempotent. docopt2 fmt prints to stdout - pipe it back to rewrite the file, or diff it in CI to flag a usage that is not formatted.

Round-trip: results back to argv

docopt parses an argv into a result. format_argv does the inverse - the same usage message drives both directions:

doc = "Usage: prog <src> <dst> [--force]"
args = docopt(doc, "a b --force")
format_argv(args, doc)   # ['a', 'b', '--force'] - which docopt parses straight back to args

It emits every element that carries a value: what you supplied, plus whatever [env:] or [config:] resolved.
Each candidate is verified by re-parsing, and an ambiguous grammar raises rather than return a wrong argv.

Compatibility checking

Your usage message is your interface, so check_compat (and docopt2 compat) reports the changes that would break callers - invocations the old usage accepts that the new one rejects:

check_compat("Usage: git push [--force] <remote>", "Usage: git push <remote> <branch>")
# ['option `--force` removed', '`push v1` no longer accepted']

It reports only definite breaks - never a "compatible" all-clear - so docopt2 compat before after drops into a release gate like a linter.

Read the full documentation · start with Getting started


MIT License · derived from docopt · see NOTICE

Metadata

Release files for docopt2 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for docopt2 1.3.0
File Size Uploaded
docopt2-1.3.0.tar.gz 938.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for docopt2 1.3.0
File Interpreter ABI Platform
docopt2-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / docopt2-1.3.0.tar.gz

Download URL docopt2-1.3.0.tar.gz
Size 938.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f877e102dead7c13256bffdeed2971ac0a5c53fd2cc27e7715aa06ffeec4c263
BLAKE2b-256 checksum
How to use checksums
5777cca7d0ca7055a147ac80b8917e798aeb89b36b17456246daf85f68550db1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / docopt2-1.3.0-py3-none-any.whl

Download URL docopt2-1.3.0-py3-none-any.whl
Size 103.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0c88fa5708d0954e537403ff07515d53e901b88ebcda4a27d516ea5cbf47afff
BLAKE2b-256 checksum
How to use checksums
9d656b1ced6c5d2968e7409b3717922c8aac95b1de1b14d6d33cc0947b883048
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page