Repository: pydantic/pydantic

Issue:
Support python3.10 structural pattern matching (PEP 634-636)
### Checks

* [X] I added a descriptive title to this issue
* [X] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Feature

Python 3.10 introduces the new `match` syntax. https://peps.python.org/pep-0636/

Example:
```python
num = 5

match num:
    case 5:
        print('is five')
    case default:
        print('is not five')
```

They support matching on custom classes too, with the `__match_args__` attribute.

```python
class MyClass:
    __match_args__ = ('num1', 'num2')

    def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2


myinstance = MyClass(5, 10)


match myinstance:
    case MyClass(5, num2):
        print(f'is 5 and {num2}')
    case MyClass(_, num2):
        print(f'is not 5 and is {num2}')
```

I've found myself adding this attribute to pydantic classes by hand, eventhough it seems pretty intuitive to me that the default behavior should match fields in the order that they were specified. So my code looks kind of like this:

```python
class UpdateThing(BaseModel):
    __match_args__ = ("thing", "value")
    thing: Thing
    value: str


class AddThing(BaseModel):
    __match_args__ = ("index", "thing")
    index: int
    thing: Thing
```

Might as well add it natively?

I'm opening this issue to couple it with a PR :)


Discussion:

Resolving patch:
diff --git a/docs/examples/models_structural_pattern_matching.py b/docs/examples/models_structural_pattern_matching.py
new file mode 100644
--- /dev/null
+++ b/docs/examples/models_structural_pattern_matching.py
@@ -0,0 +1,17 @@
+from pydantic import BaseModel
+
+
+class Pet(BaseModel):
+    name: str
+    species: str
+
+
+a = Pet(name='Bones', species='dog')
+
+match a:
+    # match `species` to 'dog', declare and initialize `dog_name`
+    case Pet(species='dog', name=dog_name):
+        print(f'{dog_name} is a dog')
+    # default case
+    case _:
+        print('No dog matched')


Test patch:
diff --git a/tests/test_structural_pattern_matching.py b/tests/test_structural_pattern_matching.py
new file mode 100644
--- /dev/null
+++ b/tests/test_structural_pattern_matching.py
@@ -0,0 +1,29 @@
+import sys
+
+import pytest
+
+
+@pytest.mark.skipif(sys.version_info < (3, 10), reason='requires python 3.10 or higher')
+def test_match_kwargs(create_module):
+    module = create_module(
+        # language=Python
+        """
+from pydantic import BaseModel
+
+class Model(BaseModel):
+    a: str
+    b: str
+
+def main(model):
+    match model:
+        case Model(a='a', b=b):
+            return b
+        case Model(a='a2'):
+            return 'b2'
+        case _:
+            return None
+"""
+    )
+    assert module.main(module.Model(a='a', b='b')) == 'b'
+    assert module.main(module.Model(a='a2', b='b')) == 'b2'
+    assert module.main(module.Model(a='x', b='b')) is None