Example¶
Prereq Example¶
from __future__ import annotations
import asyncio
import json
import random
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, NewType, Protocol, TextIO, TypedDict, override
from prereq import Resolver, provides
if TYPE_CHECKING:
from collections.abc import Generator
class Level(Enum):
SESSION = 1
REQUEST = 2
UserID = NewType("UserID", str)
class Config(TypedDict):
db_path: str
point_delta: int
@provides
async def create_config() -> Config:
return {
"db_path": "db.json",
"point_delta": 10,
}
class Database(Protocol):
def get(self, user_id: UserID, key: str) -> Any: ...
def set(self, user_id: UserID, key: str, value: Any) -> None: ... # pyright: ignore[reportAny]
@dataclass
class JSONDatabase(Database):
file: TextIO
@classmethod
def create(cls, config: Config) -> Generator[JSONDatabase]:
db_file = Path(config["db_path"])
if not db_file.exists():
_ = db_file.write_text(json.dumps({
"BOB": {
"name": "Bobby Tables",
"points": 900,
},
"ALICE": {
"name": "Alice Keys",
"points": 500,
},
}))
with open(db_file, "r+") as file:
yield cls(file)
@override
def get(self, user_id: UserID, key: str) -> Any:
_ = self.file.seek(0)
return json.load(self.file)[user_id][key] # pyright: ignore[reportAny]
@override
def set(self, user_id: UserID, key: str, value: Any) -> None: # pyright: ignore[reportAny]
_ = self.file.seek(0)
data: dict[UserID, dict[str, Any]] = json.loads( # pyright: ignore[reportAny]
self.file.read(),
)
if user_id not in data:
data[user_id] = {}
data[user_id][key] = value
_ = self.file.seek(0)
_ = json.dump(data, self.file)
db_create = provides(JSONDatabase.create, level=Level.REQUEST)
@dataclass
class User:
user_id: UserID
name: str
points: int
@provides(level=Level.REQUEST)
def create_user(user_id: UserID, db: Database) -> User:
return User(
user_id=user_id,
name=db.get(user_id, "name"),
points=db.get(user_id, "points"),
)
session = Resolver()
session.add_providers(
create_config,
db_create,
create_user,
)
def add_points(user: User, db: Database, config: Config) -> None:
db.set(user.user_id, "points", user.points + config["point_delta"])
def remove_points(user: User, db: Database, config: Config) -> None:
db.set(user.user_id, "points", user.points - config["point_delta"])
USER_IDS: list[str] = ["BOB", "ALICE"]
async def main() -> None:
selected_user = random.choice(USER_IDS)
print(f"Changing points for {selected_user}")
change = random.choice((add_points, remove_points))
print(f"Performing {change.__name__} operation.")
async with (
session({UserID: selected_user}) as request,
request.resolve(change) as kwargs,
):
change(**kwargs)
print("Updated random user!")
if __name__ == "__main__":
asyncio.run(main())