Skip to main content

TypeScript's ts-pattern for Python - powerful, type-safe pattern matching with an expressive API

Project description

match-expression

A Python implementation of TypeScript's ts-pattern, bringing powerful, type-safe pattern matching to Python with an expressive, chainable API.

Features

  • Chainable API: Intuitive match(value).case(pattern, then).exhaustive() syntax
  • Type-safe: Full type inference support with pyright/mypy
  • Exhaustiveness checking: Ensures all cases are handled at compile time
  • Zero dependencies: Lightweight and fast
  • Pythonic: Leverages Python 3.10+ type system features

Installation

pip install match-expression

Quick Start

from typing import Literal
from match_expression import match

# Literal type matching
def process_status(status: Literal["pending", "success", "error"]) -> int:
    return (
        match(status)
        .case("pending", 0)
        .case("success", 1)
        .case("error", -1)
        .exhaustive()
    )

# Type matching with classes
class Dog:
    def bark(self) -> str:
        return "Woof!"

class Cat:
    def meow(self) -> str:
        return "Meow!"

def handle_animal(animal: Dog | Cat) -> str:
    return (
        match(animal)
        .case(Dog, lambda d: d.bark())
        .case(Cat, lambda c: c.meow())
        .exhaustive()
    )

Examples

Literal Type Matching

from typing import Literal
from match_expression import match

type Platform = Literal["web", "mobile", "desktop"]

def get_app_name(platform: Platform) -> str:
    return (
        match(platform)
        .case("web", "Web Application")
        .case("mobile", "Mobile App")
        .case("desktop", "Desktop Software")
        .exhaustive()
    )

# Type checker knows all cases are covered!

Class Type Matching

from match_expression import match

class Success:
    def __init__(self, value: str):
        self.value = value

class Error:
    def __init__(self, message: str):
        self.message = message

def handle_result(result: Success | Error) -> str:
    return (
        match(result)
        .case(Success, lambda s: f"Success: {s.value}")
        .case(Error, lambda e: f"Error: {e.message}")
        .exhaustive()
    )

Using otherwise for Default Cases

from match_expression import match

def classify_number(n: int) -> str:
    return (
        match(n)
        .case(0, "zero")
        .case(1, "one")
        .case(2, "two")
        .otherwise("many")
    )

Mixed Return Types

The library correctly infers union return types:

from match_expression import match

def process(value: int | str) -> int | str:
    return (
        match(value)
        .case(int, lambda i: i * 2)      # Returns int
        .case(str, lambda s: s.upper())  # Returns str
        .exhaustive()
    )
    # Type is inferred as: int | str

How It Works

Type System Design

The library uses Python's powerful type system with TypeVars to ensure type safety:

  • V: The type of the matched value
  • P: The narrowed type after pattern matching
  • R: The return type of the first branch
  • UR: Union of return types from multiple branches

This design enables:

  1. Type narrowing: In each when branch, the value is narrowed to the specific matched type
  2. Return type union: Different branches can return different types, and the final type is their union
  3. Exhaustiveness checking: The type system ensures all possible cases are handled

Example Type Flow

match(value: Dog | Cat | Bird)    # V = Dog | Cat | Bird
  .case(Dog, lambda d: 123)       # P = Dog, R = int
  .case(Cat, lambda c: "hello")   # P = Cat, UR = str
  .case(Bird, lambda b: True)     # P = Bird, UR = bool
  .exhaustive()                   # Returns: int | str | bool

API Reference

match(value: V) -> Match[V]

Starts a pattern matching chain.

.case(pattern: P, then: R) -> Case[V, P, R]

Matches against a pattern. If the pattern matches, executes then.

  • pattern: A value to match against (for literals) or a type (for isinstance checks)
  • then: The value to return or a function to execute with the matched value

.exhaustive() -> R

Ensures all cases are handled. Raises ExhaustiveError if not all cases are covered.

.otherwise(default: R) -> R

Provides a default value for unmatched cases.

Type Checking

The library is designed to work with type checkers like pyright and mypy:

# Install pyright
pip install pyright

# Type check your code
pyright your_file.py

Contributing

Contributions are welcome! Here's how to get started:

  1. Clone the repository
git clone https://github.com/qodot/py-pattern.git
cd py-pattern
  1. Install development dependencies
uv sync --dev
  1. Run tests
uv run pytest
  1. Type check
uv run pyright src/ tests/

Comparison with Alternatives

Feature match-expression Python match/case Traditional if/elif
Expression-based
Type inference Partial
Exhaustiveness check
Chainable API
Runtime overhead Minimal None None

Requirements

  • Python 3.10 or higher
  • No external dependencies

License

MIT License - see LICENSE file for details

Acknowledgments

This library is inspired by the excellent ts-pattern library for TypeScript. We aim to bring the same level of type safety and expressiveness to Python.

Resources


Made with ❤️ for the Python community

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

match_expression-0.1.0.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

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

match_expression-0.1.0-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

File details

Details for the file match_expression-0.1.0.tar.gz.

File metadata

  • Download URL: match_expression-0.1.0.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.10

File hashes

Hashes for match_expression-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3b87823b90294e94ab385e6e72887d6181850a9978f4a4d9aa7c563c6fc87f92
MD5 2892e87f803f63064629c0b9d1161a23
BLAKE2b-256 45422146de581c521d5ef4675b693ff57e2a4355e6d44fe3960854849f12c606

See more details on using hashes here.

File details

Details for the file match_expression-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for match_expression-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 530e04eb988517fb7ca02e3400d6df736ad5f58feb5cb072b0225c695def4f35
MD5 012b54496e9946acb5e1d0c06c6f7b10
BLAKE2b-256 90c92d15f15599b86d49d42fa72f69cc876b03eb1e6b9e85705a8c2f2f185494

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