Skip to content

Commit

Permalink
Merge branch 'feature/pycon.jp.20240927'
Browse files Browse the repository at this point in the history
  • Loading branch information
rhoboro committed Sep 23, 2024
2 parents 604ae5e + 5dfa722 commit 7008f39
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pycon.jp.20240927/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[PyCon JP 2024](https://2024.pycon.jp/)の資料です。

* [セッション](https://2024.pycon.jp/ja/talk/LXVHNY)
* [発表資料](https://docs.google.com/presentation/d/18mWjo1fSQYHg7UqvmhCHcVaJ25jewUvwptRXcEI3DkI/edit?usp=sharing)
* [ソースコード等](./src)

3 changes: 3 additions & 0 deletions pycon.jp.20240927/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```bash
(venv) $ pip install 'fastapi[standard]==0.114.2' pydantic==2.9.1 sqlalchemy==2.0.34 mypy==1.11.2
```
17 changes: 17 additions & 0 deletions pycon.jp.20240927/src/fastapi/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fastapi import FastAPI
from schemas import CreateItemRequest, Item

app = FastAPI()


@app.post("/items")
async def create_item(
data: CreateItemRequest,
) -> Item:
item = {
"item_id": 0, # dummy
"name": data.name,
"price": data.price,
"kind": data.kind,
}
return Item.model_validate(item)
26 changes: 26 additions & 0 deletions pycon.jp.20240927/src/fastapi/app2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from uuid import UUID, uuid4

from fastapi import Depends, FastAPI
from schemas import CreateItemRequest, Item


async def gen_request_id() -> UUID:
return uuid4()


app = FastAPI()


@app.post("/items")
async def create_item(
data: CreateItemRequest,
request_id: UUID = Depends(gen_request_id),
) -> Item:
print(f"{request_id=}")
item = {
"item_id": 0, # dummy
"name": data.name,
"price": data.price,
"kind": data.kind,
}
return Item.model_validate(item)
26 changes: 26 additions & 0 deletions pycon.jp.20240927/src/fastapi/app3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi import FastAPI

from di3 import Connection, RequestID
from schemas import CreateItemRequest, Item

app = FastAPI()


@app.post("/items")
async def create_item(
data: CreateItemRequest,
request_id: RequestID,
conn: Connection,
) -> Item:
print(f"{request_id=}")
print(f"{conn.request_id=}")
item = {
"item_id": 0, # dummy
"name": data.name,
"price": data.price,
"kind": data.kind,
}
return Item.model_validate(item)



19 changes: 19 additions & 0 deletions pycon.jp.20240927/src/fastapi/di3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Annotated
from uuid import UUID, uuid4

from fastapi import Depends


def gen_request_id() -> UUID:
return uuid4()


RequestID = Annotated[UUID, Depends(gen_request_id)]


class SomeConnection:
def __init__(self, request_id: RequestID) -> None:
self.request_id = request_id


Connection = Annotated[SomeConnection, Depends(SomeConnection)]
21 changes: 21 additions & 0 deletions pycon.jp.20240927/src/fastapi/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from enum import StrEnum

from pydantic import BaseModel, PositiveInt


class Kind(StrEnum):
DRINK = "DRINK"
FOOD = "FOOD"


class Item(BaseModel):
item_id: int
name: str
price: PositiveInt
kind: Kind = Kind.DRINK


class CreateItemRequest(BaseModel):
name: str
price: PositiveInt
kind: Kind = Kind.DRINK
12 changes: 12 additions & 0 deletions pycon.jp.20240927/src/impl/app4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from di4 import Connection, RequestID
from light_dpends import resolve


@resolve
def handler(request_id: RequestID, connection: Connection) -> None:
print(f"{request_id=}")
print(f"{connection.request_id=}")


if __name__ == "__main__":
handler()
17 changes: 17 additions & 0 deletions pycon.jp.20240927/src/impl/create_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
pass


class Item(Base):
__tablename__ = "items"
item_id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(index=True, unique=True)
price: Mapped[int | None]


engine = create_engine("sqlite+pysqlite://", echo=True)
Base.metadata.create_all(engine)
22 changes: 22 additions & 0 deletions pycon.jp.20240927/src/impl/di4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Annotated
from uuid import UUID, uuid4

from light_dpends import Depends


def gen_request_id() -> UUID:
return uuid4()


RequestID = Annotated[UUID, Depends(gen_request_id)]


class SomeConnection:
def __init__(self, request_id: RequestID) -> None:
self.request_id = request_id

def __repr__(self) -> str:
return f"<{self.__class__.__name__}(request_id={self.request_id!r})>"


Connection = Annotated[SomeConnection, Depends(SomeConnection)]
97 changes: 97 additions & 0 deletions pycon.jp.20240927/src/impl/light_dpends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from collections.abc import Callable
from dataclasses import dataclass, field
from functools import wraps
from inspect import signature
from typing import Annotated, get_args, get_origin


@dataclass
class Depends:
dependencies: Callable
use_cache: bool = True


@dataclass
class Dependant:
call: Callable
name: str | None = None
dependencies: list["Dependant"] = field(default_factory=list)
use_cache: bool = True


@dataclass
class SolvedDependency:
values: dict


def resolve(f: Callable):
dependant = get_dependant(call=f)

@wraps(f)
def inner(*args, **kwargs):
solved = solve_dependencies(dependant)
v = f(*args, **solved.values | kwargs)
return v

return inner


def get_dependant(
call: Callable, name: str | None = None, use_cache: bool = True
) -> Dependant:
"""CallableをDependantにラップし、Depends()を利用している引数をdependant.dependenciesに格納していく"""
dependant = Dependant(call=call, name=name, use_cache=use_cache)
sig = signature(call)
# 横向きに Depends() が使われている引数を探索
for name, param in sig.parameters.items():
depends = analyze_param(name, param.annotation, param.default)
if depends is not None:
# 見つかった場合は縦向きにも Depends() が使われている引数を探索
sub_dependant = get_dependant(
call=depends.dependencies, name=name, use_cache=depends.use_cache
)
dependant.dependencies.append(sub_dependant)
return dependant


def solve_dependencies(
dependant: Dependant,
dependency_cache: dict | None = None,
) -> SolvedDependency:
"""dependant.dependenciesに格納されたDepends()を順番に解決していく"""
# 解決済みの結果を格納する辞書。キーは引数名。
values: dict = {}
dependency_cache = dependency_cache or {}
for sub_dependant in dependant.dependencies:
call = sub_dependant.call
solved_result = solve_dependencies(sub_dependant, dependency_cache)
sig = signature(sub_dependant.call)
# キャッシュの利用
if sub_dependant.use_cache and sub_dependant.call in dependency_cache:
solved = dependency_cache[sub_dependant.call]
else:
solved = call(**solved_result.values)

if sub_dependant.name is not None:
values[sub_dependant.name] = solved

# キャッシュに格納
if sub_dependant.call not in dependency_cache:
dependency_cache[sub_dependant.call] = solved

return SolvedDependency(values=values)


def analyze_param(name, annotation, default_value) -> Depends | None:
"""Dependsが使われているパラメータであればそれを返す"""
if get_origin(annotation) is Annotated:
# Annotated[T, ...] から Depends() を探索
annotated_args = get_args(annotation)
depends = [d for d in annotated_args[1:] if isinstance(d, Depends)]
if depends:
return depends[0]

if isinstance(default_value, Depends):
return default_value

return None
16 changes: 16 additions & 0 deletions pycon.jp.20240927/src/intro/tuples_to_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def tuples_to_dict(
tuples: list[tuple[str, int]],
) -> dict[str, int] | None:
if not tuples:
return None
return {t[0]: t[1] for t in tuples}


def main():
ts = [("ham", 1), ("egg", 2)]
# {'ham': 1, 'egg': 2}
print(tuples_to_dict(ts))


if __name__ == "__main__":
main()
12 changes: 12 additions & 0 deletions pycon.jp.20240927/src/intro/with_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def add(lhs: int, rhs: int) -> int:
return lhs + rhs

def main() -> None:
x, y = 1, "1"
print(add(x, x))
print(add(y, y))
print(add(x, y))

if __name__ == "__main__":
main()

11 changes: 11 additions & 0 deletions pycon.jp.20240927/src/intro/without_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def add(lhs, rhs):
return lhs + rhs

def main():
x, y = 1, "1"
print(add(x, x))
print(add(y, y))
print(add(x, y))

if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions pycon.jp.20240927/src/pydantic/basis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from schemas import Item

data = {"item_id": 0, "name": "Coke", "price": 120, "kind": "DRINK"}
item = Item.model_validate(data)
print(item)

json_str = '{"item_id": 1, "name": "Green Tea", "price": 140}'
item = Item.model_validate_json(json_str)
print(item)
5 changes: 5 additions & 0 deletions pycon.jp.20240927/src/pydantic/json_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import json
from schemas import Item

json_schema = Item.model_json_schema()
print(json.dumps(json_schema, indent=2))
14 changes: 14 additions & 0 deletions pycon.jp.20240927/src/pydantic/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from enum import StrEnum
from pydantic import BaseModel, PositiveInt


class Kind(StrEnum):
DRINK = "DRINK"
FOOD = "FOOD"


class Item(BaseModel):
item_id: int
name: str
price: PositiveInt
kind: Kind = Kind.DRINK
6 changes: 6 additions & 0 deletions pycon.jp.20240927/src/pydantic/serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from schemas import Item

data = {"item_id": 0, "name": "Coke", "price": 120}
item = Item.model_validate(data)
print(item.model_dump())
print(item.model_dump_json())
8 changes: 8 additions & 0 deletions pycon.jp.20240927/src/pydantic/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from schemas import Item
from pydantic import ValidationError

data = {"item_id": 0, "name": "Coke", "price": -100, "kind": "SNACK"}
try:
Item.model_validate(data)
except ValidationError as e:
print(e)
5 changes: 5 additions & 0 deletions pycon.jp.20240927/src/sqlalchemy/create_table_sqlite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from item import Base
from sqlalchemy import create_engine

engine = create_engine("sqlite+pysqlite://", echo=True)
Base.metadata.create_all(engine)
12 changes: 12 additions & 0 deletions pycon.jp.20240927/src/sqlalchemy/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
pass


class Item(Base):
__tablename__ = "items"
item_id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(index=True, unique=True)
price: Mapped[int | None]

0 comments on commit 7008f39

Please sign in to comment.