Skip to content

Commit cb3cec2

Browse files
authored
Merge pull request #109 from igorbenav/fast-crud
Fast crud
2 parents bd8963a + d0a1140 commit cb3cec2

File tree

16 files changed

+55
-663
lines changed

16 files changed

+55
-663
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
- 🏬 Easy redis caching
5656
- 👜 Easy client-side caching
5757
- 🚦 ARQ integration for task queue
58-
- ⚙️ Efficient querying (only queries what's needed) with support for joins
59-
- ⎘ Out of the box pagination support
58+
- ⚙️ Efficient and robust queries with <a href="https://github.com/igorbenav/fastcrud">fastcrud</a>
59+
- ⎘ Out of the box offset and cursor pagination support with <a href="https://github.com/igorbenav/fastcrud">fastcrud</a>
6060
- 🛑 Rate Limiter dependency
6161
- 👮 FastAPI docs behind authentication and hidden based on the environment
6262
- 🦾 Easily extendable
@@ -749,14 +749,15 @@ poetry run alembic upgrade head
749749

750750
### 5.6 CRUD
751751

752-
Inside `app/crud`, create a new `crud_entities.py` inheriting from `CRUDBase` for each new entity:
752+
Inside `app/crud`, create a new `crud_entities.py` inheriting from `FastCRUD` for each new entity:
753753

754754
```python
755-
from app.crud.crud_base import CRUDBase
755+
from fastcrud import FastCRUD
756+
756757
from app.models.entity import Entity
757758
from app.schemas.entity import EntityCreateInternal, EntityUpdate, EntityUpdateInternal, EntityDelete
758759

759-
CRUDEntity = CRUDBase[Entity, EntityCreateInternal, EntityUpdate, EntityUpdateInternal, EntityDelete]
760+
CRUDEntity = FastCRUD[Entity, EntityCreateInternal, EntityUpdate, EntityUpdateInternal, EntityDelete]
760761
crud_entity = CRUDEntity(Entity)
761762
```
762763

@@ -767,7 +768,7 @@ So, for users:
767768
from app.model.user import User
768769
from app.schemas.user import UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete
769770

770-
CRUDUser = CRUDBase[User, UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete]
771+
CRUDUser = FastCRUD[User, UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete]
771772
crud_users = CRUDUser(User)
772773
```
773774

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ redis = "^5.0.1"
3030
arq = "^0.25.0"
3131
gunicorn = "^21.2.0"
3232
bcrypt = "^4.1.1"
33+
fastcrud = "^0.1.5"
3334

3435

3536
[build-system]

src/app/api/v1/posts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ async def write_post(
3636
post_internal_dict["created_by_user_id"] = db_user["id"]
3737

3838
post_internal = PostCreateInternal(**post_internal_dict)
39-
return await crud_posts.create(db=db, object=post_internal)
39+
created_post: PostRead = await crud_posts.create(db=db, object=post_internal)
40+
return created_post
4041

4142

4243
@router.get("/{username}/posts", response_model=PaginatedListResponse[PostRead])
@@ -77,7 +78,7 @@ async def read_post(
7778
if db_user is None:
7879
raise NotFoundException("User not found")
7980

80-
db_post = await crud_posts.get(
81+
db_post: PostRead | None = await crud_posts.get(
8182
db=db, schema_to_select=PostRead, id=id, created_by_user_id=db_user["id"], is_deleted=False
8283
)
8384
if db_post is None:

src/app/api/v1/rate_limits.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ async def write_rate_limit(
3131
raise DuplicateValueException("Rate Limit Name not available")
3232

3333
rate_limit_internal = RateLimitCreateInternal(**rate_limit_internal_dict)
34-
return await crud_rate_limits.create(db=db, object=rate_limit_internal)
34+
created_rate_limit: RateLimitRead = await crud_rate_limits.create(db=db, object=rate_limit_internal)
35+
return created_rate_limit
3536

3637

3738
@router.get("/tier/{tier_name}/rate_limits", response_model=PaginatedListResponse[RateLimitRead])
@@ -65,7 +66,9 @@ async def read_rate_limit(
6566
if not db_tier:
6667
raise NotFoundException("Tier not found")
6768

68-
db_rate_limit = await crud_rate_limits.get(db=db, schema_to_select=RateLimitRead, tier_id=db_tier["id"], id=id)
69+
db_rate_limit: dict | None = await crud_rate_limits.get(
70+
db=db, schema_to_select=RateLimitRead, tier_id=db_tier["id"], id=id
71+
)
6972
if db_rate_limit is None:
7073
raise NotFoundException("Rate Limit not found")
7174

src/app/api/v1/tiers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ async def write_tier(
2424
raise DuplicateValueException("Tier Name not available")
2525

2626
tier_internal = TierCreateInternal(**tier_internal_dict)
27-
return await crud_tiers.create(db=db, object=tier_internal)
27+
created_tier: TierRead = await crud_tiers.create(db=db, object=tier_internal)
28+
return created_tier
2829

2930

3031
@router.get("/tiers", response_model=PaginatedListResponse[TierRead])
@@ -40,7 +41,7 @@ async def read_tiers(
4041

4142
@router.get("/tier/{name}", response_model=TierRead)
4243
async def read_tier(request: Request, name: str, db: Annotated[AsyncSession, Depends(async_get_db)]) -> dict:
43-
db_tier = await crud_tiers.get(db=db, schema_to_select=TierRead, name=name)
44+
db_tier: TierRead | None = await crud_tiers.get(db=db, schema_to_select=TierRead, name=name)
4445
if db_tier is None:
4546
raise NotFoundException("Tier not found")
4647

src/app/api/v1/users.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ async def write_user(
3636
del user_internal_dict["password"]
3737

3838
user_internal = UserCreateInternal(**user_internal_dict)
39-
return await crud_users.create(db=db, object=user_internal)
39+
created_user: UserRead = await crud_users.create(db=db, object=user_internal)
40+
return created_user
4041

4142

4243
@router.get("/users", response_model=PaginatedListResponse[UserRead])
@@ -61,7 +62,9 @@ async def read_users_me(request: Request, current_user: Annotated[UserRead, Depe
6162

6263
@router.get("/user/{username}", response_model=UserRead)
6364
async def read_user(request: Request, username: str, db: Annotated[AsyncSession, Depends(async_get_db)]) -> dict:
64-
db_user = await crud_users.get(db=db, schema_to_select=UserRead, username=username, is_deleted=False)
65+
db_user: UserRead | None = await crud_users.get(
66+
db=db, schema_to_select=UserRead, username=username, is_deleted=False
67+
)
6568
if db_user is None:
6669
raise NotFoundException("User not found")
6770

@@ -168,7 +171,7 @@ async def read_user_tier(
168171
if not db_tier:
169172
raise NotFoundException("Tier not found")
170173

171-
joined = await crud_users.get_joined(
174+
joined: dict = await crud_users.get_joined(
172175
db=db,
173176
join_model=Tier,
174177
join_prefix="tier_",
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from ...crud.crud_base import CRUDBase
1+
from fastcrud import FastCRUD
2+
23
from ..db.token_blacklist import TokenBlacklist
34
from ..schemas import TokenBlacklistCreate, TokenBlacklistUpdate
45

5-
CRUDTokenBlacklist = CRUDBase[TokenBlacklist, TokenBlacklistCreate, TokenBlacklistUpdate, TokenBlacklistUpdate, None]
6+
CRUDTokenBlacklist = FastCRUD[TokenBlacklist, TokenBlacklistCreate, TokenBlacklistUpdate, TokenBlacklistUpdate, None]
67
crud_token_blacklist = CRUDTokenBlacklist(TokenBlacklist)
Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,11 @@
1-
from http import HTTPStatus
2-
3-
from fastapi import HTTPException, status
4-
5-
6-
class CustomException(HTTPException):
7-
def __init__(self, status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR, detail: str | None = None):
8-
if not detail:
9-
detail = HTTPStatus(status_code).description
10-
super().__init__(status_code=status_code, detail=detail)
11-
12-
13-
class BadRequestException(CustomException):
14-
def __init__(self, detail: str | None = None):
15-
super().__init__(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
16-
17-
18-
class NotFoundException(CustomException):
19-
def __init__(self, detail: str | None = None):
20-
super().__init__(status_code=status.HTTP_404_NOT_FOUND, detail=detail)
21-
22-
23-
class ForbiddenException(CustomException):
24-
def __init__(self, detail: str | None = None):
25-
super().__init__(status_code=status.HTTP_403_FORBIDDEN, detail=detail)
26-
27-
28-
class UnauthorizedException(CustomException):
29-
def __init__(self, detail: str | None = None):
30-
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)
31-
32-
33-
class UnprocessableEntityException(CustomException):
34-
def __init__(self, detail: str | None = None):
35-
super().__init__(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail)
36-
37-
38-
class DuplicateValueException(CustomException):
39-
def __init__(self, detail: str | None = None):
40-
super().__init__(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail)
41-
42-
43-
class RateLimitException(CustomException):
44-
def __init__(self, detail: str | None = None):
45-
super().__init__(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=detail)
1+
# ruff: noqa
2+
from fastcrud.exceptions.http_exceptions import (
3+
CustomException,
4+
BadRequestException,
5+
NotFoundException,
6+
ForbiddenException,
7+
UnauthorizedException,
8+
UnprocessableEntityException,
9+
DuplicateValueException,
10+
RateLimitException,
11+
)

src/app/core/worker/functions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import logging
3+
34
import uvloop
45
from arq.worker import Worker
56

@@ -16,7 +17,7 @@ async def sample_background_task(ctx: Worker, name: str) -> str:
1617

1718
# -------- base functions --------
1819
async def startup(ctx: Worker) -> None:
19-
logging.info('Worker Started')
20+
logging.info("Worker Started")
2021

2122

2223
async def shutdown(ctx: Worker) -> None:

src/app/core/worker/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from arq.connections import RedisSettings
2+
23
from ...core.config import settings
3-
from .functions import sample_background_task, startup, shutdown
4+
from .functions import sample_background_task, shutdown, startup
45

56
REDIS_QUEUE_HOST = settings.REDIS_QUEUE_HOST
67
REDIS_QUEUE_PORT = settings.REDIS_QUEUE_PORT

0 commit comments

Comments
 (0)