├── .gitignore ├── README.md ├── create_db.py ├── crud.py ├── db.py ├── main.py ├── models.py ├── requirements.txt └── schemas.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.db 3 | *.sqlite3 4 | .env 5 | env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Noted-FastAPI 2 | This is a REST API for a note-taking service. This was built to demonstrate how to use FastAPI with Async SQLAlchemy and PostgreSQL 3 | [Video here](https://youtu.be/nC9ob8xM3AM) 4 | 5 | ## How to run the code 6 | - Create a ```DATABASE_URL``` variable in a ```.env``` file. This must be a Postgresql database URI. 7 | - Install all project requirements from the ```requirements.txt``` file using ```pip install -r requirements.txt``` 8 | - Create the database by running ``` python create_db ``` 9 | - Finally run your server with ```uvicorn main:app``` 10 | 11 | Please star this repo 12 | -------------------------------------------------------------------------------- /create_db.py: -------------------------------------------------------------------------------- 1 | from db import Base, engine 2 | import asyncio 3 | 4 | 5 | async def create_db(): 6 | """ 7 | coroutine responsible for creating database tables 8 | """ 9 | async with engine.begin() as conn: 10 | from models import Note 11 | 12 | await conn.run_sync(Base.metadata.drop_all) 13 | await conn.run_sync(Base.metadata.create_all) 14 | 15 | await engine.dispose() 16 | 17 | 18 | asyncio.run(create_db()) 19 | -------------------------------------------------------------------------------- /crud.py: -------------------------------------------------------------------------------- 1 | from models import Note 2 | from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession 3 | from sqlalchemy import select 4 | from models import Note 5 | 6 | 7 | class CRUD: 8 | async def get_all(self, async_session: async_sessionmaker[AsyncSession]): 9 | """ 10 | Get all note objects from db 11 | """ 12 | async with async_session() as session: 13 | statement = select(Note).order_by(Note.id) 14 | 15 | result = await session.execute(statement) 16 | 17 | return result.scalars() 18 | 19 | async def add(self, async_session: async_sessionmaker[AsyncSession], note: Note): 20 | """ 21 | Create note object 22 | """ 23 | async with async_session() as session: 24 | session.add(note) 25 | await session.commit() 26 | 27 | return note 28 | 29 | async def get_by_id( 30 | self, async_session: async_sessionmaker[AsyncSession], note_id: str 31 | ): 32 | """ 33 | Get note by id 34 | """ 35 | async with async_session() as session: 36 | statement = select(Note).filter(Note.id == note_id) 37 | 38 | result = await session.execute(statement) 39 | 40 | return result.scalars().one() 41 | 42 | async def update( 43 | self, async_session: async_sessionmaker[AsyncSession], note_id, data 44 | ): 45 | """ 46 | Update note by id 47 | """ 48 | async with async_session() as session: 49 | statement = select(Note).filter(Note.id == note_id) 50 | 51 | result = await session.execute(statement) 52 | 53 | note = result.scalars().one() 54 | 55 | note.title = data["title"] 56 | note.content = data["content"] 57 | 58 | await session.commit() 59 | 60 | return note 61 | 62 | async def delete(self, async_session: async_sessionmaker[AsyncSession], note: Note): 63 | """delete note by id 64 | """ 65 | async with async_session() as session: 66 | await session.delete(note) 67 | await session.commit() 68 | 69 | return {} 70 | -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.asyncio import create_async_engine 2 | from sqlalchemy.orm import DeclarativeBase 3 | from dotenv import load_dotenv 4 | import os 5 | 6 | 7 | load_dotenv() 8 | 9 | 10 | #engine object to connect to db 11 | engine = create_async_engine( 12 | url= os.getenv('DATABASE_URL'), 13 | echo = True 14 | ) 15 | 16 | 17 | #base class for creating database models 18 | class Base(DeclarativeBase): 19 | pass 20 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from sqlalchemy.ext.asyncio import async_sessionmaker 3 | from crud import CRUD 4 | from db import engine 5 | from schemas import NoteModel, NoteCreateModel 6 | from http import HTTPStatus 7 | from typing import List 8 | from models import Note 9 | import uuid 10 | 11 | app = FastAPI( 12 | title="Noted API", description="This is a simple note taking service", docs_url="/" 13 | ) 14 | 15 | #create an async session object for CRUD 16 | session = async_sessionmaker(bind=engine, expire_on_commit=False) 17 | 18 | db = CRUD() 19 | 20 | 21 | @app.get("/notes", response_model=List[NoteModel]) 22 | async def get_all_notes(): 23 | """API endpoint for listing all note resources 24 | """ 25 | notes = await db.get_all(session) 26 | 27 | return notes 28 | 29 | 30 | @app.post("/notes", status_code=HTTPStatus.CREATED) 31 | async def create_note(note_data: NoteCreateModel) -> dict: 32 | """API endpoint for creating a note resource 33 | 34 | 7890 35 | 36 | Args: 37 | note_data (NoteCreateModel): data for creating a note using the note schema 38 | 39 | Returns: 40 | dict: note that has been created 41 | """ 42 | new_note = Note( 43 | id=str(uuid.uuid4()), title=note_data.title, content=note_data.content 44 | ) 45 | 46 | note = await db.add(session, new_note) 47 | 48 | return note 49 | 50 | 51 | @app.get("/note/{note_id}") 52 | async def get_note_by_id(note_id) -> dict: 53 | """API endpoint for retrieving a note by its ID 54 | 55 | Args: 56 | note_id (str): the ID of the note to retrieve 57 | 58 | Returns: 59 | dict: The retrieved note 60 | """ 61 | note = await db.get_by_id(session, note_id) 62 | 63 | return note 64 | 65 | 66 | @app.patch("/note/{note_id}") 67 | async def update_note(note_id: str, data: NoteCreateModel): 68 | """Update by ID 69 | 70 | Args: 71 | note_id (str): ID of note to update 72 | data (NoteCreateModel): data to update note 73 | 74 | Returns: 75 | dict: the updated note 76 | """ 77 | note = await db.update( 78 | session, note_id, data={"title": data.title, "content": data.content} 79 | ) 80 | 81 | return note 82 | 83 | 84 | @app.delete("/note/{note_id}", status_code=HTTPStatus.NO_CONTENT) 85 | async def delete_note(note_id) -> None: 86 | """Delete note by id 87 | 88 | Args: 89 | note_id (str): ID of note to delete 90 | 91 | """ 92 | note = await db.get_by_id(session, note_id) 93 | 94 | result = await db.delete(session, note) 95 | 96 | return result 97 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from db import Base 2 | from sqlalchemy.orm import Mapped, mapped_column 3 | from sqlalchemy import Text 4 | from datetime import datetime 5 | 6 | """ 7 | class Note: 8 | id str 9 | title str 10 | content str 11 | date_created datetime 12 | 13 | """ 14 | 15 | #the database model for notes 16 | class Note(Base): 17 | __tablename__ = "notes" 18 | id: Mapped[str] = mapped_column(primary_key=True) 19 | title: Mapped[str] = mapped_column(nullable=False) 20 | content: Mapped[str] = mapped_column(Text, nullable=False) 21 | date_created: Mapped[datetime] = mapped_column(default=datetime.utcnow) 22 | 23 | def __repr__(self) -> str: 24 | return f"" 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.5.0 2 | anyio==3.7.1 3 | asyncpg==0.28.0 4 | black==23.7.0 5 | click==8.1.6 6 | colorama==0.4.6 7 | fastapi==0.100.0 8 | greenlet==2.0.2 9 | h11==0.14.0 10 | idna==3.4 11 | mypy-extensions==1.0.0 12 | packaging==23.1 13 | pathspec==0.11.1 14 | platformdirs==3.9.1 15 | pydantic==2.0.3 16 | pydantic_core==2.3.0 17 | python-dotenv==1.0.0 18 | sniffio==1.3.0 19 | SQLAlchemy==2.0.19 20 | starlette==0.27.0 21 | typing_extensions==4.7.1 22 | uvicorn==0.23.1 23 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConfigDict 2 | from datetime import datetime 3 | 4 | 5 | #schema for returning a note 6 | class NoteModel(BaseModel): 7 | 8 | id : str 9 | title :str 10 | content: str 11 | date_created : datetime 12 | 13 | model_config = ConfigDict( 14 | from_attributes= True 15 | ) 16 | 17 | 18 | #schema for creating a note 19 | class NoteCreateModel(BaseModel): 20 | title :str 21 | content: str 22 | 23 | model_config = ConfigDict( 24 | from_attributes= True, 25 | json_schema_extra={ 26 | "example":{ 27 | "title":"Sample title", 28 | "content" : "Sample content" 29 | } 30 | } 31 | ) 32 | --------------------------------------------------------------------------------