Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,13 +607,14 @@ from app.schemas.user import UserCreateInternal, UserUpdate, UserUpdateInternal,
CRUDUser = CRUDBase[User, UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete]
crud_users = CRUDUser(User)
```

#### 5.6.1 Get
When actually using the crud in an endpoint, to get data you just pass the database connection and the attributes as kwargs:
```python
# Here I'm getting the first user with email == user.email (email is unique in this case)
user = await crud_users.get(db=db, email=user.email)
```

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

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

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

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

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

#### 5.6.7 Delete
To delete we have two options:
- db_delete: actually deletes the row from the database
- delete:
Expand All @@ -706,6 +712,59 @@ crud_users.delete(db=db, username="myusername")
crud_users.db_delete(db=db, username="myusername")
```

#### 5.6.8 Get Joined
To retrieve data with a join operation, you can use the get_joined method from your CRUD module. Here's how to do it:

```python
# Fetch a single record with a join on another model (e.g., User and Tier).
result = await crud_users.get_joined(
db=db, # The SQLAlchemy async session.
join_model=Tier, # The model to join with (e.g., Tier).
schema_to_select=UserSchema, # Pydantic schema for selecting User model columns (optional).
join_schema_to_select=TierSchema # Pydantic schema for selecting Tier model columns (optional).
)
```

**Relevant Parameters:**
- `join_model`: The model you want to join with (e.g., Tier).
- `join_prefix`: Optional prefix to be added to all columns of the joined model. If None, no prefix is added.
- `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.
- `schema_to_select`: A Pydantic schema to select specific columns from the primary model (e.g., UserSchema).
- `join_schema_to_select`: A Pydantic schema to select specific columns from the joined model (e.g., TierSchema).
- `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".
- `kwargs`: Filters to apply to the primary query.

This method allows you to perform a join operation, selecting columns from both models, and retrieve a single record.

#### 5.6.9 Get Multi Joined
Similarly, to retrieve multiple records with a join operation, you can use the get_multi_joined method. Here's how:

```python
# Retrieve a list of objects with a join on another model (e.g., User and Tier).
result = await crud_users.get_multi_joined(
db=db, # The SQLAlchemy async session.
join_model=Tier, # The model to join with (e.g., Tier).
join_prefix="tier_", # Optional prefix for joined model columns.
join_on=and_(User.tier_id == Tier.id, User.is_superuser == True), # Custom join condition.
schema_to_select=UserSchema, # Pydantic schema for selecting User model columns.
join_schema_to_select=TierSchema, # Pydantic schema for selecting Tier model columns.
username="john_doe" # Additional filter parameters.
)
```

**Relevant Parameters:**
- `join_model`: The model you want to join with (e.g., Tier).
- `join_prefix`: Optional prefix to be added to all columns of the joined model. If None, no prefix is added.
- `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.
- `schema_to_select`: A Pydantic schema to select specific columns from the primary model (e.g., UserSchema).
- `join_schema_to_select`: A Pydantic schema to select specific columns from the joined model (e.g., TierSchema).
- `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".
- `kwargs`: Filters to apply to the primary query.
- `offset`: The offset (number of records to skip) for pagination. Default 0.
- `limit`: The limit (maximum number of records to return) for pagination. Default 100.
- `kwargs`: Filters to apply to the primary query.


#### More Efficient Selecting
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.
```python
Expand Down
4 changes: 4 additions & 0 deletions src/app/api/v1/rate_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,12 @@ async def patch_rate_limit(
tier_id=db_tier["id"],
path=values.path
)
if db_rate_limit_path is not None:
raise HTTPException(status_code=404, detail="There is already a rate limit for this path")

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

await crud_rate_limits.update(db=db, object=values, id=db_rate_limit["id"])
return {"message": "Rate Limit updated"}
Expand Down
36 changes: 32 additions & 4 deletions src/app/api/v1/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
from fastapi import Request
import fastapi

from app.schemas.user import UserCreate, UserCreateInternal, UserUpdate, UserRead, UserTierUpdate
from app.api.dependencies import get_current_user, get_current_superuser
from app.api.exceptions import privileges_exception
from app.api.paginated import PaginatedListResponse, paginated_response, compute_offset
from app.core.database import async_get_db
from app.core.security import get_password_hash
from app.crud.crud_users import crud_users
from app.crud.crud_tier import crud_tiers
from app.crud.crud_rate_limit import crud_rate_limits
from app.api.exceptions import privileges_exception
from app.api.paginated import PaginatedListResponse, paginated_response, compute_offset
from app.models.tier import Tier
from app.schemas.user import UserCreate, UserCreateInternal, UserUpdate, UserRead, UserTierUpdate
from app.schemas.tier import TierRead

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

Expand Down Expand Up @@ -166,6 +168,32 @@ async def read_user_rate_limits(
return db_user


@router.get("/user/{username}/tier")
async def read_user_tier(
request: Request,
username: str,
db: Annotated[AsyncSession, Depends(async_get_db)]
):
db_user = await crud_users.get(db=db, username=username, schema_to_select=UserRead)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")

db_tier = await crud_tiers.exists(db=db, id=db_user["tier_id"])
if not db_tier:
raise HTTPException(status_code=404, detail="Tier not found")

joined = await crud_users.get_joined(
db=db,
join_model=Tier,
join_prefix="tier_",
schema_to_select=UserRead,
join_schema_to_select=TierRead,
username=username
)

return joined


@router.patch("/user/{username}/tier", dependencies=[Depends(get_current_superuser)])
async def patch_user_tier(
request: Request,
Expand All @@ -180,6 +208,6 @@ async def patch_user_tier(
db_tier = await crud_tiers.get(db=db, id=values.tier_id)
if db_tier is None:
raise HTTPException(status_code=404, detail="Tier not found")

await crud_users.update(db=db, object=values, username=username)
return {"message": f"User {db_user['name']} Tier updated"}
Loading