# match-expression
A Python implementation of TypeScript's [ts-pattern](https://github.com/gvergnaud/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
```bash
pip install match_expression
```
## Quick Start
```python
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
```python
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
```python
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
```python
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:
```python
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`:
```python
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:
```bash
# 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
```bash
git clone https://github.com/qodot/match-expression.git
cd match-expression
```
2. Install development dependencies
```bash
uv sync --dev
```
3. Run tests
```bash
uv run pytest
```
4. Type check
```bash
uv run pyright src/ tests/
```
## Requirements
- Python 3.12 or higher
- No external dependencies
## Special Thanks
- [@code-yeongyu](https://github.com/code-yeongyu) and [@indentcorp](https://github.com/indentcorp)
Raw data
{
"_id": null,
"home_page": null,
"name": "match-expression",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "exhaustive, functional, match, pattern, pattern-matching, ts-pattern, type-safe, typescript",
"author": null,
"author_email": "Qodot <qodot@example.com>",
"download_url": "https://files.pythonhosted.org/packages/08/af/18d19aca9f4065c9da481280f81e8276e3a4325e2536f59fa219ed8d8d1d/match_expression-0.3.0.tar.gz",
"platform": null,
"description": "# match-expression\n\nA Python implementation of TypeScript's [ts-pattern](https://github.com/gvergnaud/ts-pattern), bringing powerful, type-safe pattern matching to Python with an expressive, chainable API.\n\n## Features\n\n- **Chainable API**: Intuitive `match(value).case(pattern, then).exhaustive()` syntax\n- **Type-safe**: Full type inference support with pyright/mypy\n- **Exhaustiveness checking**: Ensures all cases are handled at compile time\n- **Zero dependencies**: Lightweight and fast\n- **Pythonic**: Leverages Python 3.12+ type system features\n\n## Installation\n\n```bash\npip install match_expression\n```\n\n## Quick Start\n\n```python\nfrom typing import Literal\nfrom match_expression import match\n\n# Literal type matching\ndef process_status(status: Literal[\"pending\", \"success\", \"error\"]) -> int:\n return (\n match(status)\n .case(\"pending\", 0)\n .case(\"success\", 1)\n .case(\"error\", -1)\n .exhaustive()\n )\n\n# Type matching with classes\nclass Dog:\n def bark(self) -> str:\n return \"Woof!\"\n\nclass Cat:\n def meow(self) -> str:\n return \"Meow!\"\n\ndef handle_animal(animal: Dog | Cat) -> str:\n return (\n match(animal)\n .case(Dog, lambda d: d.bark())\n .case(Cat, lambda c: c.meow())\n .exhaustive()\n )\n```\n\n## Examples\n\n### Literal Type Matching\n\n```python\nfrom typing import Literal\nfrom match_expression import match\n\ntype Platform = Literal[\"web\", \"mobile\", \"desktop\"]\n\ndef get_app_name(platform: Platform) -> str:\n return (\n match(platform)\n .case(\"web\", \"Web Application\")\n .case(\"mobile\", \"Mobile App\")\n .case(\"desktop\", \"Desktop Software\")\n .exhaustive()\n )\n\n# Type checker knows all cases are covered!\n```\n\n### Class Type Matching\n\n```python\nfrom match_expression import match\n\nclass Success:\n def __init__(self, value: str):\n self.value = value\n\nclass Error:\n def __init__(self, message: str):\n self.message = message\n\ndef handle_result(result: Success | Error) -> str:\n return (\n match(result)\n .case(Success, lambda s: f\"Success: {s.value}\")\n .case(Error, lambda e: f\"Error: {e.message}\")\n .exhaustive()\n )\n```\n\n### Using `otherwise` for Default Cases\n\n```python\nfrom match_expression import match\n\ndef classify_number(n: int) -> str:\n return (\n match(n)\n .case(0, \"zero\")\n .case(1, \"one\")\n .case(2, \"two\")\n .otherwise(\"many\")\n )\n```\n\n### Mixed Return Types\n\nThe library correctly infers union return types:\n\n```python\nfrom match_expression import match\n\ndef process(value: int | str) -> int | str:\n return (\n match(value)\n .case(int, lambda i: i * 2) # Returns int\n .case(str, lambda s: s.upper()) # Returns str\n .exhaustive()\n )\n # Type is inferred as: int | str\n```\n\n### Delayed Evaluation with `eval=False`\n\nYou can defer the evaluation of callable functions by using `eval=False`:\n\n```python\nfrom match_expression import match\nfrom typing import Callable\n\ndef get_handler(command: str) -> Callable[[], str]:\n return (\n match(command)\n .case(\"start\", lambda: \"Starting application...\")\n .case(\"stop\", lambda: \"Stopping application...\")\n .case(\"restart\", lambda: \"Restarting application...\")\n .exhaustive(eval=False) # Returns the lambda without calling it\n )\n\n# Get the handler function without executing it\nhandler = get_handler(\"start\")\n# Execute later when needed\nresult = handler() # \"Starting application...\"\n```\n\nThis is useful when you want to:\n- Return handler functions for later execution\n- Implement lazy evaluation patterns\n- Build command dispatch systems\n\n## API Reference\n\n### `match(value: V) -> Match[V]`\nStarts a pattern matching chain.\n\n### `.case(pattern: P, then: R) -> Case[V, P, R]`\nMatches against a pattern. If the pattern matches, executes `then`.\n\n- `pattern`: A value to match against (for literals) or a type (for isinstance checks)\n- `then`: The value to return or a function to execute with the matched value\n\n### `.exhaustive(eval: bool = True) -> R`\nEnsures all cases are handled. Raises `ExhaustiveError` if not all cases are covered.\n\n- `eval`: When `True` (default), evaluates callable functions. When `False`, returns the callable without evaluating it.\n\n### `.otherwise(default: R, eval: bool = True) -> R`\nProvides a default value for unmatched cases.\n\n- `default`: The value to return or a function to execute when no patterns match\n- `eval`: When `True` (default), evaluates callable functions. When `False`, returns the callable without evaluating it.\n\n## Type Checking\n\nThe library is designed to work with type checkers like pyright and mypy:\n\n```bash\n# Install pyright\npip install pyright\n\n# Type check your code\npyright your_file.py\n```\n\n## Contributing\n\nContributions are welcome! Here's how to get started:\n\n1. Clone the repository\n```bash\ngit clone https://github.com/qodot/match-expression.git\ncd match-expression\n```\n\n2. Install development dependencies\n```bash\nuv sync --dev\n```\n\n3. Run tests\n```bash\nuv run pytest\n```\n\n4. Type check\n```bash\nuv run pyright src/ tests/\n```\n\n## Requirements\n\n- Python 3.12 or higher\n- No external dependencies\n\n## Special Thanks\n- [@code-yeongyu](https://github.com/code-yeongyu) and [@indentcorp](https://github.com/indentcorp)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python implementation of TypeScript's ts-pattern, type-safe pattern matching with an expressive API",
"version": "0.3.0",
"project_urls": {
"Bug Tracker": "https://github.com/qodot/match-expression/issues",
"Homepage": "https://github.com/qodot/match-expression",
"Repository": "https://github.com/qodot/match-expression"
},
"split_keywords": [
"exhaustive",
" functional",
" match",
" pattern",
" pattern-matching",
" ts-pattern",
" type-safe",
" typescript"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "7ed02f4f4d0c9c5ad5c63235816a7500b6f3844c271f98cecab30c07933fd9c3",
"md5": "b0936d4cfb0adc9caa7a289205aa8a33",
"sha256": "27b95e8401206ef7736831d6e3cde9dd26857834c0fc48447e4c15caa5e6b7f4"
},
"downloads": -1,
"filename": "match_expression-0.3.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b0936d4cfb0adc9caa7a289205aa8a33",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 5196,
"upload_time": "2025-09-03T02:52:47",
"upload_time_iso_8601": "2025-09-03T02:52:47.966827Z",
"url": "https://files.pythonhosted.org/packages/7e/d0/2f4f4d0c9c5ad5c63235816a7500b6f3844c271f98cecab30c07933fd9c3/match_expression-0.3.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "08af18d19aca9f4065c9da481280f81e8276e3a4325e2536f59fa219ed8d8d1d",
"md5": "86e0d62dfe41c31ea081382ad972af0b",
"sha256": "6bc0d2f320bcd4a646a7e3f57e89cdea4df5b282b61cdf6f74ad0d4f91c53e24"
},
"downloads": -1,
"filename": "match_expression-0.3.0.tar.gz",
"has_sig": false,
"md5_digest": "86e0d62dfe41c31ea081382ad972af0b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 6141,
"upload_time": "2025-09-03T02:52:49",
"upload_time_iso_8601": "2025-09-03T02:52:49.175187Z",
"url": "https://files.pythonhosted.org/packages/08/af/18d19aca9f4065c9da481280f81e8276e3a4325e2536f59fa219ed8d8d1d/match_expression-0.3.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-03 02:52:49",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "qodot",
"github_project": "match-expression",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "match-expression"
}