Metadata-Version: 2.4
Name: py-typewriter-cli
Version: 0.1.0
Summary: A CLI tool for generating type annotations.
Author-email: Daniel Henderson <me@danhenderson.dev>
License-Expression: MIT
Project-URL: Homepage, https://github.com/danphenderson/python-typewriter
Project-URL: Repository, https://github.com/danphenderson/python-typewriter
Project-URL: Issues, https://github.com/danphenderson/python-typewriter/issues
Keywords: typing,codemod,libcst,optional,union,typer,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Unix
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: libcst==1.1.0
Requires-Dist: typer==0.24.1
Provides-Extra: tests
Requires-Dist: coverage[toml]; extra == "tests"
Requires-Dist: exceptiongroup; python_version < "3.11" and extra == "tests"
Requires-Dist: pytest; extra == "tests"
Requires-Dist: pytest-cov; extra == "tests"
Requires-Dist: tomli; python_version < "3.11" and extra == "tests"
Provides-Extra: docs
Requires-Dist: sphinx; extra == "docs"
Requires-Dist: sphinx-py3doc-enhanced-theme; extra == "docs"
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
Requires-Dist: sphinx-autodoc-annotation; extra == "docs"
Requires-Dist: sphinxcontrib-asyncio; extra == "docs"
Requires-Dist: sphinxcontrib-napoleon; extra == "docs"
Requires-Dist: sphinxcontrib-trio; extra == "docs"
Provides-Extra: dev
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: black==23.10.0; extra == "dev"
Requires-Dist: flake8==6.1.0; extra == "dev"
Requires-Dist: isort==5.13.2; extra == "dev"
Requires-Dist: autoflake==v1.4; extra == "dev"
Requires-Dist: mypy==v1.8.0; extra == "dev"
Dynamic: license-file

# Typewriter

[![Documentation Status](https://readthedocs.org/projects/python-typewriter/badge/?style=flat)](https://danphenderson.github.io/python-typewriter/)
[![GitHub Actions Build Status](https://github.com/danphenderson/python-typewriter/actions/workflows/CI.yml/badge.svg)](https://github.com/danphenderson/python-typewriter/actions)
[![Coverage Status](https://coveralls.io/repos/danphenderson/python-typewriter/badge.svg?branch=main&service=github)](https://coveralls.io/r/danphenderson/python-typewriter)
[![Coverage Status](https://codecov.io/gh/danphenderson/python-typewriter/branch/main/graphs/badge.svg?branch=main)](https://codecov.io/github/danphenderson/python-typewriter)
[![PyPI Package latest release](https://img.shields.io/pypi/v/py-typewriter-cli.svg)](https://pypi.org/project/py-typewriter-cli)
[![PyPI Wheel](https://img.shields.io/pypi/wheel/py-typewriter-cli.svg)](https://pypi.org/project/py-typewriter-cli)
[![Supported versions](https://img.shields.io/pypi/pyversions/py-typewriter-cli.svg)](https://pypi.org/project/py-typewriter-cli)
[![Supported implementations](https://img.shields.io/pypi/implementation/py-typewriter-cli.svg)](https://pypi.org/project/py-typewriter-cli)

## Overview

Typewriter is a Python [Typer](https://typer.tiangolo.com/) CLI built on [LibCST](https://libcst.readthedocs.io/en/latest/) that normalizes `None`-related type annotations while preserving formatting and comments.

### What it rewrites

- `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)`

Additional behavior:
- `Any` annotations are left unchanged (for example, `x: Any = None` stays unchanged).
- Qualified typing references are preserved (for example, `typing.Union[...]` -> `typing.Optional[...]`).
- When bare `Optional[...]` is introduced, Typewriter adds `from typing import Optional` when needed. Also, if `Union` is no longer needed, Typewriter removes the previously imported `Union` from `typing`.

### Directory scanning behavior

When you run against a directory, Typewriter recursively processes `*.py` files and skips common non-source folders such as `.git`, `.venv`, `venv`, `__pycache__`, `build`, and `dist`.

## Installation

```bash
pip install py-typewriter-cli
```

## Quick Start

Suppose you start with:
```python
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}"
```

Preview changes:
```bash
typewriter run path/to/file.py --check
```

Apply changes:
```bash
typewriter run path/to/file.py
```

Result:
```python
from typing import Optional, Any

count: Optional[int] = None
name: Optional[str] = None
payload: Any = None

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

Run recursively on a directory:
```bash
typewriter run examples
```

## CLI

Typewriter exposes one subcommand: `run`.

### Transform a directory

Recursively transforms all `*.py` files under the directory.

```bash
typewriter run path/to/python_package_or_repo
```

### Transform a single file

```bash
typewriter run path/to/python_file.py
```

Non-`.py` files are rejected.

### Check mode (no writes)

Use `--check` to see what would change without writing files.

```bash
typewriter run path/to/python_dir --check
typewriter run path/to/python_file.py --check
```

In `--check` mode, Typewriter prints a unified diff for each file that would change:

```text
Would transform path/to/python_file.py
--- path/to/python_file.py
+++ path/to/python_file.py
@@ ...
-old line
+new line
```

Exit codes for `--check`:
- `0`: no changes needed
- `1`: changes would be made
- `2`: error

### Transform an in-memory string

Use `--code` to transform a string and print the transformed code to stdout (no files are written).

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

`--code` is mutually exclusive with `PATH`. In `--check` mode, this reports whether the provided code would change:

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

When changes are needed, `--check` also prints a unified diff labeled `provided`.

Notes:
- `PATH` and `--code` are mutually exclusive.
- Literal `\\n` sequences in `--code` input are interpreted as newlines.

## Motivation

This project was created to resolve issue [#2303](https://github.com/microsoft/playwright-python/issues/2303) in `microsoft/playwright-python`.
