diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/.gitignore b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/.gitignore new file mode 100644 index 00000000..9cd6643f --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/.gitignore @@ -0,0 +1,4 @@ +**.pyc +*.db +__pycache__/ +.env \ No newline at end of file diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/README.md b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/README.md new file mode 100644 index 00000000..d744614e --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/README.md @@ -0,0 +1,69 @@ +# FastAPI CRUD App + +**Contributor:** AksharGoyal + +## Overview +Simple, starter FastAPI application demonstrating CRUD operations backed by SQLAlchemy ORM. Includes a minimal API for creating, listing, updating and deleting "items". + +## Tech stack +- Python 3.12+ +- FastAPI +- SQLAlchemy +- Uvicorn (ASGI server) +- "uv" as the local environment manager (see pyproject.toml) + +## Prerequisites +- Linux +- Python 3.12+ +- The `uv` tool is used in these instructions as the environment manager + +## Quickstart +1. Create and activate the virtual environment +```sh +curl -LsSf https://astral.sh/uv/install.sh | sh # Install uv +uv venv --python=3.12 # creates .venv by default +source .venv/bin/activate +``` + +2. Install dependencies +```sh +uv pip install -r requirements.txt +``` + +3. Run the server +```sh +uv run uvicorn app.main:app --reload +``` + +## API endpoints +- GET /items/ — List items +- POST /items/ — Create item +- GET /items/{id} — Retrieve single item +- PUT /items/{id} — Update item +- DELETE /items/{id} — Delete item + +## Examples +List items: +```sh +curl -X GET http://127.0.0.1:8000/items/ +# returns: [] +``` + +Create an item: +```sh +curl -X POST http://127.0.0.1:8000/items/ \ + -H "Content-Type: application/json" \ + -d '{"title":"A Nice Hoodie","description":"A nice hoodie to wear with style.","price":39.99}' +``` + +Update item with id 1: +```sh +curl -X PUT http://127.0.0.1:8000/items/1 \ + -H "Content-Type: application/json" \ + -d '{"title":"A Cool Hoodie","description":"A nice hoodie to wear with style.","price":49.99}' +``` + +Delete item with id 1: +```sh +curl -X DELETE http://127.0.0.1:8000/items/1 +``` diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/__init__.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/controllers.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/controllers.py new file mode 100644 index 00000000..60790e8c --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/controllers.py @@ -0,0 +1,73 @@ +from typing import List, Optional + +from sqlalchemy.orm import Session + +from . import models, schemas + + +def get_items(db: Session, skip: int = 0, limit: int = 100) -> List[models.Item]: + """ + Return a list of items from the database. + + Args: + db: SQLAlchemy Session + skip: number of records to skip (offset) + limit: maximum number of records to return + + Returns: + List of Item instances. + """ + return db.query(models.Item).offset(skip).limit(limit).all() + + +def get_item(db: Session, item_id: int) -> Optional[models.Item]: + """ + Return a single item by id or None if not found. + """ + return db.query(models.Item).filter(models.Item.id == item_id).first() + + +def create_item(db: Session, item: schemas.ItemCreate) -> models.Item: + """ + Create and persist a new item. + + Commits the transaction and refreshes the instance to populate generated fields (e.g. id). + """ + db_item = models.Item( + title=item.title, description=item.description, price=item.price + ) + db.add(db_item) + db.commit() + db.refresh(db_item) + return db_item + + +def update_item(db: Session, item_id: int, item: schemas.ItemCreate) -> Optional[models.Item]: + """ + Update fields of an existing item. + + Returns the updated item or None if the item does not exist. + """ + db_item = db.query(models.Item).filter(models.Item.id == item_id).first() + if db_item is None: + return None + db_item.title = item.title + db_item.description = item.description + db_item.price = item.price + db.commit() + db.refresh(db_item) + return db_item + + +def delete_item(db: Session, item_id: int) -> Optional[models.Item]: + """ + Delete an item by id. + + Returns the deleted item (detached) or None if not found. + """ + db_item = db.query(models.Item).filter(models.Item.id == item_id).first() + if db_item is None: + return None + db.delete(db_item) + db.commit() + return db_item \ No newline at end of file diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/database.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/database.py new file mode 100644 index 00000000..dea30dc9 --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/database.py @@ -0,0 +1,20 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, Session + +""" +Database configuration and session factory. + +- Uses a local SQLite database file `items.db` for this example. +- SessionLocal is a SQLAlchemy session factory to be used via dependency injection. +""" + +SQLALCHEMY_DATABASE_URL = "sqlite:///./items.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) +# SessionLocal is a factory for sessions. Use `SessionLocal()` to get a Session instance. +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/main.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/main.py new file mode 100644 index 00000000..50945e52 --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/main.py @@ -0,0 +1,91 @@ +from typing import Generator + +from fastapi import Depends, FastAPI, HTTPException +from sqlalchemy.orm import Session + +from . import controllers, models, schemas +from .database import SessionLocal, engine + +models.Base.metadata.create_all(bind=engine) + +app = FastAPI( + title="FastAPI CRUD App", + description="Minimal example API demonstrating CRUD operations backed by SQLAlchemy." +) + + +def get_db() -> Generator[Session, None, None]: + """ + FastAPI dependency that yields a SQLAlchemy Session. + + Ensures the session is closed after the request finishes. + Usage: db: Session = Depends(get_db) + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + + +@app.get("/items/", response_model=list[schemas.Item]) +def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """ + List items with optional pagination. + + Query params: + - skip: offset + - limit: maximum number of items to return + """ + items = controllers.get_items(db, skip=skip, limit=limit) + return items + + +@app.get("/items/{item_id}", response_model=schemas.Item) +def read_item(item_id: int, db: Session = Depends(get_db)): + """ + Retrieve a single item by id. + + Returns 404 if the item does not exist. + """ + db_item = controllers.get_item(db, item_id=item_id) + if db_item is None: + raise HTTPException(status_code=404, detail="Item not found") + return db_item + + +@app.post("/items/", response_model=schemas.Item) +def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)): + """ + Create a new item. + + Body: ItemCreate + """ + return controllers.create_item(db=db, item=item) + + +@app.put("/items/{item_id}", response_model=schemas.Item) +def update_item(item_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)): + """ + Update an existing item by id. + + Returns 404 if the item does not exist. + """ + db_item = controllers.update_item(db, item_id=item_id, item=item) + if db_item is None: + raise HTTPException(status_code=404, detail="Item not found") + return db_item + + +@app.delete("/items/{item_id}", response_model=schemas.Item) +def delete_item(item_id: int, db: Session = Depends(get_db)): + """ + Delete an item by id. + + Returns the deleted item on success; 404 if not found. + """ + db_item = controllers.delete_item(db, item_id=item_id) + if db_item is None: + raise HTTPException(status_code=404, detail="Item not found") + return db_item diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/models.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/models.py new file mode 100644 index 00000000..17277736 --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/models.py @@ -0,0 +1,21 @@ +from sqlalchemy import Column, Integer, String, Float, Text + +from .database import Base + + +class Item(Base): + """ + SQLAlchemy ORM model for an item. + + Columns: + - id: primary key + - title: short title of the item + - description: longer description (Text) + - price: numeric price stored as a float + """ + __tablename__ = "items" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String(200), nullable=False, index=True) + description = Column(Text, nullable=True) + price = Column(Float, nullable=False) diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/schemas.py b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/schemas.py new file mode 100644 index 00000000..5b1d54a9 --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/app/schemas.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel, Field + + +class ItemBase(BaseModel): + """ + Shared properties for an item used for input validation. + """ + title: str + description: str | None = None + price: float + + +class ItemCreate(ItemBase): + """ + Properties required to create/update an item. + Inherits from ItemBase; separated for clarity and future extension. + """ + pass + + +class Item(ItemBase): + """ + Item schema returned by the API. + Note: orm_mode is enabled to allow returning SQLAlchemy models directly. + """ + id: int diff --git a/Domains/Backend/MiniProjects/FastAPI-CRUD-app/requirements.txt b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/requirements.txt new file mode 100644 index 00000000..a70e8ac7 --- /dev/null +++ b/Domains/Backend/MiniProjects/FastAPI-CRUD-app/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +sqlalchemy +pydantic \ No newline at end of file