Skip to content

Commit e0a436d

Browse files
authored
Merge pull request #186 from Saiyashwanth7/main
Switch from UUID v4 to v7
2 parents 49f87ee + 79fc7e6 commit e0a436d

File tree

17 files changed

+700
-628
lines changed

17 files changed

+700
-628
lines changed

docker-compose.test.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
web:
3+
user: root # Run as root for tests to allow global package installation
4+
environment:
5+
- PYTHONPATH=/usr/local/lib/python3.11/site-packages
6+
command: bash -c "pip install faker pytest-asyncio pytest-mock && pytest tests/ -v"
7+
volumes:
8+
- ./tests:/code/tests

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies = [
1414
"uvloop>=0.19.0",
1515
"httptools>=0.6.1",
1616
"uuid>=1.30",
17+
"uuid6>=2024.1.12",
1718
"alembic>=1.13.1",
1819
"asyncpg>=0.29.0",
1920
"SQLAlchemy-Utils>=0.41.1",
@@ -117,4 +118,4 @@ explicit_package_bases = true
117118

118119
[[tool.mypy.overrides]]
119120
module = "src.app.*"
120-
disallow_untyped_defs = true
121+
disallow_untyped_defs = true

src/app/api/dependencies.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ async def get_current_user(
3434
user = await crud_users.get(db=db, username=token_data.username_or_email, is_deleted=False)
3535

3636
if user:
37-
return cast(dict[str, Any], user)
37+
if hasattr(user, 'model_dump'):
38+
return user.model_dump()
39+
else:
40+
return user
3841

3942
raise UnauthorizedException("User not authenticated.")
4043

src/app/api/v1/posts.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import Annotated, Any, cast
2-
32
from fastapi import APIRouter, Depends, Request
43
from fastcrud.paginated import PaginatedListResponse, compute_offset, paginated_response
54
from sqlalchemy.ext.asyncio import AsyncSession
@@ -60,7 +59,13 @@ async def read_posts(
6059
page: int = 1,
6160
items_per_page: int = 10,
6261
) -> dict:
63-
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
62+
db_user = await crud_users.get(
63+
db=db,
64+
username=username,
65+
is_deleted=False,
66+
schema_to_select=UserRead,
67+
return_as_model=True
68+
)
6469
if not db_user:
6570
raise NotFoundException("User not found")
6671

@@ -82,7 +87,13 @@ async def read_posts(
8287
async def read_post(
8388
request: Request, username: str, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]
8489
) -> PostRead:
85-
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
90+
db_user = await crud_users.get(
91+
db=db,
92+
username=username,
93+
is_deleted=False,
94+
schema_to_select=UserRead,
95+
return_as_model=True
96+
)
8697
if db_user is None:
8798
raise NotFoundException("User not found")
8899

@@ -106,7 +117,13 @@ async def patch_post(
106117
current_user: Annotated[dict, Depends(get_current_user)],
107118
db: Annotated[AsyncSession, Depends(async_get_db)],
108119
) -> dict[str, str]:
109-
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
120+
db_user = await crud_users.get(
121+
db=db,
122+
username=username,
123+
is_deleted=False,
124+
schema_to_select=UserRead,
125+
return_as_model=True
126+
)
110127
if db_user is None:
111128
raise NotFoundException("User not found")
112129

@@ -131,7 +148,13 @@ async def erase_post(
131148
current_user: Annotated[dict, Depends(get_current_user)],
132149
db: Annotated[AsyncSession, Depends(async_get_db)],
133150
) -> dict[str, str]:
134-
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
151+
db_user = await crud_users.get(
152+
db=db,
153+
username=username,
154+
is_deleted=False,
155+
schema_to_select=UserRead,
156+
return_as_model=True
157+
)
135158
if db_user is None:
136159
raise NotFoundException("User not found")
137160

@@ -153,7 +176,13 @@ async def erase_post(
153176
async def erase_db_post(
154177
request: Request, username: str, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]
155178
) -> dict[str, str]:
156-
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
179+
db_user = await crud_users.get(
180+
db=db,
181+
username=username,
182+
is_deleted=False,
183+
schema_to_select=UserRead,
184+
return_as_model=True
185+
)
157186
if db_user is None:
158187
raise NotFoundException("User not found")
159188

@@ -162,4 +191,4 @@ async def erase_db_post(
162191
raise NotFoundException("Post not found")
163192

164193
await crud_posts.db_delete(db=db, id=id)
165-
return {"message": "Post deleted from the database"}
194+
return {"message": "Post deleted from the database"}

src/app/api/v1/users.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ async def read_user(request: Request, username: str, db: Annotated[AsyncSession,
7272
return cast(UserRead, db_user)
7373

7474

75+
7576
@router.patch("/user/{username}")
7677
async def patch_user(
7778
request: Request,
@@ -80,23 +81,27 @@ async def patch_user(
8081
current_user: Annotated[dict, Depends(get_current_user)],
8182
db: Annotated[AsyncSession, Depends(async_get_db)],
8283
) -> dict[str, str]:
83-
db_user = await crud_users.get(db=db, username=username, schema_to_select=UserRead)
84+
db_user = await crud_users.get(db=db, username=username)
8485
if db_user is None:
8586
raise NotFoundException("User not found")
8687

87-
db_user = cast(UserRead, db_user)
88-
if db_user.username != current_user["username"]:
89-
raise ForbiddenException()
88+
if isinstance(db_user, dict):
89+
db_username = db_user["username"]
90+
db_email = db_user["email"]
91+
else:
92+
db_username = db_user.username
93+
db_email = db_user.email
9094

91-
if values.username != db_user.username:
92-
existing_username = await crud_users.exists(db=db, username=values.username)
93-
if existing_username:
94-
raise DuplicateValueException("Username not available")
95+
if db_username != current_user["username"]:
96+
raise ForbiddenException()
9597

96-
if values.email != db_user.email:
97-
existing_email = await crud_users.exists(db=db, email=values.email)
98-
if existing_email:
98+
if values.email is not None and values.email != db_email:
99+
if await crud_users.exists(db=db, email=values.email):
99100
raise DuplicateValueException("Email is already registered")
101+
102+
if values.username is not None and values.username != db_username:
103+
if await crud_users.exists(db=db, username=values.username):
104+
raise DuplicateValueException("Username not available")
100105

101106
await crud_users.update(db=db, object=values, username=username)
102107
return {"message": "User updated"}

src/app/core/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class EnvironmentSettings(BaseSettings):
129129

130130
class Settings(
131131
AppSettings,
132+
SQLiteSettings,
132133
PostgresSettings,
133134
CryptSettings,
134135
FirstUserSettings,

src/app/core/db/database.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ class Base(DeclarativeBase, MappedAsDataclass):
1111
pass
1212

1313

14-
DATABASE_URI = settings.POSTGRES_URI
14+
DATABASE_URI = settings.POSTGRES_URI
1515
DATABASE_PREFIX = settings.POSTGRES_ASYNC_PREFIX
1616
DATABASE_URL = f"{DATABASE_PREFIX}{DATABASE_URI}"
1717

18+
1819
async_engine = create_async_engine(DATABASE_URL, echo=False, future=True)
1920

2021
local_session = async_sessionmaker(bind=async_engine, class_=AsyncSession, expire_on_commit=False)

src/app/core/db/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid as uuid_pkg
2+
from uuid6 import uuid7
23
from datetime import UTC, datetime
34

45
from sqlalchemy import Boolean, DateTime, text
@@ -8,7 +9,7 @@
89

910
class UUIDMixin:
1011
uuid: Mapped[uuid_pkg.UUID] = mapped_column(
11-
UUID, primary_key=True, default=uuid_pkg.uuid4, server_default=text("gen_random_uuid()")
12+
UUID(as_uuid=True), primary_key=True, default=uuid7, server_default=text("gen_random_uuid()")
1213
)
1314

1415

src/app/core/schemas.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid as uuid_pkg
2+
from uuid6 import uuid7
23
from datetime import UTC, datetime
34
from typing import Any
45

@@ -13,7 +14,7 @@ class HealthCheck(BaseModel):
1314

1415
# -------------- mixins --------------
1516
class UUIDSchema(BaseModel):
16-
uuid: uuid_pkg.UUID = Field(default_factory=uuid_pkg.uuid4)
17+
uuid: uuid_pkg.UUID = Field(default_factory=uuid7)
1718

1819

1920
class TimestampSchema(BaseModel):

src/app/core/utils/cache.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,15 @@ async def _delete_keys_by_pattern(pattern: str) -> None:
172172
many keys simultaneously may impact the performance of the Redis server.
173173
"""
174174
if client is None:
175-
raise MissingClientError
176-
177-
cursor = -1
178-
while cursor != 0:
175+
return
176+
177+
cursor = 0
178+
while True:
179179
cursor, keys = await client.scan(cursor, match=pattern, count=100)
180180
if keys:
181181
await client.delete(*keys)
182+
if cursor == 0:
183+
break
182184

183185

184186
def cache(

0 commit comments

Comments
 (0)