Skip to content

Commit 54d8575

Browse files
committed
refactor: use UUID instead of integer ID
fastapi/full-stack-fastapi-template#1259
1 parent 07745b3 commit 54d8575

File tree

6 files changed

+41
-28
lines changed

6 files changed

+41
-28
lines changed

backend/app/api/routes/items.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from typing import Any
23

34
from fastapi import APIRouter, HTTPException
@@ -41,7 +42,7 @@ def read_items(
4142

4243

4344
@router.get("/{id}", response_model=ItemPublic)
44-
def read_item(session: SessionDep, current_user: CurrentUser, id: int) -> Any:
45+
def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
4546
"""
4647
Get item by ID.
4748
"""
@@ -69,7 +70,11 @@ def create_item(
6970

7071
@router.put("/{id}", response_model=ItemPublic)
7172
def update_item(
72-
*, session: SessionDep, current_user: CurrentUser, id: int, item_in: ItemUpdate
73+
*,
74+
session: SessionDep,
75+
current_user: CurrentUser,
76+
id: uuid.UUID,
77+
item_in: ItemUpdate,
7378
) -> Any:
7479
"""
7580
Update an item.
@@ -88,7 +93,9 @@ def update_item(
8893

8994

9095
@router.delete("/{id}")
91-
def delete_item(session: SessionDep, current_user: CurrentUser, id: int) -> Message:
96+
def delete_item(
97+
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
98+
) -> Message:
9299
"""
93100
Delete an item.
94101
"""

backend/app/api/routes/users.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from typing import Any
23

34
from fastapi import APIRouter, Depends, HTTPException
@@ -163,7 +164,7 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any:
163164

164165
@router.get("/{user_id}", response_model=UserPublic)
165166
def read_user_by_id(
166-
user_id: int, session: SessionDep, current_user: CurrentUser
167+
user_id: uuid.UUID, session: SessionDep, current_user: CurrentUser
167168
) -> Any:
168169
"""
169170
Get a specific user by id.
@@ -187,7 +188,7 @@ def read_user_by_id(
187188
def update_user(
188189
*,
189190
session: SessionDep,
190-
user_id: int,
191+
user_id: uuid.UUID,
191192
user_in: UserUpdate,
192193
) -> Any:
193194
"""
@@ -213,7 +214,7 @@ def update_user(
213214

214215
@router.delete("/{user_id}", dependencies=[Depends(get_current_active_superuser)])
215216
def delete_user(
216-
session: SessionDep, current_user: CurrentUser, user_id: int
217+
session: SessionDep, current_user: CurrentUser, user_id: uuid.UUID
217218
) -> Message:
218219
"""
219220
Delete a user.

backend/app/crud.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from typing import Any
23

34
from sqlmodel import Session, select
@@ -45,7 +46,7 @@ def authenticate(*, session: Session, email: str, password: str) -> User | None:
4546
return db_user
4647

4748

48-
def create_item(*, session: Session, item_in: ItemCreate, owner_id: int) -> Item:
49+
def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
4950
db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
5051
session.add(db_item)
5152
session.commit()

backend/app/models.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from datetime import datetime
23

34
from pydantic import EmailStr
@@ -41,15 +42,15 @@ class UpdatePassword(SQLModel):
4142

4243
# Database model, database table inferred from class name
4344
class User(UserBase, table=True):
44-
id: int | None = Field(default=None, primary_key=True)
45+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
4546
hashed_password: str
4647
items: list["Item"] = Relationship(back_populates="owner")
4748
tasks: list["Task"] = Relationship(back_populates="owner")
4849

4950

5051
# Properties to return via API, id is always required
5152
class UserPublic(UserBase):
52-
id: int
53+
id: uuid.UUID
5354

5455

5556
class UsersPublic(SQLModel):
@@ -75,16 +76,16 @@ class ItemUpdate(ItemBase):
7576

7677
# Database model, database table inferred from class name
7778
class Item(ItemBase, table=True):
78-
id: int | None = Field(default=None, primary_key=True)
79+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
7980
title: str = Field(max_length=255)
80-
owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False)
81+
owner_id: uuid.UUID = Field(default=None, foreign_key="user.id", nullable=False)
8182
owner: User | None = Relationship(back_populates="items")
8283

8384

8485
# Properties to return via API, id is always required
8586
class ItemPublic(ItemBase):
86-
id: int
87-
owner_id: int
87+
id: uuid.UUID
88+
owner_id: uuid.UUID
8889

8990

9091
class ItemsPublic(SQLModel):
@@ -105,7 +106,7 @@ class Token(SQLModel):
105106

106107
# Contents of JWT token
107108
class TokenPayload(SQLModel):
108-
sub: int | None = None
109+
sub: str | None = None
109110

110111

111112
class NewPassword(SQLModel):
@@ -130,14 +131,14 @@ class TaskBase(SQLModel):
130131

131132

132133
class Task(TaskBase, table=True):
133-
id: int | None = Field(default=None, primary_key=True)
134-
owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False)
134+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
135+
owner_id: uuid.UUID = Field(default=None, foreign_key="user.id", nullable=False)
135136
owner: User | None = Relationship(back_populates="tasks")
136137

137138

138139
class TaskPublic(TaskBase):
139-
id: int
140-
owner_id: int
140+
id: uuid.UUID
141+
owner_id: uuid.UUID
141142

142143

143144
class TasksPublic(SQLModel):

backend/app/tests/api/routes/test_items.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uuid
2+
13
from fastapi.testclient import TestClient
24
from sqlmodel import Session
35

@@ -34,15 +36,15 @@ def test_read_item(
3436
content = response.json()
3537
assert content["title"] == item.title
3638
assert content["description"] == item.description
37-
assert content["id"] == item.id
38-
assert content["owner_id"] == item.owner_id
39+
assert content["id"] == str(item.id)
40+
assert content["owner_id"] == str(item.owner_id)
3941

4042

4143
def test_read_item_not_found(
4244
client: TestClient, superuser_token_headers: dict[str, str]
4345
) -> None:
4446
response = client.get(
45-
f"{settings.API_V1_STR}/items/999",
47+
f"{settings.API_V1_STR}/items/{uuid.uuid4()}",
4648
headers=superuser_token_headers,
4749
)
4850
assert response.status_code == 404
@@ -91,16 +93,16 @@ def test_update_item(
9193
content = response.json()
9294
assert content["title"] == data["title"]
9395
assert content["description"] == data["description"]
94-
assert content["id"] == item.id
95-
assert content["owner_id"] == item.owner_id
96+
assert content["id"] == str(item.id)
97+
assert content["owner_id"] == str(item.owner_id)
9698

9799

98100
def test_update_item_not_found(
99101
client: TestClient, superuser_token_headers: dict[str, str]
100102
) -> None:
101103
data = {"title": "Updated title", "description": "Updated description"}
102104
response = client.put(
103-
f"{settings.API_V1_STR}/items/999",
105+
f"{settings.API_V1_STR}/items/{uuid.uuid4()}",
104106
headers=superuser_token_headers,
105107
json=data,
106108
)
@@ -141,7 +143,7 @@ def test_delete_item_not_found(
141143
client: TestClient, superuser_token_headers: dict[str, str]
142144
) -> None:
143145
response = client.delete(
144-
f"{settings.API_V1_STR}/items/999",
146+
f"{settings.API_V1_STR}/items/{uuid.uuid4()}",
145147
headers=superuser_token_headers,
146148
)
147149
assert response.status_code == 404

backend/app/tests/api/routes/test_users.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest.mock import patch
23

34
from fastapi.testclient import TestClient
@@ -105,7 +106,7 @@ def test_get_existing_user_permissions_error(
105106
client: TestClient, normal_user_token_headers: dict[str, str]
106107
) -> None:
107108
r = client.get(
108-
f"{settings.API_V1_STR}/users/999999",
109+
f"{settings.API_V1_STR}/users/{uuid.uuid4()}",
109110
headers=normal_user_token_headers,
110111
)
111112
assert r.status_code == 403
@@ -371,7 +372,7 @@ def test_update_user_not_exists(
371372
) -> None:
372373
data = {"full_name": "Updated_full_name"}
373374
r = client.patch(
374-
f"{settings.API_V1_STR}/users/99999999",
375+
f"{settings.API_V1_STR}/users/{uuid.uuid4()}",
375376
headers=superuser_token_headers,
376377
json=data,
377378
)
@@ -468,7 +469,7 @@ def test_delete_user_not_found(
468469
client: TestClient, superuser_token_headers: dict[str, str]
469470
) -> None:
470471
r = client.delete(
471-
f"{settings.API_V1_STR}/users/99999999",
472+
f"{settings.API_V1_STR}/users/{uuid.uuid4()}",
472473
headers=superuser_token_headers,
473474
)
474475
assert r.status_code == 404

0 commit comments

Comments
 (0)