-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/pycon.jp.20240927'
- Loading branch information
Showing
21 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |