Skip to main content

🗝 Fix and improve typer 🗝

What is dtyper, in one sentence?

Using import dtyper as typer instead of import typer would make your typer.commands directly callable.

(There's also a neat way to make dataclasses from typer commands, but that would be two sentences.)

Why dtyper?

typer is a famously clear and useful system for writing Python CLIs but it has two issues that people seem to run into a lot:

  1. You can't call the typer.command functions it creates directly because they have the wrong defaults.

  2. As you add more arguments to your CLI, there is no easy way to break up the code sitting in one file without passing around long, verbose parameter lists.

dtyper is a tiny, single-file library that adds to an existing installation of typer to solve these two problems without changing existing code at all.

  • dtyper.command executes typer.command then fixes the defaults.

  • dtyper.function decorates an existing typer.command to have correct defaults.

  • dtyper.dataclass automatically makes a dataclass from a typer.command.

How to use dtyper?

Install as usual with poetry add dtyper, pip install dtyper, or your favorite package manager.

dtyper is a drop-in replacement for typer - it copies all typers properties - so you can even write

import dtyper as typer

to experiment with it before deciding.

dtyper has two new functions that typer doesn't, and overrides a typer class:

  • @dtyper.function is a decorator that takes a typer command and returns a callable function with the correct defaults. It is unncessary if you use dtyper.Typer (below)

  • @dtyper.dataclass is a decorator that takes an existing typer or dtyper command and makes a dataclass from it.

  • dtyper.Typeris a class identical to typer.Typer, except it fixes Typer.command functions so you can call them directly.

None of the typer functionality is changed to the slightest degree - adding dtyper will not affect how your command line program runs at all.

Example 1: using dtyper instead of typer

from dtyper import Argument, Option, Typer

app = Typer()

@app.command(help='test')
def get_keys(
    bucket: str = Argument(
        'buck', help='The bucket to use'
    ),

    keys: bool = Option(
        False, help='The keys to download'
    ),
):
    print(bucket, keys)

You can call get_keys() from other code and get the right defaults.

Without regular typer, you sometimes get a typer.Argument or typer.Option in place of an expected str or bool.

Example 2: a simple dtyper.dataclass

Here's a simple CLI in one Python file with two Arguments and an Option:

@command(help='test')
def get_keys(
    bucket: str = Argument(
        ..., help='The bucket to use'
    ),

    keys: str = Argument(
        'keys', help='The keys to download'
    ),

    pid: Optional[int] = Option(
        None, '--pid', '-p', help='process id, or None for this process'
    ),
):
    get_keys = GetKeys(**locals())
    print(get_keys.run())


@dtyper.dataclass(get_keys)
class GetKeys:
    site = 'https://www.some-websijt.nl'

    def run(self):
        return self.url, self.keys, self.pid

    def __post_init__(self):
        self.pid = self.pid or os.getpid()

    def url(self):
       return f'{self.site}/{self.url}/{self.pid}'

Example: splitting a large typer.command into multiple files

Real world CLIs frequently have dozens if not hundreds of commands, with hundreds if not thousands of options, arguments, settings or command line flags.

The natural structure for this is the "big ball of mud", a popular anti-pattern known to cause misery and suffering to maintainers.

dtyper.dataclass can split the user-facing definition of the API from its implementation and then split that implementation over multiple files in a natural and convenient way.

The example has three Python files.

interface.py contains the Typer CLI definitions for this command.

@command(help='test')
def big_calc(
    bucket: str = Argument(
        ..., help='The bucket to use'
    ),
    more: str = Argument(
        '', help='More information'
    ),
    enable_something: boolean = Option(
        False, help='Turn on one of many important parameters'
    ),
    # [dozens of parameters here]
):
    d = dict(locals())  # Capture all the command line arguments as a dict

    from .big_calc import BigCalc  # Lazy import to avoid a cycle

    bc = BigCalc(**d)
    bc.run()

big_calc.py contains the dtyper.dataclass implementation

from .interface import big_calc
from . import helper
import dtyper


@dtyper.dataclass(big_calc)
class BigCalc:
    def run(self):
       # Each argument in `big_calc` becomes a dataclass field
       print(self.bucket, self.more)
       print(self)  # dataclass gives you a nice output of all fields

       if helper.huge_thing(self) and self._etc():
          self.stuff()
          helper.more_stuff(self)
          ...

    def _etc(self):
       ...
       # Dozens more methods here perhaps!

Some of the code is offloaded to helper files like helper.py:

def huge_thing(big_calc):
    if has_hole(big_calc.bucket):
       fix_it(big_calc.bucket, big_calc.more)

def more_stuff(big_calc):
    # even more code

API Documentation

Metadata

Release files for dtyper 2.8.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 dtyper 2.8.0
File Size Uploaded
dtyper-2.8.0.tar.gz 19.8 kB Details

Built distribution (wheel)

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

Total release size: 28.4 kB

Release files / dtyper-2.8.0.tar.gz

Download URL dtyper-2.8.0.tar.gz
Size 19.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1ee1b986306b58582a805855c81b104cff457f0dfd9523da99e2b0997a8ba7e1
BLAKE2b-256 checksum
How to use checksums
4ef9b752ea7ad2e6e8fe6738a2654575958d8ba2f89eace7dc7e0aeb23dfe513
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / dtyper-2.8.0-py3-none-any.whl

Download URL dtyper-2.8.0-py3-none-any.whl
Size 8.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5be43603ff1382380659fbbd79969b5e8944492f56551d7a38b13eb9979ec6b
BLAKE2b-256 checksum
How to use checksums
31391fdc945a3445060d6885f6d267aac06d92b6ace89dd4febfef5bbbb7e438
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

2.8.0 This release

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.1

2 release files

2.4.0

2 release files

2.3.1

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.10.0

2 release files

0.9.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