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: WhenTrue(default), evaluates callable functions. WhenFalse, 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 matcheval: WhenTrue(default), evaluates callable functions. WhenFalse, 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:
- 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/
Requirements
- Python 3.12 or higher
- No external dependencies
Special Thanks
Release files for match-expression 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| match_expression-0.3.1.tar.gz | 6.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| match_expression-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 11.4 kB
Release files / match_expression-0.3.1.tar.gz
| Download URL | match_expression-0.3.1.tar.gz |
|---|---|
| Size | 6.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
82b80b5de4063ac824fb431e47fcc3e59db9649658dd1c9aed82854d0e1d6769
|
|
BLAKE2b-256 checksum How to use checksums |
7e6eb3c0719daecfec905dac78054e76058d88a37d17517b88d6aa98659957ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.10
|
Release files / match_expression-0.3.1-py3-none-any.whl
| Download URL | match_expression-0.3.1-py3-none-any.whl |
|---|---|
| Size | 5.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3487e4209a4a1530f0bed45fe261d3e6178d99d687c09caa9d510bd10ecca2db
|
|
BLAKE2b-256 checksum How to use checksums |
33ba4fe3c8d49634bda1f7403821ad7561f433b0ee8b3f0eab4dc6182bbc70c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.10
|