Skip to content

Commit f41f443

Browse files
estebanx64Esteban Maya Cadavidtiangolo
authored
♻ Refactor items and services endpoints to return count and data, and add CI tests (#599)
Co-authored-by: Esteban Maya Cadavid <[email protected]> Co-authored-by: Sebastián Ramírez <[email protected]>
1 parent 176b6fb commit f41f443

File tree

9 files changed

+104
-30
lines changed

9 files changed

+104
-30
lines changed

.github/workflows/test.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
types:
9+
- opened
10+
- synchronize
11+
12+
jobs:
13+
14+
test:
15+
runs-on: ubuntu-latest
16+
defaults:
17+
run:
18+
working-directory: src
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.10'
27+
28+
- name: Docker Compose build
29+
run: docker compose build
30+
- name: Docker Compose remove old containers and volumes
31+
run: docker compose down -v --remove-orphans
32+
- name: Docker Compose up
33+
run: docker compose up -d
34+
- name: Docker Compose run tests
35+
run: docker compose exec -T backend bash /app/tests-start.sh
36+
- name: Docker Compose cleanup
37+
run: docker compose down -v --remove-orphans

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
from typing import Any
22

33
from fastapi import APIRouter, HTTPException
4-
from sqlmodel import select
4+
from sqlmodel import select, func
55

66
from app.api.deps import CurrentUser, SessionDep
7-
from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message
7+
from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut
88

99
router = APIRouter()
1010

1111

12-
@router.get("/", response_model=list[ItemOut])
12+
@router.get("/", response_model=ItemsOut)
1313
def read_items(
1414
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
1515
) -> Any:
1616
"""
1717
Retrieve items.
1818
"""
1919

20+
statment = select(func.count()).select_from(Item)
21+
count = session.exec(statment).one()
22+
2023
if current_user.is_superuser:
2124
statement = select(Item).offset(skip).limit(limit)
22-
return session.exec(statement).all()
25+
items = session.exec(statement).all()
2326
else:
2427
statement = (
2528
select(Item)
2629
.where(Item.owner_id == current_user.id)
2730
.offset(skip)
2831
.limit(limit)
2932
)
30-
return session.exec(statement).all()
33+
items = session.exec(statement).all()
34+
35+
return ItemsOut(data=items, count=count)
3136

3237

3338
@router.get("/{id}", response_model=ItemOut)

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any, List
22

33
from fastapi import APIRouter, Depends, HTTPException
4-
from sqlmodel import select
4+
from sqlmodel import select, func
55

66
from app import crud
77
from app.api.deps import (
@@ -18,6 +18,7 @@
1818
UserCreate,
1919
UserCreateOpen,
2020
UserOut,
21+
UsersOut,
2122
UserUpdate,
2223
UserUpdateMe,
2324
)
@@ -29,15 +30,20 @@
2930
@router.get(
3031
"/",
3132
dependencies=[Depends(get_current_active_superuser)],
32-
response_model=List[UserOut],
33+
response_model=UsersOut
3334
)
3435
def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
3536
"""
3637
Retrieve users.
3738
"""
39+
40+
statment = select(func.count()).select_from(User)
41+
count = session.exec(statment).one()
42+
3843
statement = select(User).offset(skip).limit(limit)
3944
users = session.exec(statement).all()
40-
return users
45+
46+
return UsersOut(data=users, count=count)
4147

4248

4349
@router.post(

src/backend/app/app/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ class UserOut(UserBase):
5151
id: int
5252

5353

54+
class UsersOut(SQLModel):
55+
data: list[UserOut]
56+
count: int
57+
58+
5459
# Shared properties
5560
class ItemBase(SQLModel):
5661
title: str
@@ -80,6 +85,12 @@ class Item(ItemBase, table=True):
8085
# Properties to return via API, id is always required
8186
class ItemOut(ItemBase):
8287
id: int
88+
owner_id: int
89+
90+
91+
class ItemsOut(SQLModel):
92+
data: list[ItemOut]
93+
count: int
8394

8495

8596
# Generic message

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
def test_celery_worker_test(
99
client: TestClient, superuser_token_headers: Dict[str, str]
1010
) -> None:
11-
data = {"msg": "test"}
11+
data = {"message": "test"}
1212
r = client.post(
1313
f"{settings.API_V1_STR}/utils/test-celery/",
1414
json=data,
1515
headers=superuser_token_headers,
1616
)
1717
response = r.json()
18-
assert response["msg"] == "Word received"
18+
assert response["message"] == "Word received"

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def test_retrieve_users(
110110
r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers)
111111
all_users = r.json()
112112

113-
assert len(all_users) > 1
114-
for item in all_users:
113+
assert len(all_users["data"]) > 1
114+
assert "count" in all_users
115+
for item in all_users["data"]:
115116
assert "email" in item

src/backend/app/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"}
2323
httpx = "^0.25.1"
2424
psycopg = {extras = ["binary"], version = "^3.1.13"}
2525
sqlmodel = "^0.0.16"
26+
# Pin bcrypt until passlib supports the latest
27+
bcrypt = "4.0.1"
2628

2729
[tool.poetry.group.dev.dependencies]
2830
mypy = "^1.7.0"

src/docker-compose.override.yml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,22 @@ services:
2828
- traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`)
2929
- traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80
3030

31+
db:
32+
ports:
33+
- "5432:5432"
34+
3135
pgadmin:
3236
ports:
3337
- "5050:5050"
3438

39+
# Uncomment the section below to be able to debug locally
40+
# queue:
41+
# ports:
42+
# - "5671:5671"
43+
# - "5672:5672"
44+
# - "15672:15672"
45+
# - "15671:15671"
46+
3547
flower:
3648
ports:
3749
- "5555:5555"
@@ -84,14 +96,14 @@ services:
8496
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`)
8597
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
8698

87-
new-frontend:
88-
build:
89-
context: ./new-frontend
90-
labels:
91-
- traefik.enable=true
92-
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
93-
- traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
94-
- traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
99+
# new-frontend:
100+
# build:
101+
# context: ./new-frontend
102+
# labels:
103+
# - traefik.enable=true
104+
# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
105+
# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
106+
# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
95107

96108
networks:
97109
traefik-public:

src/docker-compose.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,16 @@ services:
191191
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`)
192192
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
193193

194-
new-frontend:
195-
image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}'
196-
build:
197-
context: ./new-frontend
198-
deploy:
199-
labels:
200-
- traefik.enable=true
201-
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
202-
- traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
203-
- traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
194+
# new-frontend:
195+
# image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}'
196+
# build:
197+
# context: ./new-frontend
198+
# deploy:
199+
# labels:
200+
# - traefik.enable=true
201+
# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
202+
# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`)
203+
# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80
204204

205205

206206
volumes:

0 commit comments

Comments
 (0)