Technology1 min read

Show HN: Gawain – Require type hints on all Python variables

By · Published by Everything Blog

In short

Gawain operates as a pre-check utility that halts the downstream type checking step whenever it detects missing variable or class attribute annotations. The system pairs by default with ty, though it can alternative run pyrefly or execute in a standalone mode that only inspects annotation presence. In standard operation, variables with obvious types like simple constructor calls are permitted without annotations if they are never reassigned, though an optional strict mode enforces annotations on every assignment. Teams can integrate the tool into pre-commit pipelines using Rust 1.97 or newer and manage rules via configuration files.

Key points

  • Standard settings allow unannotated variables for literals and simple constructors provid…: Standard settings allow unannotated variables for literals and simple constructors provided the variable is never reassigned.
  • Developers can activate a strict mode to mandate explicit type annotations on every singl…: Developers can activate a strict mode to mandate explicit type annotations on every single variable assignment.
  • The utility integrates into pre-commit workflows requiring Rust 1.97 or newer and support…: The utility integrates into pre-commit workflows requiring Rust 1.97 or newer and supports code-level error suppressions.

Sources checked by our analysis pipeline: 1

gawain requires type annotations on the variables, parameters, returns and class attributes of Python code, then runs a type checker, ty or pyrefly, to check that the types are correct.

Type checkers such as mypy, Pyright, ty and pyrefly infer the type of a variable that has no annotation. ruff's ANN

rules and flake8-annotations ask for annotations on functions only. gawain also asks for them on variables and

class attributes.

I built gawain only because I hate dict[str, Any]

and wanted to do something about it. If your answer to

this is "but Python already infers that" or "but my IDE infers it for me", then don't use it, little padawan.

Given shop.py

:

PORT = 8080

retries = 3

total = compute()

def send(to, body):

print(f"to {to}: {body}")

class Order:

def __init__(self, items: list[str]) -> None:

self.items = items

gawain check shop.py

prints:

[presence]

shop.py:1:1 GW001 variable 'PORT' has no type annotation

shop.py:3:1 GW001 variable 'total' has no type annotation

shop.py:6:5 GW003 function 'send' has no return type annotation

shop.py:6:10 GW002 parameter 'to' of function 'send' has no type…

Technology