Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple test with sqlite testing db #10

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: main
- name: Add environment variables to .env
run: |
echo "DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/starter_db_dev" >> .env
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.10-slim
FROM python:3.11-slim

WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE 1
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pull official base image
FROM python:3.10-slim
FROM python:3.11-slim

# set work directory
WORKDIR /usr/src/app
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ rebuild:
rebuild-d:
docker-compose down -v --remove-orphans && sudo rm -rf postgres-data;
docker-compose up --build -d;

d-test:
# Rebuilds container and runs docker test
docker-compose build web-test && docker-compose run web-test pytest tests -x -o log_cli=true
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
# starter-api

![Continuous Integration](https://github.com/jonwhittlestone/starter-api/workflows/Continuous%20Integration/badge.svg)

## Usage

```bash
make up-build
```

Run the command above and then visit: http://127.0.0.1:8004
Run the command above and then visit: http://127.0.0.1:8004

### Run the tests:

> See [snippets-resources.md](snippets-resources.md) for handy snippets and resources.
```bash
make d-test
```

> See [snippets-resources.md](snippets-resources.md) for handy snippets and resources.

## Debug FastAPI using VSCode

1. Run this:
```bash
make up-build
```

```bash
make up-build
```

2. In vscode add a breakpoint

3. Run the debugger (F5)

4. Visit: http://127.0.0.1:8004
4. Visit: http://127.0.0.1:8004

5. The vscode debugger will pause execution at your breakpoint.

Expand All @@ -35,13 +42,13 @@ Run the command above and then visit: http://127.0.0.1:8004
- [x] Poetry, Dynaconf
- [x] Containerised tests
- [x] Github Action to run tests
- [ ] FastAPI repositories, schemas / logging / cleanup and mocked tests
- [ ] FastAPI config, routers, user model, auth, repositories, schemas, logging, cleanup and mocked tests.
- [ ] Production deployment
- [ ] Pre-commit / manage.py / migrations
- [ ] Tag this repo and release
```

<!--
<!--
```
- [ ] Simple React Typescript Frontend
- [ ] Okta auth
Expand Down
Empty file added backend/api/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions backend/api/album.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from fastapi import APIRouter, Depends, status


from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession

from ..models import Album, AlbumCreate
from ..db import init_db, get_session

routers = APIRouter()

@routers.get("/albums", response_model=list[Album])
async def get_songs(session: AsyncSession = Depends(get_session)):
result = await session.execute(select(Album))
albums = result.scalars().all()
return [
Album(name=album.name, artist=album.artist, year=album.year, id=album.id)
for album in albums
]


@routers.post("/albums", status_code=status.HTTP_201_CREATED, response_model=Album)
async def add_album(album: AlbumCreate, session: AsyncSession = Depends(get_session)):
album = Album(name=album.name, artist=album.artist, year=album.year)
session.add(album)
await session.commit()
await session.refresh(album)
return album
9 changes: 9 additions & 0 deletions backend/api/endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

from fastapi import APIRouter

from .health import routers as health_router
from .album import routers as album_router

routers = APIRouter()
routers.include_router(health_router, tags=["health"])
routers.include_router(album_router, tags=["album"])
19 changes: 19 additions & 0 deletions backend/api/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


from fastapi import APIRouter, status
from fastapi.responses import Response, JSONResponse
from ..config.dyna import settings

routers = APIRouter()


@routers.get(
"/health",
response_model=str,
description="health check",
name="health",
)
async def health() -> JSONResponse:
return JSONResponse(content={"current_env": settings.current_env},
status_code=status.HTTP_200_OK
)
25 changes: 25 additions & 0 deletions backend/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

import os
from pydantic import BaseSettings

from .dyna import settings


class CoreSettings(BaseSettings):
DEBUG: bool = False
SERVICE_NAME: str = "starter-api"
SERVICE_LABEL: str = f"starter-api {settings.current_env}"
SERVICE_VERSION = "1.0.0"
SERVICE_DESCRIPTION = "A no-brainer starter for a dockerised FastAPI app"
DATABASE_URL: str = os.environ.get("DATABASE_URL", "")
APP_HOST: str = "0.0.0.0" # nosec
APP_PORT: int = 8000

class ModuleSettings(BaseSettings):
...

class Sett(CoreSettings, ModuleSettings):
pass


setts = Sett()
14 changes: 7 additions & 7 deletions backend/config/settings.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[default]
APP_NAME="Starter API"

APP_LABEL="Starter API"
[development]
APP_NAME="Starter API DEV"
APP_LABEL="Starter API DEV"

[ci]
APP_NAME="Starter API CI"
APP_LABEL="Starter API CI"

[testing]
APP_NAME="Starter API TEST"
APP_LABEL="Starter API TEST"

[staging]
APP_NAME="Starter API ST"
APP_LABEL="Starter API ST"

[production]
APP_NAME="Starter API PR"
APP_LABEL="Starter API PR"
4 changes: 2 additions & 2 deletions backend/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

from .config import setts

DATABASE_URL = os.environ.get("DATABASE_URL")

engine = create_async_engine(DATABASE_URL, echo=True, future=True)
engine = create_async_engine(setts.DATABASE_URL, echo=True, future=True)


async def init_db():
Expand Down
55 changes: 15 additions & 40 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
import uvicorn

from fastapi import Body, FastAPI, Depends, Request
from starlette.status import HTTP_201_CREATED
from .db import init_db, get_session
from .models import Album, AlbumCreate
from .config.dyna import settings

from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession

from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from .db import init_db
from .config import setts
from .api.endpoints import routers


app = FastAPI(
title=setts.SERVICE_LABEL,
description=setts.SERVICE_DESCRIPTION,
version=setts.SERVICE_VERSION,
debug=setts.DEBUG,
)

app = FastAPI(title=settings.APP_NAME,)
app.mount("/static", StaticFiles(directory="backend/static"), name="static")
templates = Jinja2Templates(directory="backend/templates")


@app.on_event("startup")
async def on_startup():
await init_db()


@app.get("/health")
def health():
return JSONResponse(status_code=200,
content={
"app_name": settings.APP_NAME,
"current_env": settings.current_env,
})


@app.get("/")
def home(request: Request):
# add a breakpoint on line below
Expand All @@ -42,25 +33,9 @@ def home(request: Request):
"step-two": "In vscode add a breakpoint",
"step-three": "Run the debugger (F5)",
"step-four": "Open the browser to http://127.0.0.1:8004",
"step-five": "It will pause execution at the breakpoint"
"step-five": "It will pause execution at the breakpoint",
}
#return templates.TemplateResponse("home.html", context={"request": request})

@app.get("/albums", response_model=list[Album])
async def get_songs(session: AsyncSession = Depends(get_session)):
result = await session.execute(select(Album))
albums = result.scalars().all()
return [
Album(name=album.name, artist=album.artist, year=album.year, id=album.id)
for album in albums
]


@app.post("/albums", status_code=HTTP_201_CREATED, response_model=Album)
async def add_album(album: AlbumCreate, session: AsyncSession = Depends(get_session)):
album = Album(name=album.name, artist=album.artist, year=album.year)
session.add(album)
await session.commit()
await session.refresh(album)
return album

# register routes
app.include_router(routers, prefix="/api/v1")
Loading