Slide 1

Slide 1 text

Having fun with Pydantic and pattern matching Sebastian Buczyński

Slide 2

Slide 2 text

• Software Architect @ Sauce Labs • Trainer / Consultant @ Bottega IT Minds • I like memes • Blogger @ breadcrumbscollector.tech • Educate Pythonistas about software engineering whoami

Slide 3

Slide 3 text

match latte: case Coffee() : print("It's caffee latte or latte machiato!") case Tea() : print("It's Matcha Latte") case _: print("Huh, no idea what it is”)

Slide 4

Slide 4 text

match latte: case Coffee() : print("It's caffee latte or latte machiato!") case Tea() : print("It's Matcha Latte") case _: print("Huh, no idea what is it")

Slide 5

Slide 5 text

match latte: case Coffee() : print("It's caffee latte or latte machiato!") case Tea() : print("It's Matcha Latte") case _: print("Huh, no idea what is it")

Slide 6

Slide 6 text

price = 200 match price: ...

Slide 7

Slide 7 text

price = 200 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea")

Slide 8

Slide 8 text

price = 200 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea")

Slide 9

Slide 9 text

price = 200 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea")

Slide 10

Slide 10 text

price = 200 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea")

Slide 11

Slide 11 text

price = 200 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea") What we’ll see? #1

Slide 12

Slide 12 text

price = 2137 match price: case 100 : print("Cheap!") case 200 : print("Expensive!") case _: print("No idea") What we’ll see? #2

Slide 13

Slide 13 text

data = (1, 2, 3) match data: case {} : print("It's a dict!") case tuple() : print("Huh, a tuple!") case _: print("No idea") What we’ll see? #3

Slide 14

Slide 14 text

data = {"name": "Sebastian"} match data: case {} : print("It's a dict!") case tuple() : print("Huh, a tuple!") case _: print("No idea") What we’ll see? #4

Slide 15

Slide 15 text

What we’ll see? #5 data = {"name": "Sebastian"} match data: case {"name": _} : print("That's a dict with 'name'") case {} : print("It's a dict!") case _: print("No idea")

Slide 16

Slide 16 text

What we’ll see? #6 data = {"name": "Sebastian"} match data: case {} : print("It's a dict!") case {"name": _} : print("That's a dict with 'name'") case _: print("No idea")

Slide 17

Slide 17 text

What we’ll see? #6 data = {"name": "Sebastian"} match data: case {} : print("It's a dict!") case {"name": _} : print("That's a dict with 'name'") case _: print("No idea") Ordering of ‚cases’ is signi f icant

Slide 18

Slide 18 text

@brock1137

Slide 19

Slide 19 text

…just like with exceptions try: ... except Exception: ... except ValueError: ... # dead code!

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Consuming stream of events

Slide 22

Slide 22 text

Consuming stream of events: data {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"}

Slide 23

Slide 23 text

Consuming stream of events: data {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"} {"type": "AccountUpdated", "id": "bb915f85", "old_status": "trial", "new_status": "paid"} {"type": "AccountDeleted", "id": "57ae55c5"}

Slide 24

Slide 24 text

Consuming stream of events: data {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"} {"type": "AccountUpdated", "id": "bb915f85", "old_status": "trial", "new_status": "paid"} {"type": "AccountDeleted", "id": "57ae55c5"} {"type": "AccountUpdated", "id": "fede79b2", "old_status": "paid", "new_status": "trial"} {"type": "AccountCreated", "id": "23826148", "name": "John"}

Slide 25

Slide 25 text

Consuming stream of events: data {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"} {"type": "AccountUpdated", "id": "bb915f85", "old_status": "trial", "new_status": "paid"} {"type": "AccountDeleted", "id": "57ae55c5"} {"type": "AccountUpdated", "id": "fede79b2", "old_status": "paid", "new_status": "trial"} {"type": "AccountCreated", "id": "23826148", "name": "John"} {"username": "Hecker"}

Slide 26

Slide 26 text

def handle(payload: dict) -> None: ... # let's code this!

Slide 27

Slide 27 text

def handle(payload: dict) -> None: # code like it's 2020 😎 if payload.get("type") == "AccountCreated": ... elif payload.get("type") == "AccountDeleted": ... elif payload.get("type") == "AccountUpdated": ... else: print("Omg, what is this?!")

Slide 28

Slide 28 text

def handle(payload: dict) -> None: # code like it's 2020 😎 # ... and pretend we're in control 🙈🙉🙊 if payload.get("type") == "AccountCreated": ... elif payload.get("type") == "AccountDeleted": ... elif payload.get("type") == "AccountUpdated": if payload["new_status"] == "paid": ... elif payload["new_status"] == "trial": ... else: print("Omg, what is this?!")

Slide 29

Slide 29 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case _: if payload.get("type") == "AccountCreated": ... elif payload.get("type") == "AccountDeleted": ... elif payload.get("type") == "AccountUpdated": if payload["new_status"] == "paid": ... elif payload["new_status"] == "trial": ... else: print("Omg, what is this?!")

Slide 30

Slide 30 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case {"type": "AccountCreated"} : ... case _: if payload.get("type") == "AccountDeleted": ... elif payload.get("type") == "AccountUpdated": if payload["new_status"] == "paid": ... elif payload["new_status"] == "trial": ... else: print("Omg, what is this?!")

Slide 31

Slide 31 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case {"type": "AccountCreated"} : ... case {"type": "AccountDeleted"} : ... case _: if payload.get("type") == "AccountUpdated": if payload["new_status"] == "paid": ... elif payload["new_status"] == "trial": ... else: print("Omg, what is this?!")

Slide 32

Slide 32 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case {"type": "AccountCreated"} : ... case {"type": "AccountDeleted"} : ... case {"type": "AccountUpdated"} : if payload["new_status"] == "paid": ... elif payload["new_status"] == "trial": ... case _: print("Omg, what is this?!")

Slide 33

Slide 33 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case {"type": "AccountCreated"} : ... case {"type": "AccountDeleted"} : ... case {"type": "AccountUpdated", "new_status": "paid"} : ... case {"type": "AccountUpdated", "new_status": "trial"} : ... case _: print("Omg, what is this?!")

Slide 34

Slide 34 text

def handle(payload: dict) -> None: # HAMMER TIME! 🔨 match payload: case {"type": "AccountCreated"} : ... case {"type": "AccountDeleted"} : ... case {"type": "AccountUpdated", "new_status": "paid"} : ... case {"type": "AccountUpdated", "new_status": "trial"} : ... case _: print("Omg, what is this?!")

Slide 35

Slide 35 text

Pydantic model {"type": "AccountCreated", "id": "64160ccb", "name": "Mateusz"}

Slide 36

Slide 36 text

Pydantic model data = {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"} class AccountCreated(BaseModel) : type: str id: str name: str AccountCreated( ** data) # AccountCreated(type='AccountCreated', id='64160ccb', name='Matthew')

Slide 37

Slide 37 text

Pydantic model with a constant data = {"type": "AccountCreated", "id": "64160ccb", "name": "Matthew"} class AccountCreated(BaseModel) : type: Literal["AccountCreated"] id: str name: str AccountCreated(type="BAZINGA", id=„64160ccb", name="Matthew") # raises exception

Slide 38

Slide 38 text

Pydantic models class AccountCreated(BaseModel) : ... class AccountDeleted(BaseModel) : ... class AccountUpdated(BaseModel) : type: Literal["AccountUpdated"] id: str old_status: Literal["trial", "paid"] new_status: Literal["trial", "paid"]

Slide 39

Slide 39 text

def handle(payload: dict) -> None: match payload: case {"type": "AccountCreated"} : ... case {"type": "AccountDeleted"} : ... case {"type": "AccountUpdated", "new_status": "paid"} : ... case {"type": "AccountUpdated", "new_status": "trial"} : ... case _: print("Omg, what is this?!")

Slide 40

Slide 40 text

def handle(payload: dict) -> None: match payload: case {"type": "AccountCreated"} : event = AccountCreated( ** payload) case {"type": "AccountDeleted"} : event = AccountDeleted( ** payload) case {"type": "AccountUpdated", "new_status": "paid"} : event = AccountUpdated( ** payload) case {"type": "AccountUpdated", "new_status": "trial"} : event = AccountUpdated( ** payload) case _: print("Omg, what is this?!”)

Slide 41

Slide 41 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated ) match payload: case {"type": "AccountCreated"} : ...

Slide 42

Slide 42 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated ) adapter = TypeAdapter(supported_model) match payload: case {"type": "AccountCreated"} : ...

Slide 43

Slide 43 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) # event will be an instance of a supported model! match payload: case {"type": "AccountCreated"} : ...

Slide 44

Slide 44 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) match event: case AccountCreated() : ... case AccountUpdated(new_status="paid") : ... case AccountUpdated(new_status="trial") : ...

Slide 45

Slide 45 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) match event: case AccountCreated() : ... case AccountUpdated(new_status="paid") : ... case AccountUpdated(new_status="trial") : ... case _: print("Omg, what is this?!")

Slide 46

Slide 46 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) match event: case AccountCreated() : ... case AccountUpdated(new_status="paid") : ... case AccountUpdated(new_status="trial") : ... case _: print("Omg, what is this?!")

Slide 47

Slide 47 text

def handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdated | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) match event: case AccountCreated() : ... case AccountUpdated(new_status="paid") : ... case AccountUpdated(new_status="trial") : ... case _: print("Omg, what is this?!")

Slide 48

Slide 48 text

class AccountUpdated(BaseModel) : type: Literal["AccountUpdated"] id: str old_status: Literal["trial", "paid"] new_status: Literal["trial", "paid"]

Slide 49

Slide 49 text

class AccountUpdatedToPaid(BaseModel) : type: Literal["AccountUpdated"] id: str old_status: Literal["trial"] new_status: Literal["paid"] class AccountUpdatedToTrial(BaseModel) : type: Literal["AccountUpdated"] id: str old_status: Literal["paid"] new_status: Literal["trial"]

Slide 50

Slide 50 text

def handle(payload: dict) -> None: supported_model = ( ... | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) match event: case AccountCreated() : ... case AccountUpdatedToPaid() : ... case AccountUpdatedToTrial() : ... case _: print("Omg, what is this?!")

Slide 51

Slide 51 text

import functools @functools.singledispatch def event_handler(event: Any) -> None: print("Omg, what is this?!")

Slide 52

Slide 52 text

import functools @functools.singledispatch def event_handler(event: Any) -> None: print("Omg, what is this?!") @event_handler.register def handle_account_created(event: AccountCreated) -> None: ... @event_handler.register def handle_account_updated_to_paid(event: AccountUpdatedToPaid) -> None: ...

Slide 53

Slide 53 text

ef handle(payload: dict) -> None: supported_model = ( AccountCreated | AccountDeleted | AccountUpdatedToTrial | AccountUpdatedToPaid | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload) event_handler(event)

Slide 54

Slide 54 text

match latte: case Coffee() : print("It's caffee latte or latte machiato!") case Tea() : print("It's Matcha Latte") case _: print("Huh, no idea what is it") match…case on types

Slide 55

Slide 55 text

match event: case AccountCreated() : ... case AccountUpdated(new_status="paid") : ... case AccountUpdated(new_status="trial") : ... case _: print("Omg, what is this?!") match…case on attributes' values

Slide 56

Slide 56 text

pattern matching using Pydantic class AccountCreated(BaseModel) : type: Literal["AccountCreated"] id: str name: str class AccountDeleted(BaseModel) : type: Literal["AccountDeleted"] id: str supported_model = ( AccountCreated | AccountDeleted | AccountUpdated ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload)

Slide 57

Slide 57 text

pattern matching using Pydantic class AccountCreated(BaseModel) : type: Literal["AccountCreated"] id: str name: str class AccountDeleted(BaseModel) : type: Literal["AccountDeleted"] id: str supported_model = ( AccountCreated | AccountDeleted | AccountUpdated | Any ) adapter = TypeAdapter(supported_model) event = adapter.validate_python(payload)

Slide 58

Slide 58 text

Dispatch based on type using @singledispatch import functools @functools.singledispatch def event_handler(event: Any) -> None: print("Omg, what is this?!") @event_handler.register def handle_account_created(event: AccountCreated) -> None: ...

Slide 59

Slide 59 text

Start writing classes 'cause types are awesome!

Slide 60

Slide 60 text

Sebastian Buczyński breadcrumbscollector.tech