{"id":13594549,"url":"https://github.com/scravy/awesome-pattern-matching","last_synced_at":"2025-04-09T16:10:34.944Z","repository":{"id":45193011,"uuid":"326142059","full_name":"scravy/awesome-pattern-matching","owner":"scravy","description":"Pattern Matching for Python 3.7+ in a simple, yet powerful, extensible manner.","archived":false,"fork":false,"pushed_at":"2023-03-29T14:24:09.000Z","size":217,"stargazers_count":107,"open_issues_count":3,"forks_count":6,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-05-22T05:00:38.430Z","etag":null,"topics":["functional","libraries","library","matcher","pattern-matching","pypy3","python","python3","python310","python37","python38","python39","structural-patterns","validation","validation-library"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/awesome-pattern-matching/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scravy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-01-02T08:39:58.000Z","updated_at":"2024-01-25T11:47:12.000Z","dependencies_parsed_at":"2023-07-18T09:14:10.742Z","dependency_job_id":null,"html_url":"https://github.com/scravy/awesome-pattern-matching","commit_stats":{"total_commits":260,"total_committers":4,"mean_commits":65.0,"dds":0.09230769230769231,"last_synced_commit":"ec62254242f15294edb85e4f33c0ae156ab640bf"},"previous_names":["scravy/ornament"],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scravy%2Fawesome-pattern-matching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scravy%2Fawesome-pattern-matching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scravy%2Fawesome-pattern-matching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scravy%2Fawesome-pattern-matching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scravy","download_url":"https://codeload.github.com/scravy/awesome-pattern-matching/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065283,"owners_count":21041871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional","libraries","library","matcher","pattern-matching","pypy3","python","python3","python310","python37","python38","python39","structural-patterns","validation","validation-library"],"created_at":"2024-08-01T16:01:35.509Z","updated_at":"2025-04-09T16:10:34.921Z","avatar_url":"https://github.com/scravy.png","language":"Python","funding_links":[],"categories":["Python","Awesome Functional Python"],"sub_categories":["Libraries"],"readme":"# Awesome Pattern Matching (_apm_) for Python\n\n[![Github Actions](https://github.com/scravy/awesome-pattern-matching/workflows/Python%20application/badge.svg)](https://github.com/scravy/awesome-pattern-matching/actions)\n[![Downloads](https://pepy.tech/badge/awesome-pattern-matching)](https://pepy.tech/project/awesome-pattern-matching)\n[![PyPI version](https://badge.fury.io/py/awesome-pattern-matching.svg)](https://pypi.org/project/awesome-pattern-matching/)\n\n```bash\npip install awesome-pattern-matching\n```\n\n- Simple\n- Powerful\n- Extensible\n- Composable\n- Functional\n- Python 3.7+, PyPy3.7+\n- Typed (IDE friendly)\n- Offers different styles (expression, declarative, statement, ...)\n\nThere's a ton of pattern matching libraries available for python, all with varying degrees of maintenance and usability;\nalso [since Python 3.10 there is the PEP-634 `match` statement](https://www.python.org/dev/peps/pep-0634/). However,\nthis library still offers functionality that PEP-634 doesn't offer, as well as pattern matching for python versions\nbefore 3.10. [A detailed comparison of PEP-634 and _`apm`_ is available](https://github.com/scravy/awesome-pattern-matching/blob/main/docs/apm_vs_pep634.md).\n\n_`apm`_ defines patterns as objects which are _composable_ and _reusable_. Pieces can be matched and captured into\nvariables, much like pattern matching in Haskell or Scala (a feature which most libraries actually lack, but which also\nmakes pattern matching useful in the first place - the capability to easily extract data). Here is an example:\n\n```python\nfrom apm import *\n\nif result := match([1, 2, 3, 4, 5], [1, '2nd' @ _, '3rd' @ _, 'tail' @ Remaining(...)]):\n    print(result['2nd'])   # 2\n    print(result['3rd'])   # 3\n    print(result['tail'])  # [4, 5]\n\n# If you find it more readable, '\u003e\u003e' can be used instead of '@' to capture a variable\nmatch([1, 2, 3, 4, 5], [1, _ \u003e\u003e '2nd', _ \u003e\u003e '3rd', Remaining(...) \u003e\u003e 'tail'])\n```\n\nPatterns can be composed using `\u0026`, `|`, and `^`, or via their more explicit counterparts `AllOf`, `OneOf`, and `Either`\n. Since patterns are objects, they can be stored in variables and be reused.\n\n```python\npositive_integer = InstanceOf(int) \u0026 Check(lambda x: x \u003e= 0)\n```\n\nSome fancy matching patterns are available out of the box:\n\n```python\nfrom apm import *\n\ndef f(x: int, y: float) -\u003e int:\n    pass\n\nif match(f, Arguments(int, float) \u0026 Returns(int)):\n    print(\"Function satisfies required signature\")\n```\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [Multiple Styles](#multiple-styles)\n- [Nested pattern matches](#nested-pattern-matches)\n- [Multimatch](#multimatch)\n- [Strict vs non-strict matches](#strict-vs-non-strict-matches)\n- [Match head and tail of a list](#match-head-and-tail-of-a-list)\n- [Wildcard matches anything using `_`](#wildcard-matches-anything-using-_)\n- [Wildcard matches anything using `...`](#wildcard-matches-anything-using-)\n- [Support for dataclasses](#support-for-dataclasses)\n- [The different styles in detail](#the-different-styles-in-detail)\n  - [Simple style](#simple-style)\n    - [pre `:=` version (Python 3.7)](#pre--version-python-37)\n  - [Expression style](#expression-style)\n  - [Statement style](#statement-style)\n  - [Declarative style](#declarative-style)\n    - [Nota bene: Overloading using `@case_distinction`](#nota-bene-overloading-using-case_distinction)\n  - [Terse style](#terse-style)\n- [Available patterns](#available-patterns)\n  - [`Capture(pattern, name=\u003cstr\u003e)`](#capturepattern-namestr)\n  - [`Strict(pattern)`](#strictpattern)\n  - [`OneOf(*pattern)`](#oneofpattern)\n  - [`AllOf(*pattern)`](#allofpattern)\n  - [`NoneOf(*pattern)`](#noneofpattern)\n  - [`Not(pattern)`](#notpattern)\n  - [`Each(pattern [, at_least=]`](#eachpattern--at_least)\n  - [`EachItem(key_pattern, value_pattern)`](#eachitemkey_pattern-value_pattern)\n  - [`Some(pattern)` (aka `Many` and `Remaining`)](#somepattern-aka-many-and-remaining)\n  - [`Remainder(pattern)`](#remainderpattern)\n  - [`Between(lower, upper)`](#betweenlower-upper)\n  - [`Length(length)`](#lengthlength)\n  - [`Contains(item)`](#containsitem)\n  - [`Regex(regex_pattern, bind_groups: bool = True)`](#regexregex_pattern-bind_groups-bool--true)\n  - [`Check(predicate)`](#checkpredicate)\n  - [`InstanceOf(*types)`](#instanceoftypes)\n  - [`SubclassOf(*types)`](#subclassoftypes)\n  - [`Parameters(...)`](#parameters)\n  - [`Arguments(*types)`](#argumentstypes)\n  - [`Returns(type)`](#returnstype)\n  - [`Transformed(function, pattern)`](#transformedfunction-pattern)\n  - [`At(path, pattern)`](#atpath-pattern)\n  - [`Items(**kwargs))`](#itemskwargs)\n  - [`Object(type, *args, **kwargs)`](#objecttype-args-kwargs)\n- [Extensible](#extensible)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n## Multiple Styles\n\nFor matching and selecting from multiple cases, choose your style:\n\n```python\nfrom apm import *\n\nvalue = 7\n\n# The simple style\nif match(value, Between(1, 10)):\n    print(\"It's between 1 and 10\")\nelif match(value, Between(11, 20)):\n    print(\"It's between 11 and 20\")\nelse:\n    print(\"It's not between 1 and 20\")\n    \n# The expression style\ncase(value) \\\n    .of(Between(1, 10), lambda: print(\"It's between 1 and 10\")) \\\n    .of(Between(11, 20), lambda: print(\"It's between 11 and 20\")) \\\n    .otherwise(lambda: print(\"It's not between 1 and 20\"))\n\n# The statement style\ntry:\n    match(value)\nexcept Case(Between(1, 10)):\n    print(\"It's between 1 and 10\")\nexcept Case(Between(11, 20)):\n    print(\"It's between 11 and 20\")\nexcept Default:\n    print(\"It's not between 1 and 20\")\n\n# The declarative style\n@case_distinction\ndef f(n: Match(Between(1, 10))):\n    print(\"It's between 1 and 10\")\n\n@case_distinction\ndef f(n: Match(Between(11, 20))):\n    print(\"It's between 11 and 20\")\n\n@case_distinction\ndef f(n):\n    print(\"It's not between 1 and 20\")\n\nf(value)\n\n# The terse (pampy) style\nmatch(value,\n      Between( 1, 10), lambda: print(\"It's between 1 and 10\"),\n      Between(11, 20), lambda: print(\"It's between 11 and 20\"),\n      _,               lambda: print(\"It's not between 1 and 20\"))\n```\n\n\n## Nested pattern matches\n\nPatterns are applied recursively, such that nested structures can be matched arbitrarily deep.\nThis is super useful for extracting data from complicated structures:\n\n```python\nfrom apm import *\n\nsample_k8s_response = {\n    \"containers\": [\n        {\n            \"args\": [\n                \"--cert-dir=/tmp\",\n                \"--secure-port=4443\",\n                \"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname\",\n                \"--kubelet-use-node-status-port\"\n            ],\n            \"image\": \"k8s.gcr.io/metrics-server/metrics-server:v0.4.1\",\n            \"imagePullPolicy\": \"IfNotPresent\",\n            \"name\": \"metrics-server\",\n            \"ports\": [\n                {\n                    \"containerPort\": 4443,\n                    \"name\": \"https\",\n                    \"protocol\": \"TCP\"\n                }\n            ]\n        }\n    ]\n}\n\nif result := match(sample_k8s_response, {\n        \"containers\": Each({\n            \"image\": 'image' @ _,\n            \"name\": 'name' @ _,\n            \"ports\": Each({\n                \"containerPort\": 'port' @ _\n            }),\n        })\n    }):\n    print(f\"Image: {result['image']}, Name: {result['name']}, Port: {result['port']}\")\n```\n\nThe above will print\n\n```\nImage: k8s.gcr.io/metrics-server/metrics-server:v0.4.1, Name: metrics-server, Port: 4443\n```\n\n\n## Multimatch\n\nBy default `match` records only the last match for captures. If for example `'item' @ InstanceOf(int)` matches multiple times,\nthe last match will be recorded in `result['item']`. `match` can record all captures using the `multimatch=True` flag:\n\n```python\nif result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...)), multimatch=True):\n    print(result['item'])  # [5, 7]\n\n# The default since v0.15.0 is multimatch=False\nif result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...))):\n  print(result['item'])  # 7\n```\n\n\n## Strict vs non-strict matches\n\nAny value which occurs verbatim in a pattern is matched verbatim (`int`, `str`, `list`, ...), except Dictionaries (\nanything which has an `items()` actually).\n\nThus:\n\n```python\nsome_very_complex_object = {\n    \"A\": 1,\n    \"B\": 2,\n    \"C\": 3,\n}\nmatch(some_very_complex_object, {\"C\": 3})  # matches!\n```\n\nIf you do not want unknown keys to be ignored, wrap the pattern in a `Strict`:\n\n```python\n# does not match, only matches exactly `{\"C\": 3}`\nmatch(some_very_complex_object, Strict({\"C\": 3}))\n```\n\nLists (anything iterable which does not have an `items()` actually) are also compared as they are, i.e.:\n\n```python\nls = [1, 2, 3]\nmatch(ls, [1, 2, 3])  # matches\nmatch(ls, [1, 2])  # does not match\n```\n\n\n## Match head and tail of a list\n\nIt is possible to match the remainder of a list though:\n\n```python\nmatch(ls, [1, 2, Remaining(InstanceOf(int))])\n```\n\nAnd each item:\n\n```python\nmatch(ls, Each(InstanceOf(int)))\n```\n\nPatterns can be joined using `\u0026`, `|`, and `^`:\n\n```python\nmatch(ls, Each(InstanceOf(int) \u0026 Between(1, 3)))\n```\n\nWild-card matches are supported using Ellipsis (`...`):\n\n```python\nmatch(ls, [1, Remaining(..., at_least=2)])\n```\n\nThe above example also showcases how `Remaining` can be made to match\n`at_least` _n_ number of items (`Each` also has an `at_least` keyword argument).\n\n\n## Wildcard matches anything using `_`\n\nA wildcard pattern can be expressed using `_`. `_` is a `Pattern` and thus `\u003e\u003e` and `@` can be used with it.\n\n```python\nmatch([1, 2, 3, 4], [1, _, 3, _])\n```\n\n\n## Wildcard matches anything using `...`\n\nThe `Ellipsis` can be used as a wildcard match, too. It is however not a `Pattern` (so `|`, `\u0026`, `@`, etc. can not\nbe used on it). If you actually want to match `Ellipsis`, wrap it using `Value(...)`.\n\nOtherwise `...` is equivalent for most intents and purposes to `_`:\n\n```python\nmatch([1, 2, 3, 4], [1, ..., 3, ...])\n```\n\n\n## Support for dataclasses\n\n```python\n@dataclass\nclass User:\n    first_name: str\n    last_name: str\n\nvalue = User(\"Jane\", \"Doe\")\n\nif match(value, User(_, \"Doe\")):\n    print(\"Welcome, member of the Doe family!\")\nelif match(value, User(_, _)):\n    print(\"Welcome, anyone!\")\n```\n\n\n## The different styles in detail\n\n### Simple style\n\n- 💚 has access to result captures\n- 💚 vanilla python\n- 💔 no case guards\n- 💔 can not return values (since it's a statement, not an expression)\n- 🖤 a bit repetetive\n- 💚 simplest and most easy to understand style\n- 🖤 fastest of them all\n\n```python\nfrom apm import *\n\nvalue = {\"a\": 7, \"b\": \"foo\", \"c\": \"bar\"}\n\nif result := match(value, EachItem(_, 'value' @ InstanceOf(str) | ...), multimatch=True):\n    print(result['value'])  # [\"foo\", \"bar\"]\n```\n\n#### pre `:=` version (Python 3.7)\n\n`bind()` can be used on a `MatchResult` to bind the matched items to an existing dictionary.\n\n```python\nfrom apm import *\n\nvalue = {\"a\": 7, \"b\": \"foo\", \"c\": \"bar\"}\n\nresult = {}\nif match(value, EachItem(_, 'value' @ InstanceOf(str) | ...)).bind(result):\n    print(result['value'])  # [\"foo\", \"bar\"]\nelif match(value, {\"quux\": _ \u003e\u003e 'quux'}).bind(result):\n    print(result['quux'])\n```\n\n### Expression style\n\n- 💚 has access to result captures\n- 💚 vanilla python\n- 💚 can return values directly as it is an expression\n- 💚 can use case guards via `when=` or `guarded`\n- 🖤 so terse that it is sometimes hard to read\n\nThe expression style is summarized:\n\n```python\ncase(value).of(pattern, action) ... .otherwise(default_action)\n```\n\n...where action is either a value or a callable. The captures from the matching result are bound to the named\nparameters of the given callable, i.e. `result['foo']` and `result['bar']` from `'foo' @ _` and `'bar' @ _` will be\nbound to `foo` and `bar` respectively in `lambda foo, bar: ...`.\n\n```python\nfrom apm import *\n\ndisplay_name = case({'user': 'some-user-id', 'first_name': \"Jane\", 'last_name': \"Doe\"}) \\\n    .of({'first_name': 'first' @ _, 'last_name': 'last' @ _}, lambda first, last: f\"{first}, {last}\") \\\n    .of({'user': 'user_id' @ _}, lambda user_id: f\"#{user_id}\") \\\n    .otherwise(\"anonymous\")\n```\n\n_Note: To return a value an `.otherwise(...)` case must always be present._\n\n\n### Statement style\n\nThis is arguable the most hacky style in _`apm`_, as it re-uses the `try .. except`\nmechanism. It is nevertheless quite readable.\n\n- 💚 has access to result captures\n- 💚 very readable\n- 💔 can not return values (since it's a statement, not an expression)\n- 💚 can use case guards via `when=`\n- 🖤 misuse of the `try .. except` statement\n\n```python\nfrom apm import *\n\ntry:\n    match({'user': 'some-user-id', 'first_name': \"Jane\", 'last_name': \"Doe\"})\nexcept Case({'first_name': 'first' @ _, 'last_name': 'last' @ _}) as result:\n    user = f\"{result['first']} {result['last']}\"\nexcept Case({'user': 'user_id' @ _}) as result:\n    user = f\"#{result['user_id']}\"\nexcept Default:\n    user = \"anonymous\"\n    \nprint(user)  # \"Jane Doe\"\n```\n\n\n### Declarative style\n\n- 💔 does not have access to result captures\n- 💚 very readable\n- 💚 can use case guards via `when=`\n- 💚 can return values\n- 🖤 the most bloated version of all styles\n\n```python\nfrom apm import *\n\n@case_distinction\ndef fib(n: Match(OneOf(0, 1))):\n   return n\n\n@case_distinction\ndef fib(n):\n    return fib(n - 2) + fib(n - 1)\n\nfor i in range(0, 6):\n    print(fib(i))\n```\n\n#### Nota bene: Overloading using `@case_distinction`\n\nIf not for its pattern matching capabilities, `@case_distinction` can be used\nto implement overloading. In fact, it can be imported as `@overload`.\nThe mechanism is aware of arity and argument types.\n\n```python\nfrom apm.overload import overload\n\n@overload\ndef add(a: str, b: str):\n    return \"\".join([a, b])\n\n@overload\ndef add(a: int, b: int):\n    return a + b\n\nadd(\"a\", \"b\")\nadd(1, 2)\n```\n\n### Terse style\n\n- 💚 has access to result captures\n- 💚 can use case guards via `guarded`\n- 💚 very concise\n- 💚 can return values\n- 🖤 very readable when formatted nicely\n- 🖤 not so well suited for larger match actions\n- 🖤 slowest of them all\n\nAs the name indicates the \"terse\" style is terse. It is inspired by the `pampy`\npattern matching library and mimics some of its behavior. Despite a slim surface\narea it also comes with some simplifications:\n\n- A type given as a pattern is matched against as if it was wrapped in an `InstanceOf`\n- `re.Pattern` objects (result of `re.compile`) are matched against as if it was given via `Regex`\n- Captures are passed to actions in the same order as they occur in the pattern (not by name)\n  \n```python\nfrom apm import *\n\ndef fibonacci(n):\n  return match(n,\n               1, 1,\n               2, 1,\n               _, lambda x: fibonacci(x - 1) + fibonacci(x - 2)\n               )\n\nfibonacci(6)  # -\u003e 8 \n\n\nclass Animal:        pass\nclass Hippo(Animal): pass\nclass Zebra(Animal): pass\nclass Horse(Animal): pass\n\ndef what_am_i(x):\n  return match(x,\n               Hippo,  'hippopotamus',\n               Zebra,  'zebra',\n               Animal, 'some other animal',\n               _,      'not at all an animal',\n               )\n\nwhat_am_i(Hippo())  # -\u003e 'hippopotamus'\nwhat_am_i(Zebra())  # -\u003e 'zebra'\nwhat_am_i(Horse())  # -\u003e 'some other animal'\nwhat_am_i(42)       # -\u003e 'not at all an animal'\n```\n\n\n## Available patterns\n\n### `Capture(pattern, name=\u003cstr\u003e)`\n\nCaptures a piece of the thing being matched by name.\n\n```python\nif result := match([1, 2, 3, 4], [1, 2, Capture(Remaining(InstanceOf(int)), name='tail')]):\n    print(result['tail'])  ## -\u003e [3, 4]\n```\n\nAs this syntax is rather verbose, two shorthand notations can be used:\n\n```python\n# using the matrix multiplication operator '@' (syntax resembles that of Haskell and Scala)\nif result := match([1, 2, 3, 4], [1, 2, 'tail' @ Remaining(InstanceOf(int))]):\n    print(result['tail'])  ## -\u003e [3, 4]\n\n# using the right shift operator\nif result := match([1, 2, 3, 4], [1, 2, Remaining(InstanceOf(int)) \u003e\u003e 'tail']):\n    print(result['tail'])  ## -\u003e [3, 4]\n```\n\n\n### `Strict(pattern)`\n\nPerforms a strict pattern match. A strict pattern match also compares the type of verbatim values. That is, while\n_`apm`_ would match `3` with `3.0` it would not do so when using `Strict`. Also _`apm`_ performs partial matches of\ndictionaries (that is: it ignores unknown keys). It will perform an exact match for dictionaries using `Strict`.\n\n```python\n# The following will match\nmatch({\"a\": 3, \"b\": 7}, {\"a\": ...})\nmatch(3.0, 3)\n\n# These will not match\nmatch({\"a\": 3, \"b\": 7}, Strict({\"a\": ...}))\nmatch(3.0, Strict(3))\n```\n\n\n### `OneOf(*pattern)`\n\nMatches against any of the provided patterns. Equivalent to `p1 | p2 | p3 | ..`\n(but operator overloading does not work with values that do not inherit from `Pattern`)\n\n```python\nmatch(\"quux\", OneOf(\"bar\", \"baz\", \"quux\"))\n```\n\n```python\nmatch(3, OneOf(InstanceOf(int), None))\n```\n\nPatterns can also be joined using `|` to form a `OneOf` pattern:\n\n```python\nmatch(3, InstanceOf(int) | InstanceOf(float))\n```\n\nThe above example is rather contrived, as `InstanceOf` already accepts multiple types natively:\n\n```python\nmatch(3, InstanceOf(int, float))\n```\n\nSince bare values do not inherit from `Pattern` they can be wrapped in `Value`:\n\n```python\nmatch(\"quux\", Value(\"foo\") | Value(\"quux\"))\n```\n\n\n### `AllOf(*pattern)`\n\nChecks whether the value matches all of the given pattern. Equivalent to `p1 \u0026 p2 \u0026 p3 \u0026 ..`\n(but operator overloading does not work with values that do not inherit from `Pattern`)\n\n```python\nmatch(\"quux\", AllOf(InstanceOf(\"str\"), Regex(\"[a-z]+\")))\n```\n\n\n### `NoneOf(*pattern)`\n\nSame as `Not(OneOf(*pattern))` (also `~OneOf(*pattern)`).\n\n\n### `Not(pattern)`\n\nMatches if the given pattern does not match.\n\n```python\nmatch(3, Not(4))  # matches\nmatch(5, Not(4))  # matches\nmatch(4, Not(4))  # does not match\n```\n\nThe bitflip prefix operator (`~`) can be used to express the same thing. Note that it does not work on bare values,\nso they need to be wrapped in `Value`.\n\n```python\nmatch(3, ~Value(4))  # matches\nmatch(5, ~Value(4))  # matches\nmatch(4, ~Value(4))  # does not match\n```\n\n`Not` can be used do create a `NoneOf` kind of pattern:\n\n```python\nmatch(\"string\", ~OneOf(\"foo\", \"bar\"))  # matches everything except \"foo\" and \"bar\"\n```\n\n`Not` can be used to create a pattern that never matches:\n\n```python\nNot(...)\n```\n\n\n### `Each(pattern [, at_least=]`\n\nMatches each item in an iterable.\n\n```python\nmatch(range(1, 10), Each(Between(1, 9)))\n```\n\n\n### `EachItem(key_pattern, value_pattern)`\n\nMatches an object if each key satisfies `key_pattern` and each value satisfies `value_pattern`.\n\n```python\nmatch({\"a\": 1, \"b\": 2}, EachItem(Regex(\"[a-z]+\"), InstanceOf(int)))\n```\n\n\n### `Some(pattern)` (aka `Many` and `Remaining`)\n\nMatches a sequence of items within a list:\n\n```python\nif result := match(range(1, 10), [1, 'a' @ Some(...), 4, 'b' @ Some(...), 8, 9]):\n    print(result['a'])  # [2, 3]\n    print(result['b'])  # [5, 6, 7]\n```\n\nTakes the optional values `exactly`, `at_least`, and `at_most` which makes `Some` match\neither `exactly` _n_ items, `at_least` _n_, or `at_most` _n_ items (`at_least` and `at_most` can be given at the same\ntime, but not together with `exactly`).\n\nNote the difference between `Some(1, 2)` and `Some([1, 2])`. The first version matches subsequences, the second\nversion matches items which are themselves lists:\n\n```python\nmatch([0,  1, 2 ,  1, 2 , 3], [0, Some( 1, 2 ), 3])  # matches the subsequence 1, 2 twice\nmatch([0, [1, 2], [1, 2], 3], [0, Some([1, 2]), 3])  # matches the item [1, 2] twice, which happen to be lists\n```\n\n`Some` also goes by the names of `Many` and `Remaining`, which is sometimes nice to convey meaning:\n\n```python\nmatch(range(1, 10), [1, 2, 'remaining' @ Remaining()])\nmatch([0, 1, 1, 1, 2, 1], [0, Many(1), Remaining(InstanceOf(int))])\n```\n\nWhen used with no arguments, `Some()` is the same as `Some(...)`.\n\n\n### `Remainder(pattern)`\n\nCan be used to match the unmatched parts of a Dictionary/Mapping.\n\n```python\nresult = match({\n    \"foo\": 1,\n    \"bar\": 2,\n    \"qux\": 4,\n    \"quuz\": 8,\n}, {\"foo\": 'foo' @ _, \"bar\": 'bar' @ _} ** Remainder('rs' @ _))\nprint(result.foo)  # 1\nprint(result.bar)  # 2\nprint(result.rs)   # {'qux': 4, 'quuz': 8}\n```\n\n`Remainder` is, strictly speaking, not a `Pattern` and only works in conjunction with `**` on dictionaries,\nand it only works on the right-hand side of the dictionary.\n\n\n### `Between(lower, upper)`\n\nMatches an object if it is between `lower` and `upper` (inclusive). The optional keyword arguments\n`lower_bound_exclusive` and `upper_bound_exclusive` can be set to `True` respectively to exclude the\nlower/upper from the range of matching values.\n\n\n### `Length(length)`\n\nMatches an object if it has the given length. Alternatively also accepts `at_least` and `at_most` keyword arguments.\n\n```python\nmatch(\"abc\", Length(3))\nmatch(\"abc\", Length(at_least=2))\nmatch(\"abc\", Length(at_most=4))\nmatch(\"abc\", Length(at_least=2, at_most=4))\n```\n\n\n### `Contains(item)`\n\nMatches an object if it contains the given item (as per the same logic as the `in` operator).\n\n```python\nmatch(\"hello there, world\", Contains(\"there\"))\nmatch([1, 2, 3], Contains(2) \u0026 Contains(3))\nmatch({'foo': 1, 'bar': 2}, Contains('quux') | Contains('bar'))\n```\n\n\n### `Regex(regex_pattern, bind_groups: bool = True)`\n\nMatches a string if it completely matches the given regex, as per `re.fullmatch`.\nIf the regular expression pattern contains named capturing groups and `bind_groups` is set to `True`,\nthis pattern will bind the captured results in the `MatchResult` (the default).\n\nTo mimic `re.match` or `re.search` the given regular expression `x` can be augmented as `x.*` or `.*x.*`\nrespectively.\n\n\n### `Check(predicate)`\n\nMatches an object if it satisfies the given predicate.\n\n```python\nmatch(2, Check(lambda x: x % 2 == 0))\n```\n\n\n### `InstanceOf(*types)`\n\nMatches an object if it is an instance of any of the given types.\n\n```python\nmatch(1, InstanceOf(int, flaot))\n```\n\n\n### `SubclassOf(*types)`\n\nMatches if the matched type is a subclass of any of the given types.\n\n```python\nmatch(int, SubclassOf(int, float))\n```\n\n\n### `Parameters(...)`\n\nMatches the parameters of a callable.\n\n```python\ndef f(x: int, *xs: float, y: str, **kwargs: bool):\n    pass\n\n\nmatch(f, Parameters(int, VarArgs(float), y=str, KwArgs(bool)))\n```\n\nEach argument to Parameters is expected to be the type of a positional argument.\n\n`Parameters` matches function signatures if their positional arguments match completely, i.e.\n\n```python\ndef f(x: int, y: float):\n    pass\n\n\nprint(bool(match(f, Parameters(int))))  # False\nprint(bool(match(f, Parameters(int, float))))  # True\nprint(bool(match(f, Parameters(int, Remaining(_)))))  # True\n```\n\nKeyword arguments are matched only if they are keyword only arguments. In contrast to positional arguments it matches\nalso impartially (which aligns with the non-strict matching behavior with respect to dictionaries):\n\n```python\ndef f(x: int, *, y: str, z: float):\n    pass\n\n\nprint(bool(match(f, Parameters(int))))  # True\nprint(bool(match(f, Parameters(y=str))))  # False – positional parameters not matched\nprint(bool(match(f, Parameters(int, y=str))))  # True\n```\n\nThis can be changed with `Strict`:\n\n```python\ndef f(x: int, *, y: str, z: float):\n    pass\n\n\nprint(bool(match(f, Strict(Parameters(int)))))  # False\nprint(bool(match(f, Strict(Parameters(int, y=str)))))  # False  (z not mentioned but present)\nprint(bool(match(f, Strict(Parameters(int, y=str, z=float)))))  # True  (has y and z exactly)\n```\n\n\n### `Arguments(*types)`\n\n\u003cspan style=\"color: red\"\u003e**DEPRECATED, use `Parameters` instead (see above)**\u003c/span\u003e\n\n\nMatches a callable if it's type annotations correspond to the given types.\n\n```python\ndef f(x: int, y: float, z):\n    ...\n\n\nmatch(f, Arguments(int, float, None))\n```\n\nArguments has an alternate form which can be used to match keyword arguments:\n\n```python\n\ndef f(x: int, y: float, z: str):\n    ...\n\nmatch(f, Arguments(x=int, y=float))\n```\n\nThe strictness rules are the same as for dictionaries (which is why the above example works).\n\n```python\n# given the f from above\nmatch(f, Strict(Arguments(x=int, y=float)))  # does not match\nmatch(f, Strict(Arguments(x=int, y=float, z=str)))  # matches\n```\n\n\n### `Returns(type)`\n\nMatches a callable if it's type annotations denote the given return type.\n\n```python\ndef g(x: int) -\u003e str:\n    ...\n\n\nmatch(g, Arguments(int) \u0026 Returns(str))\n```\n\n\n### `Transformed(function, pattern)`\n\nTransforms the currently looked at value by applying `function` on it and matches the result against `pattern`. In\nHaskell and other languages this is known as a [_view\npattern_](https://gitlab.haskell.org/ghc/ghc/-/wikis/view-patterns).\n\n```python\ndef sha256(v: str) -\u003e str:\n    import hashlib\n    return hashlib.new('sha256', v.encode('utf8')).hexdigest()\n\nmatch(\"hello\", Transformed(sha256, \"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824\"))\n```\n\nThis is handy for matching data types like `datetime.date` as this pattern won't match if the transformation\nfunction errored out with an exception.\n\n```python\nfrom apm import *\nfrom datetime import date\n\nif result := match(\"2020-08-27\", Transformed(date.fromisoformat, 'date' @ _):\n    print(repr(result['date']))  # result['date'] is a datetime.date\n```\n\n\n### `At(path, pattern)`\n\nChecks whether the nested object to be matched satisfies pattern at the given path. The match fails if the given path\ncan not be resolved.\n\n```python\nrecord = {\n    \"foo\": {\n        \"bar\": {\n            \"quux\": {\n                \"value\": \"deeply nested\"\n            }\n        }\n    }\n}\n\nresult := match(record, At(\"foo.bar.quux\", {\"value\": Capture(..., name=\"value\")}))\nresult['value']  # \"deeply nested\"\n\n# alternate form\nresult := match(record, At(['foo', 'bar', 'quux'], {\"value\": Capture(..., name=\"value\")}))\n```\n\n\n### `Items(**kwargs))`\n\nMostly syntactic sugar to match a dictionary nicely (and anything that provides an `.items()` method).\n\n```python\nfrom apm import *\nfrom datetime import datetime\n\nrequest = {\n    \"api_version\": \"v1\",\n    \"job\": {\n        \"run_at\": \"2020-08-27 14:09:30\",\n        \"command\": \"echo 'booya'\",\n    }\n}\n\nif result := match(request, Items(\n    api_version=\"v1\",\n    job=Object(\n        run_at=Transformed(datetime.fromisoformat, 'time' @ _),\n    ) \u0026 OneOf(\n        Items(command='command' @ InstanceOf(str)),\n        Items(spawn='container' @ InstanceOf(str)),\n    )\n)):\n    print(repr(result['time']))      # datetime(2020, 8, 27, 14, 9, 30)\n    print('container' not in result) # True\n    print(result['command'])         # \"echo 'booya'\"\n```\n\n\n### `Object(type, *args, **kwargs)`\n\nMatches any object of the specific type with the given attrs as in `**kwargs`.\nIt respects the `__match_args__` introduced by PEP-634.\n\n```python\nfrom apm import *\nfrom typing import Literal, Tuple\n\nclass Click:\n    __match_args__ = (\"position\", \"button\")\n\n    def __init__(self, pos: Tuple[int, int], btn: Literal['left', 'right', 'middle']):\n        self.position = pos\n        self.button = btn\n\nassert match(Click((1, 2), 'left'), Object(Click, (1, 2)))\nassert match(Click((1, 2), 'left'), Object(Click, (1, 2), 'left'))\nassert match(Click((1, 2), 'left'), Object(Click, (1, 2), button='left'))\n```\n\n\n## Extensible\n\nNew patterns can be added, just like the ones in `apm.patterns.*`. Simply extend the `apm.Pattern` class:\n\n```python\nclass Min(Pattern):\n    def __init__(self, min):\n        self.min = min\n\n    def match(self, value, *, ctx: MatchContext, strict=False) -\u003e MatchResult:\n        return ctx.match_if(value \u003e= self.min)\n\nmatch(3, Min(1))  # matches\nmatch(3, Min(5))  # does not match\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscravy%2Fawesome-pattern-matching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscravy%2Fawesome-pattern-matching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscravy%2Fawesome-pattern-matching/lists"}