Skip to main content

A Python implementation of TypeScript's ts-pattern, 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.12+ 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

Delayed Evaluation with eval=False

You can defer the evaluation of callable functions by using eval=False:

from match_expression import match
from typing import Callable

def get_handler(command: str) -> Callable[[], str]:
    return (
        match(command)
        .case("start", lambda: "Starting application...")
        .case("stop", lambda: "Stopping application...")
        .case("restart", lambda: "Restarting application...")
        .exhaustive(eval=False)  # Returns the lambda without calling it
    )

# Get the handler function without executing it
handler = get_handler("start")
# Execute later when needed
result = handler()  # "Starting application..."

This is useful when you want to:

  • Return handler functions for later execution
  • Implement lazy evaluation patterns
  • Build command dispatch systems

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(eval: bool = True) -> R

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

  • eval: When True (default), evaluates callable functions. When False, returns the callable without evaluating it.

.otherwise(default: R, eval: bool = True) -> R

Provides a default value for unmatched cases.

  • default: The value to return or a function to execute when no patterns match
  • eval: When True (default), evaluates callable functions. When False, returns the callable without evaluating it.

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/match-expression.git
cd match-expression
  1. Install development dependencies
uv sync --dev
  1. Run tests
uv run pytest
  1. Type check
uv run pyright src/ tests/

Requirements

  • Python 3.12 or higher
  • No external dependencies

Special Thanks

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.3.1.tar.gz (6.2 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.3.1-py3-none-any.whl (5.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for match_expression-0.3.1.tar.gz
Algorithm Hash digest
SHA256 82b80b5de4063ac824fb431e47fcc3e59db9649658dd1c9aed82854d0e1d6769
MD5 6f96adfea6b456af98bfa3a88f7f4c60
BLAKE2b-256 7e6eb3c0719daecfec905dac78054e76058d88a37d17517b88d6aa98659957ed

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for match_expression-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3487e4209a4a1530f0bed45fe261d3e6178d99d687c09caa9d510bd10ecca2db
MD5 d0cd0058be8997e3c059f116fee97d7a
BLAKE2b-256 33ba4fe3c8d49634bda1f7403821ad7561f433b0ee8b3f0eab4dc6182bbc70c8

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