Skip to content

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

License

Notifications You must be signed in to change notification settings

qodot/match-expression

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages