Skip to content

Commit cc3e465

Browse files
authored
Merge pull request #53 from igorbenav/crud-joined
Crud joined
2 parents d9c978f + ae5e767 commit cc3e465

File tree

6 files changed

+384
-12
lines changed

6 files changed

+384
-12
lines changed

README.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,13 +607,14 @@ from app.schemas.user import UserCreateInternal, UserUpdate, UserUpdateInternal,
607607
CRUDUser = CRUDBase[User, UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete]
608608
crud_users = CRUDUser(User)
609609
```
610-
610+
#### 5.6.1 Get
611611
When actually using the crud in an endpoint, to get data you just pass the database connection and the attributes as kwargs:
612612
```python
613613
# Here I'm getting the first user with email == user.email (email is unique in this case)
614614
user = await crud_users.get(db=db, email=user.email)
615615
```
616616

617+
#### 5.6.2 Get Multi
617618
To get a list of objects with the attributes, you should use the get_multi:
618619
```python
619620
# Here I'm getting at most 10 users with the name 'User Userson' except for the first 3
@@ -653,6 +654,7 @@ Which will return a python dict with the following structure:
653654
}
654655
```
655656

657+
#### 5.6.3 Create
656658
To create, you pass a `CreateSchemaType` object with the attributes, such as a `UserCreate` pydantic schema:
657659
```python
658660
from app.core.schemas.user import UserCreate
@@ -668,13 +670,15 @@ user_internal = UserCreate(
668670
crud_users.create(db=db, object=user_internal)
669671
```
670672

673+
#### 5.6.4 Exists
671674
To just check if there is at least one row that matches a certain set of attributes, you should use `exists`
672675
```python
673676
# This queries only the email variable
674677
# It returns True if there's at least one or False if there is none
675678
crud_users.exists(db=db, email=user@example.com)
676679
```
677680

681+
#### 5.6.5 Count
678682
You can also get the count of a certain object with the specified filter:
679683
```python
680684
# Here I'm getting the count of users with the name 'User Userson'
@@ -684,6 +688,7 @@ user = await crud_users.count(
684688
)
685689
```
686690

691+
#### 5.6.6 Update
687692
To update you pass an `object` which may be a `pydantic schema` or just a regular `dict`, and the kwargs.
688693
You will update with `objects` the rows that match your `kwargs`.
689694
```python
@@ -692,6 +697,7 @@ You will update with `objects` the rows that match your `kwargs`.
692697
crud_users.update(db=db, object={name="Updated Name"}, username="myusername")
693698
```
694699

700+
#### 5.6.7 Delete
695701
To delete we have two options:
696702
- db_delete: actually deletes the row from the database
697703
- delete:
@@ -706,6 +712,59 @@ crud_users.delete(db=db, username="myusername")
706712
crud_users.db_delete(db=db, username="myusername")
707713
```
708714

715+
#### 5.6.8 Get Joined
716+
To retrieve data with a join operation, you can use the get_joined method from your CRUD module. Here's how to do it:
717+
718+
```python
719+
# Fetch a single record with a join on another model (e.g., User and Tier).
720+
result = await crud_users.get_joined(
721+
db=db, # The SQLAlchemy async session.
722+
join_model=Tier, # The model to join with (e.g., Tier).
723+
schema_to_select=UserSchema, # Pydantic schema for selecting User model columns (optional).
724+
join_schema_to_select=TierSchema # Pydantic schema for selecting Tier model columns (optional).
725+
)
726+
```
727+
728+
**Relevant Parameters:**
729+
- `join_model`: The model you want to join with (e.g., Tier).
730+
- `join_prefix`: Optional prefix to be added to all columns of the joined model. If None, no prefix is added.
731+
- `join_on`: SQLAlchemy Join object for specifying the ON clause of the join. If None, the join condition is auto-detected based on foreign keys.
732+
- `schema_to_select`: A Pydantic schema to select specific columns from the primary model (e.g., UserSchema).
733+
- `join_schema_to_select`: A Pydantic schema to select specific columns from the joined model (e.g., TierSchema).
734+
- `join_type`: pecifies the type of join operation to perform. Can be "left" for a left outer join or "inner" for an inner join. Default "left".
735+
- `kwargs`: Filters to apply to the primary query.
736+
737+
This method allows you to perform a join operation, selecting columns from both models, and retrieve a single record.
738+
739+
#### 5.6.9 Get Multi Joined
740+
Similarly, to retrieve multiple records with a join operation, you can use the get_multi_joined method. Here's how:
741+
742+
```python
743+
# Retrieve a list of objects with a join on another model (e.g., User and Tier).
744+
result = await crud_users.get_multi_joined(
745+
db=db, # The SQLAlchemy async session.
746+
join_model=Tier, # The model to join with (e.g., Tier).
747+
join_prefix="tier_", # Optional prefix for joined model columns.
748+
join_on=and_(User.tier_id == Tier.id, User.is_superuser == True), # Custom join condition.
749+
schema_to_select=UserSchema, # Pydantic schema for selecting User model columns.
750+
join_schema_to_select=TierSchema, # Pydantic schema for selecting Tier model columns.
751+
username="john_doe" # Additional filter parameters.
752+
)
753+
```
754+
755+
**Relevant Parameters:**
756+
- `join_model`: The model you want to join with (e.g., Tier).
757+
- `join_prefix`: Optional prefix to be added to all columns of the joined model. If None, no prefix is added.
758+
- `join_on`: SQLAlchemy Join object for specifying the ON clause of the join. If None, the join condition is auto-detected based on foreign keys.
759+
- `schema_to_select`: A Pydantic schema to select specific columns from the primary model (e.g., UserSchema).
760+
- `join_schema_to_select`: A Pydantic schema to select specific columns from the joined model (e.g., TierSchema).
761+
- `join_type`: pecifies the type of join operation to perform. Can be "left" for a left outer join or "inner" for an inner join. Default "left".
762+
- `kwargs`: Filters to apply to the primary query.
763+
- `offset`: The offset (number of records to skip) for pagination. Default 0.
764+
- `limit`: The limit (maximum number of records to return) for pagination. Default 100.
765+
- `kwargs`: Filters to apply to the primary query.
766+
767+
709768
#### More Efficient Selecting
710769
For the `get` and `get_multi` methods we have the option to define a `schema_to_select` attribute, which is what actually makes the queries more efficient. When you pass a `pydantic schema` (preferred) or a list of the names of the attributes in `schema_to_select` to the `get` or `get_multi` methods, only the attributes in the schema will be selected.
711770
```python

src/app/api/v1/rate_limits.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ async def patch_rate_limit(
116116
tier_id=db_tier["id"],
117117
path=values.path
118118
)
119+
if db_rate_limit_path is not None:
120+
raise HTTPException(status_code=404, detail="There is already a rate limit for this path")
119121

120122
db_rate_limit_name = await crud_rate_limits.exists(db=db)
123+
if db_rate_limit_path is not None:
124+
raise HTTPException(status_code=404, detail="There is already a rate limit with this name")
121125

122126
await crud_rate_limits.update(db=db, object=values, id=db_rate_limit["id"])
123127
return {"message": "Rate Limit updated"}

src/app/api/v1/users.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
from fastapi import Request
66
import fastapi
77

8-
from app.schemas.user import UserCreate, UserCreateInternal, UserUpdate, UserRead, UserTierUpdate
98
from app.api.dependencies import get_current_user, get_current_superuser
9+
from app.api.exceptions import privileges_exception
10+
from app.api.paginated import PaginatedListResponse, paginated_response, compute_offset
1011
from app.core.database import async_get_db
1112
from app.core.security import get_password_hash
1213
from app.crud.crud_users import crud_users
1314
from app.crud.crud_tier import crud_tiers
1415
from app.crud.crud_rate_limit import crud_rate_limits
15-
from app.api.exceptions import privileges_exception
16-
from app.api.paginated import PaginatedListResponse, paginated_response, compute_offset
16+
from app.models.tier import Tier
17+
from app.schemas.user import UserCreate, UserCreateInternal, UserUpdate, UserRead, UserTierUpdate
18+
from app.schemas.tier import TierRead
1719

1820
router = fastapi.APIRouter(tags=["users"])
1921

@@ -166,6 +168,32 @@ async def read_user_rate_limits(
166168
return db_user
167169

168170

171+
@router.get("/user/{username}/tier")
172+
async def read_user_tier(
173+
request: Request,
174+
username: str,
175+
db: Annotated[AsyncSession, Depends(async_get_db)]
176+
):
177+
db_user = await crud_users.get(db=db, username=username, schema_to_select=UserRead)
178+
if db_user is None:
179+
raise HTTPException(status_code=404, detail="User not found")
180+
181+
db_tier = await crud_tiers.exists(db=db, id=db_user["tier_id"])
182+
if not db_tier:
183+
raise HTTPException(status_code=404, detail="Tier not found")
184+
185+
joined = await crud_users.get_joined(
186+
db=db,
187+
join_model=Tier,
188+
join_prefix="tier_",
189+
schema_to_select=UserRead,
190+
join_schema_to_select=TierRead,
191+
username=username
192+
)
193+
194+
return joined
195+
196+
169197
@router.patch("/user/{username}/tier", dependencies=[Depends(get_current_superuser)])
170198
async def patch_user_tier(
171199
request: Request,
@@ -180,6 +208,6 @@ async def patch_user_tier(
180208
db_tier = await crud_tiers.get(db=db, id=values.tier_id)
181209
if db_tier is None:
182210
raise HTTPException(status_code=404, detail="Tier not found")
183-
211+
184212
await crud_users.update(db=db, object=values, username=username)
185213
return {"message": f"User {db_user['name']} Tier updated"}

0 commit comments

Comments
 (0)