Browse Source

♻ Refactor items and services endpoints to return count and data, and add CI tests (#599)

Co-authored-by: Esteban Maya Cadavid <emaya@trueblue.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
pull/13907/head
Esteban Maya 1 year ago
committed by GitHub
parent
commit
f41f4432fe
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 37
      .github/workflows/test.yaml
  2. 15
      src/backend/app/app/api/api_v1/endpoints/items.py
  3. 12
      src/backend/app/app/api/api_v1/endpoints/users.py
  4. 11
      src/backend/app/app/models.py
  5. 4
      src/backend/app/app/tests/api/api_v1/test_celery.py
  6. 5
      src/backend/app/app/tests/api/api_v1/test_users.py
  7. 2
      src/backend/app/pyproject.toml
  8. 28
      src/docker-compose.override.yml
  9. 20
      src/docker-compose.yml

37
.github/workflows/test.yaml

@ -0,0 +1,37 @@
name: Test
on:
push:
branches:
- master
pull_request:
types:
- opened
- synchronize
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Docker Compose build
run: docker compose build
- name: Docker Compose remove old containers and volumes
run: docker compose down -v --remove-orphans
- name: Docker Compose up
run: docker compose up -d
- name: Docker Compose run tests
run: docker compose exec -T backend bash /app/tests-start.sh
- name: Docker Compose cleanup
run: docker compose down -v --remove-orphans

15
src/backend/app/app/api/api_v1/endpoints/items.py

@ -1,15 +1,15 @@
from typing import Any
from fastapi import APIRouter, HTTPException
from sqlmodel import select
from sqlmodel import select, func
from app.api.deps import CurrentUser, SessionDep
from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message
from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut
router = APIRouter()
@router.get("/", response_model=list[ItemOut])
@router.get("/", response_model=ItemsOut)
def read_items(
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
) -> Any:
@ -17,9 +17,12 @@ def read_items(
Retrieve items.
"""
statment = select(func.count()).select_from(Item)
count = session.exec(statment).one()
if current_user.is_superuser:
statement = select(Item).offset(skip).limit(limit)
return session.exec(statement).all()
items = session.exec(statement).all()
else:
statement = (
select(Item)
@ -27,7 +30,9 @@ def read_items(
.offset(skip)
.limit(limit)
)
return session.exec(statement).all()
items = session.exec(statement).all()
return ItemsOut(data=items, count=count)
@router.get("/{id}", response_model=ItemOut)

12
src/backend/app/app/api/api_v1/endpoints/users.py

@ -1,7 +1,7 @@
from typing import Any, List
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import select
from sqlmodel import select, func
from app import crud
from app.api.deps import (
@ -18,6 +18,7 @@ from app.models import (
UserCreate,
UserCreateOpen,
UserOut,
UsersOut,
UserUpdate,
UserUpdateMe,
)
@ -29,15 +30,20 @@ router = APIRouter()
@router.get(
"/",
dependencies=[Depends(get_current_active_superuser)],
response_model=List[UserOut],
response_model=UsersOut
)
def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
"""
Retrieve users.
"""
statment = select(func.count()).select_from(User)
count = session.exec(statment).one()
statement = select(User).offset(skip).limit(limit)
users = session.exec(statement).all()
return users
return UsersOut(data=users, count=count)
@router.post(

11
src/backend/app/app/models.py

@ -51,6 +51,11 @@ class UserOut(UserBase):
id: int
class UsersOut(SQLModel):
data: list[UserOut]
count: int
# Shared properties
class ItemBase(SQLModel):
title: str
@ -80,6 +85,12 @@ class Item(ItemBase, table=True):
# Properties to return via API, id is always required
class ItemOut(ItemBase):
id: int
owner_id: int
class ItemsOut(SQLModel):
data: list[ItemOut]
count: int
# Generic message

4
src/backend/app/app/tests/api/api_v1/test_celery.py

@ -8,11 +8,11 @@ from app.core.config import settings
def test_celery_worker_test(
client: TestClient, superuser_token_headers: Dict[str, str]
) -> None:
data = {"msg": "test"}
data = {"message": "test"}
r = client.post(
f"{settings.API_V1_STR}/utils/test-celery/",
json=data,
headers=superuser_token_headers,
)
response = r.json()
assert response["msg"] == "Word received"
assert response["message"] == "Word received"

5
src/backend/app/app/tests/api/api_v1/test_users.py

@ -110,6 +110,7 @@ def test_retrieve_users(
r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers)
all_users = r.json()
assert len(all_users) > 1
for item in all_users:
assert len(all_users["data"]) > 1
assert "count" in all_users
for item in all_users["data"]:
assert "email" in item

2
src/backend/app/pyproject.toml

@ -23,6 +23,8 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"}
httpx = "^0.25.1"
psycopg = {extras = ["binary"], version = "^3.1.13"}
sqlmodel = "^0.0.16"
# Pin bcrypt until passlib supports the latest
bcrypt = "4.0.1"
[tool.poetry.group.dev.dependencies]
mypy = "^1.7.0"

28
src/docker-compose.override.yml

@ -28,10 +28,22 @@ services:
- traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`)
- traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80
db:
ports:
- "5432:5432"
pgadmin:
ports:
- "5050:5050"
# Uncomment the section below to be able to debug locally
# queue:
# ports:
# - "5671:5671"
# - "5672:5672"
# - "15672:15672"
# - "15671:15671"
flower:
ports:
- "5555:5555"
@ -84,14 +96,14 @@ services:
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`)
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
new-frontend:
build:
context: ./new-frontend
labels:
- traefik.enable=true
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
- traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
- traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
# new-frontend:
# build:
# context: ./new-frontend
# labels:
# - traefik.enable=true
# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
networks:
traefik-public:

20
src/docker-compose.yml

@ -191,16 +191,16 @@ services:
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`)
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
new-frontend:
image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}'
build:
context: ./new-frontend
deploy:
labels:
- traefik.enable=true
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
- traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
- traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
# new-frontend:
# image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}'
# build:
# context: ./new-frontend
# deploy:
# labels:
# - traefik.enable=true
# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
volumes:

Loading…
Cancel
Save