Skip to main content

A CLI tool for generating type annotations.

Project description

Typewriter

Documentation Status GitHub Actions Build Status Coveralls Codecov PyPI Package latest release PyPI Wheel Supported versions Supported implementations

Overview

Typewriter is a Python Typer CLI built on LibCST that normalizes None-related type annotations in Python source code. It can be used to automatically rewrite type annotations to use Optional instead of Union when None is involved, and to ensure Optional is used for variable and parameter annotations when the default value is None.

What it rewrites (default mode, Python 3.9-compatible):

Union[T, None] -> Optional[T]

Union[T1, T2, None] -> Optional[Union[T1, T2]]

x: T = None -> x: Optional[T] = None

def f(x: T = None) -> def f(x: Optional[T] = None)

With --target-version 3.10 (PEP 604 mode):

Union[T, None] -> T | None

Optional[T] -> T | None

Union[T1, T2] -> T1 | T2

Union[T1, T2, None] -> T1 | T2 | None

x: T = None -> x: T | None = None

def f(x: T = None) -> def f(x: T | None = None)

Additional notes:

  • x: Any = None stays unchanged.
  • Qualified typing references are preserved.
  • Import statements are added as needed and deduplicated.
  • Unused Union and Optional imports are cleaned up after rewriting.

Quick Start

Installation for PyPI

pip install py-typewriter-cli # Python 3.10-3.13

Usage

Typewriter exposes one subcommand: run. Consider example.py with contents:

from typing import Any, Union

count: int = None
name: str = None
payload: Any = None

def greet(user_id: int = None, msg: Union[str, None] = None) -> Union[str, None]:
    if user_id is None:
        return None
    return f"hello-{user_id}\n{msg}"

To see what would change without writing files, run with --check:

typewriter run path/to/example.py --check
Would transform path/to/example.py
--- path/to/example.py
+++ path/to/example.py
@@ ...
-old line
+new line

In --check mode, Typewriter prints a unified diff of its proposed changes to stdout and provides the following Exit codes:

  • 0: no changes needed
  • 1: changes would be made
  • 2: error

To apply the detected changes, run without --check:

typewriter run path/to/example.py

Additional features:

To run recursively on all *.py files in a directory:

typewriter run examples # Non-`.py` files are rejected.

To transform an in-memory string and return the result to stdout, use --code:

typewriter run --code "var: int = None\\n"

PEP 604 mode

To emit T | None style unions instead of Optional[T], pass --target-version 3.10 (or any Python 3.10+ version):

typewriter run path/to/example.py --target-version 3.10

In this mode, existing Optional[...] and Union[...] annotations are normalized to PEP 604 syntax too. The default output remains Python 3.9-compatible (Optional[T]).

Skip configuration

To skip additional files or directories by glob pattern, use --ignore (repeatable):

typewriter run myproject --ignore "test_*" --ignore "generated"

Patterns are matched against both the bare file/directory name and the relative path from the scanned root. The built-in skip set (.git, .venv, __pycache__, build, dist, etc.) is always active regardless of extra patterns.

To also honor the nearest .gitignore at or above the scanned directory:

typewriter run myproject --respect-gitignore

Additional Details:

  • PATH and --code are mutually exclusive.
  • Literal \\n sequences in --code input are interpreted as newlines.
  • When a directory is provided as PATH, Typewriter will ignore non-.py files and common non-source subdirectories such as .git, .venv, venv, __pycache__, build, and dist.
  • PEP 604 syntax (T | None) is opt-in via --target-version 3.10 and is not used by default.

Versioning

The package version is the single source of truth. The documentation version in docs/source/conf.py is derived automatically from typewriter.__version__, which reads the distribution metadata for py-typewriter-cli.

Motivation

Typewriter was created to resolve issue #2303 in microsoft/playwright-python.

The issue highlighted a common inconsistency in the codebase where type annotations involving None were not standardized, leading to confusion and maintenance challenges. Specifically, some type annotations used Union[T, None] while others used Optional[T], and there were cases where variable and parameter annotations did not use Optional even when the default value was None. This made it difficult to maintain a consistent style and to leverage static analysis tools effectively.

Surely, other developers have encountered similar inconsistencies in their codebases, and Typewriter can be a useful tool for anyone looking to standardize a common inconsistency and correct a common issue related to the use of None in type annotations. By using Typewriter, developers can ensure that their codebase adheres to PEP 484 proposal of requiring optional types to be made explicit.

Contributing

Contributions to Typewriter are welcome! Please follow the fork-and-pull request workflow:

  1. Fork the repository and create a new branch for your feature or bug fix.
  2. Make your changes and commit them with clear messages.
  3. Push your branch to your fork and open a pull request against the main branch of the original repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_typewriter_cli-1.1.0.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_typewriter_cli-1.1.0-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

File details

Details for the file py_typewriter_cli-1.1.0.tar.gz.

File metadata

  • Download URL: py_typewriter_cli-1.1.0.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py_typewriter_cli-1.1.0.tar.gz
Algorithm Hash digest
SHA256 00c53de7bfdcdbf76eb2ec0e0e011ee6cd807fef23d09d3dae6e2a7ea3163901
MD5 078873f16c1c8d9febcd74dbd792f45d
BLAKE2b-256 f9c1987df9c9746cb23f8c7fe843db7788e78cbbbc581b222080b2320a95f622

See more details on using hashes here.

File details

Details for the file py_typewriter_cli-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for py_typewriter_cli-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0b08fb55f771e294b68c2da41d12b5772069ab8da761d4e3d1f807dc75feef04
MD5 e7fff80ebcc5fe8f771b59b45434067b
BLAKE2b-256 8c4d9af04a3cd0b90ca90673d66c7f42cafbf06fb4ba702f8b95398704885f81

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page