A Python implementation of TypeScript's ts-pattern, bringing powerful, type-safe pattern matching to Python with an expressive, chainable API.
- 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
pip install match_expressionfrom 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()
)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!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()
)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")
)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 | strYou 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
Starts a pattern matching chain.
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
Ensures all cases are handled. Raises ExhaustiveError if not all cases are covered.
eval: WhenTrue(default), evaluates callable functions. WhenFalse, returns the callable without evaluating it.
Provides a default value for unmatched cases.
default: The value to return or a function to execute when no patterns matcheval: WhenTrue(default), evaluates callable functions. WhenFalse, returns the callable without evaluating it.
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.pyContributions are welcome! Here's how to get started:
- Clone the repository
git clone https://github.com/qodot/match-expression.git
cd match-expression- Install development dependencies
uv sync --dev- Run tests
uv run pytest- Type check
uv run pyright src/ tests/- Python 3.12 or higher
- No external dependencies