From 2b6e4e8b381a93360facdbea2b8e28b6739dfaa5 Mon Sep 17 00:00:00 2001 From: Colin Bassett Date: Mon, 24 Feb 2025 12:03:45 -0500 Subject: [PATCH] Implement a basic API --- .gitignore | 3 +++ functions.py | 10 ++++++++++ main.py | 35 +++++++++++++++++++++++++++++++++ requirements.txt | 36 ++++++++++++++++++++++++++++++++++ sql.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+) create mode 100644 .gitignore create mode 100644 functions.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 sql.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fc9222 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv/ +.ropeproject/ +.idea/ diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..bcf2d85 --- /dev/null +++ b/functions.py @@ -0,0 +1,10 @@ +from sql import * +from sqlmodel import select + +def get_hero_by_email(email: str, session) -> User | None: + statement = select(Hero).where(Hero.email == email) + return session.exec(statement).first() + +def get_hero_by_id(id: str, session: SessionDep) -> User | None: + statement = select(Hero).where(Hero.id == id) + return session.exec(statement).first() diff --git a/main.py b/main.py new file mode 100644 index 0000000..beecade --- /dev/null +++ b/main.py @@ -0,0 +1,35 @@ +# Imports +from contextlib import asynccontextmanager +from fastapi import FastAPI, HTTPException +from sql import * +from functions import * + +# Create DB on startup +# noinspection PyUnusedLocal +@asynccontextmanager +async def lifespan(app: FastAPI): + create_db_and_tables() + yield # Code before the yield will run on startup, code after yield won't run until the program is over + +# Define the app +app = FastAPI() + +# Routes +@app.post("/heroes/create", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + +@app.get("/heros/{type}", response_model=HeroPublic) +def get_hero(type: str, session: SessionDep): + user = get_hero_by_id(type, session) + if user is None: + user = get_hero_by_email(type, session) + + if not user: + raise HTTPException(status_code=404, detail="User not found") + + return user diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f222136 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,36 @@ +annotated-types==0.7.0 +anyio==4.8.0 +certifi==2025.1.31 +click==8.1.8 +dnspython==2.7.0 +email_validator==2.2.0 +fastapi==0.115.8 +fastapi-cli==0.0.7 +greenlet==3.1.1 +h11==0.14.0 +httpcore==1.0.7 +httptools==0.6.4 +httpx==0.28.1 +idna==3.10 +Jinja2==3.1.5 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +mdurl==0.1.2 +pydantic==2.10.6 +pydantic_core==2.27.2 +Pygments==2.19.1 +python-dotenv==1.0.1 +python-multipart==0.0.20 +PyYAML==6.0.2 +rich==13.9.4 +rich-toolkit==0.13.2 +shellingham==1.5.4 +sniffio==1.3.1 +SQLAlchemy==2.0.38 +sqlmodel==0.0.22 +starlette==0.45.3 +typer==0.15.1 +typing_extensions==4.12.2 +uvicorn==0.34.0 +watchfiles==1.0.4 +websockets==15.0 diff --git a/sql.py b/sql.py new file mode 100644 index 0000000..b8429e8 --- /dev/null +++ b/sql.py @@ -0,0 +1,50 @@ +# Imports +from typing import Annotated +from fastapi import Depends, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + +# The base hero class +class Hero(SQLModel): + name: str = Field(index=True) + gold: int | None = 0 + health: int | None = 100 + +# The player +class Player(Hero, table=True): + id: int = Field(default=None, primary_key=True, index=True) + email: str = Field(index=True) + weapons: dict[str, int] + pickaxes: dict[str, int] + upgrades: dict[str, int] + +# This class will be returned to the public +class HeroPublic(Hero): + id: int + +# The class used to create a hero +class HeroCreate(Hero): + secret_name: str + +# The class used to update a hero +class HeroUpdate(Hero): + gold: int | None = 0 + health: int | None = 0 + +# SQLModel initialization +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + +# Create DB +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + +# Get the session +def get_session(): + with Session(engine) as session: + yield session + +# Return the sessiondep +SessionDep = Annotated[Session, Depends(get_session)]