diff --git a/docs/advanced/crud.md b/docs/advanced/crud.md index 905a560..709a088 100644 --- a/docs/advanced/crud.md +++ b/docs/advanced/crud.md @@ -6,20 +6,14 @@ FastCRUD offers a flexible and powerful approach to handling CRUD operations in Note that when initializing `FastCRUD`, assuming you have a model like: -```python -from sqlalchemy import Boolean, Column, DateTime, Integer, String -from sqlalchemy.orm import DeclarativeBase - -class Base(DeclarativeBase): - pass - -class User(Base): - __tablename__ = "user" - id = Column(Integer, primary_key=True) - name = Column(String) - archived = Column(Boolean, default=False) - archived_at = Column(DateTime) -``` +???+ example "Simplified `user/model.py`" + + ```python + --8<-- + fastcrud/examples/user/model.py:imports + fastcrud/examples/user/model.py:model_common + --8<-- + ``` !!! WARNING @@ -32,25 +26,43 @@ You could just pass it to `FastCRUD`: ```python from fastcrud import FastCRUD -crud_user = FastCRUD(User) +user_crud = FastCRUD(User) ``` But you also may want a more robust typing, for that purpose, you may also pass the relevant pydantic schemas in the following way: +??? example "Simplified `user/schemas.py`" + + ```python + --8<-- + fastcrud/examples/user/schemas.py:imports + fastcrud/examples/user/schemas.py:createschema_common + + + fastcrud/examples/user/schemas.py:readschema_common + + + fastcrud/examples/user/schemas.py:updateschema_common + + + fastcrud/examples/user/schemas.py:deleteschema + --8<-- + ``` + ```python -from .models.user import User -from .schemas.user import UserCreate, UserUpdate, UserUpdateInternal, UserDelete +from .user.model import User +from .user.schemas import CreateUserSchema, ReadUserSchema, UpdateUserSchema, DeleteUserSchema # Just pass None if you don't have one of the schemas -CRUDUser = FastCRUD[User, UserCreate, UserUpdate, UserUpdateInternal, UserDelete] +UserCRUD = FastCRUD[User, CreateUserSchema, UpdateUserSchema, None, DeleteUserSchema] ``` -Then you can initialize `CRUDUser` like you would any `FastCRUD` instance, but with the relevant types: +Then you can initialize `UserCRUD` like you would any `FastCRUD` instance, but with the relevant types: ```python -from .models.user import User +from .user.model import User -crud_user = CRUDUser(User) +user_crud = UserCRUD(User) ``` ## Allow Multiple Updates and Deletes @@ -284,7 +296,108 @@ items = await item_crud.upsert_multi( Consider a scenario where you want to retrieve users along with their associated tier and department information. Here's how you can achieve this using `get_multi_joined`. -Start by creating a list of the multiple models to be joined: +Start by creating the models and schemas, followed by a description of how they're to be joined: + +??? example "Models and Schemas" + + ??? example "`tier/model.py`" + + ```python + --8<-- + fastcrud/examples/tier/model.py:imports + fastcrud/examples/tier/model.py:model + --8<-- + ``` + + ??? example "`tier/schemas.py`" + + ```python + --8<-- + fastcrud/examples/tier/schemas.py:imports + fastcrud/examples/tier/schemas.py:readschema + --8<-- + ``` + + ??? example "`department/model.py`" + + ```python + --8<-- + fastcrud/examples/department/model.py:imports + fastcrud/examples/department/model.py:model + --8<-- + ``` + + ??? example "`department/schemas.py`" + + ```python + --8<-- + fastcrud/examples/department/schemas.py:imports + fastcrud/examples/department/schemas.py:readschema + --8<-- + ``` + + ??? example "`user/model.py`" + + ```python + --8<-- + fastcrud/examples/user/model.py:imports + fastcrud/examples/user/model.py:model + --8<-- + ``` + + ??? example "`user/schemas.py`" + + ```python + --8<-- + fastcrud/examples/user/schemas.py:imports + fastcrud/examples/user/schemas.py:createschema + fastcrud/examples/user/schemas.py:readschema + fastcrud/examples/user/schemas.py:updateschema + fastcrud/examples/user/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`story/model.py`" + + ```python + --8<-- + fastcrud/examples/story/model.py:imports + fastcrud/examples/story/model.py:model + --8<-- + ``` + + ??? example "`story/schemas.py`" + + ```python + --8<-- + fastcrud/examples/story/schemas.py:imports + fastcrud/examples/story/schemas.py:createschema + fastcrud/examples/story/schemas.py:readschema + fastcrud/examples/story/schemas.py:updateschema + fastcrud/examples/story/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`task/model.py`" + + ```python + --8<-- + fastcrud/examples/task/model.py:imports + fastcrud/examples/task/model.py:model + --8<-- + ``` + + ??? example "`task/schemas.py`" + + ```python + --8<-- + fastcrud/examples/task/schemas.py:imports + fastcrud/examples/task/schemas.py:createschema + fastcrud/examples/task/schemas.py:readschema + fastcrud/examples/task/schemas.py:updateschema + fastcrud/examples/task/schemas.py:deleteschema + --8<-- + ``` ```python hl_lines="1 3-10 12-19" title="Join Configurations" from fastcrud import JoinConfig @@ -294,7 +407,7 @@ joins_config = [ model=Tier, join_on=User.tier_id == Tier.id, join_prefix="tier_", - schema_to_select=TierSchema, + schema_to_select=ReadTierSchema, join_type="left", ), @@ -302,14 +415,14 @@ joins_config = [ model=Department, join_on=User.department_id == Department.id, join_prefix="dept_", - schema_to_select=DepartmentSchema, + schema_to_select=ReadDepartmentSchema, join_type="inner", ), ] users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, offset=0, limit=10, sort_columns='username', @@ -329,7 +442,7 @@ joins_config = [ users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, offset=0, limit=10, sort_columns='username', @@ -380,44 +493,44 @@ For both `get_joined` and `get_multi_joined` methods, when you need to join the #### Example: Joining the Same Model Multiple Times -Consider a task management application where tasks have both an owner and an assigned user, represented by the same `UserModel`. To fetch tasks with details of both users, we use aliases to join the `UserModel` twice, distinguishing between owners and assigned users. +Consider a task management application where tasks have both an owner and an assigned user, represented by the same `User` model. To fetch tasks with details of both users, we use aliases to join the `User` model twice, distinguishing between owners and assigned users. Let's start by creating the aliases and passing them to the join configuration. Don't forget to use the alias for `join_on`: ```python hl_lines="4-5 11 15 19 23" title="Join Configurations with Aliases" from fastcrud import FastCRUD, JoinConfig, aliased -# Create aliases for UserModel to distinguish between the owner and the assigned user -owner_alias = aliased(UserModel, name="owner") -assigned_user_alias = aliased(UserModel, name="assigned_user") +# Create aliases for User to distinguish between the owner and the assigned user +owner_alias = aliased(User, name="owner") +assigned_user_alias = aliased(User, name="assigned_user") # Configure joins with aliases joins_config = [ JoinConfig( - model=UserModel, + model=User, join_on=Task.owner_id == owner_alias.id, join_prefix="owner_", - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_type="inner", alias=owner_alias, # Pass the aliased class instance ), JoinConfig( - model=UserModel, + model=User, join_on=Task.assigned_user_id == assigned_user_alias.id, join_prefix="assigned_", - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_type="inner", alias=assigned_user_alias, # Pass the aliased class instance ), ] -# Initialize your FastCRUD instance for TaskModel -task_crud = FastCRUD(TaskModel) +# Initialize your FastCRUD instance for Task +task_crud = FastCRUD(Task) # Fetch tasks with joined user details tasks = await task_crud.get_multi_joined( db=session, - schema_to_select=TaskSchema, + schema_to_select=ReadTaskSchema, offset=0, limit=10, joins_config=joins_config, @@ -436,20 +549,20 @@ joins_config = [ ... ] -# Initialize your FastCRUD instance for TaskModel -task_crud = FastCRUD(TaskModel) +# Initialize your FastCRUD instance for Task +task_crud = FastCRUD(Task) # Fetch tasks with joined user details tasks = await task_crud.get_multi_joined( db=session, - schema_to_select=TaskSchema, + schema_to_select=ReadTaskSchema, offset=0, limit=10, joins_config=joins_config, ) ``` -In this example, `owner_alias` and `assigned_user_alias` are created from `UserModel` to distinguish between the task's owner and the assigned user within the task management system. By using aliases, you can join the same model multiple times for different purposes in your queries, enhancing expressiveness and eliminating ambiguity. +In this example, `owner_alias` and `assigned_user_alias` are created from `User` to distinguish between the task's owner and the assigned user within the task management system. By using aliases, you can join the same model multiple times for different purposes in your queries, enhancing expressiveness and eliminating ambiguity. ### Many-to-Many Relationships with `get_multi_joined` diff --git a/docs/advanced/joins.md b/docs/advanced/joins.md index 07f7778..5aa9399 100644 --- a/docs/advanced/joins.md +++ b/docs/advanced/joins.md @@ -21,6 +21,53 @@ FastCRUD simplifies CRUD operations while offering capabilities for handling com ## Applying Joins in FastCRUD Methods +??? example "Models - `Tier`, `Department`, `User`, `Story`, `Task`" + + ??? example "`tier/model.py`" + + ```python + --8<-- + fastcrud/examples/tier/model.py:imports + fastcrud/examples/tier/model.py:model + --8<-- + ``` + + ??? example "`department/model.py`" + + ```python + --8<-- + fastcrud/examples/department/model.py:imports + fastcrud/examples/department/model.py:model + --8<-- + ``` + + ??? example "`user/model.py`" + + ```python + --8<-- + fastcrud/examples/user/model.py:imports + fastcrud/examples/user/model.py:model + --8<-- + ``` + + ??? example "`story/model.py`" + + ```python + --8<-- + fastcrud/examples/story/model.py:imports + fastcrud/examples/story/model.py:model + --8<-- + ``` + + ??? example "`task/model.py`" + + ```python + --8<-- + fastcrud/examples/task/model.py:imports + fastcrud/examples/task/model.py:model + --8<-- + ``` + ### The `count` Method with Joins The `count` method can be enhanced with join operations to perform complex aggregate queries. While `count` primarily returns the number of records matching a given condition, introducing joins allows for counting records across related models based on specific relationships and conditions. @@ -31,6 +78,9 @@ For join requirements, the `count` method can be invoked with join parameters pa ```python from fastcrud import JoinConfig + +task_crud = FastCRUD(Task) + # Count the number of tasks assigned to users in a specific department task_count = await task_crud.count( db=db, @@ -67,11 +117,11 @@ For simpler join requirements, FastCRUD allows specifying join parameters direct #### Examples of Simple Joining ```python -# Fetch tasks with user details, specifying a left join +# Fetch tasks with assigned user details, specifying a left join tasks_with_users = await task_crud.get_joined( db=db, join_model=User, - join_on=Task.user_id == User.id, + join_on=Task.assigned_user_id == User.id, join_type="left", ) ``` @@ -80,25 +130,10 @@ tasks_with_users = await task_crud.get_joined( Note that by default, `FastCRUD` joins all the data and returns it in a single dictionary. -Let's define two tables: - -```python -class User(Base): - __tablename__ = "user" - id = Column(Integer, primary_key=True) - name = Column(String) - tier_id = Column(Integer, ForeignKey("tier.id")) - - -class Tier(Base): - __tablename__ = "tier" - id = Column(Integer, primary_key=True) - name = Column(String, unique=True) -``` - -And join them with `FastCRUD`: +Let's take two of the tables from above and join them with `FastCRUD`: ```python +user_crud = FastCRUD(User) user_tier = await user_crud.get_joined( db=db, join_model=Tier, @@ -111,12 +146,12 @@ user_tier = await user_crud.get_joined( We'll get: -```javascript +```json { "id": 1, "name": "Example", "tier_id": 1, - "tier_name": "Free", + "tier_name": "Free" } ``` @@ -136,13 +171,13 @@ user_tier = await user_crud.get_joined( And you will get: -```javascript +```json { "id": 1, "name": "Example", "tier": { "id": 1, - "name": "Free", + "name": "Free" } } ``` @@ -159,14 +194,23 @@ When dealing with more complex join conditions, such as multiple joins, self-ref Example: +??? example "`user/schemas.py` Excerpt" + + ```python + --8<-- + fastcrud/examples/user/schemas.py:readschema + --8<-- + ``` + ```python -# Fetch users with details from related departments and roles, using aliases for self-referential joins +# Fetch users with details from related departments and tiers, using aliases for self-referential joins from fastcrud import aliased + manager_alias = aliased(User) users = await user_crud.get_multi_joined( db=db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, joins_config=[ JoinConfig( model=Department, @@ -174,9 +218,9 @@ users = await user_crud.get_multi_joined( join_prefix="dept_", ), JoinConfig( - model=Role, - join_on=User.role_id == Role.id, - join_prefix="role_", + model=Tier, + join_on=User.tier_id == Tier.id, + join_prefix="tier_", ), JoinConfig( model=User, @@ -188,7 +232,6 @@ users = await user_crud.get_multi_joined( ) ``` - ### Handling One-to-One and One-to-Many Joins in FastCRUD FastCRUD provides flexibility in handling one-to-one and one-to-many relationships through `get_joined` and `get_multi_joined` methods, along with the ability to specify how joined data should be structured using both the `relationship_type` (default `"one-to-one"`) and the `nest_joins` (default `False`) parameters. @@ -200,24 +243,10 @@ FastCRUD provides flexibility in handling one-to-one and one-to-many relationshi ##### Example -Let's define two tables: - -```python -class User(Base): - __tablename__ = "user" - id = Column(Integer, primary_key=True) - name = Column(String) - tier_id = Column(Integer, ForeignKey("tier.id")) - -class Tier(Base): - __tablename__ = "tier" - id = Column(Integer, primary_key=True) - name = Column(String, unique=True) -``` - -Fetch a user and their tier: +Let's take two of the tables from above and join them with `FastCRUD`: ```python +user_crud = FastCRUD(User) user_tier = await user_crud.get_joined( db=db, join_model=Tier, diff --git a/fastcrud/crud/fast_crud.py b/fastcrud/crud/fast_crud.py index 2fb1399..6ddf2c3 100644 --- a/fastcrud/crud/fast_crud.py +++ b/fastcrud/crud/fast_crud.py @@ -151,25 +151,128 @@ class FastCRUD( --8<-- ``` + --- + + ??? example "`tier/model.py`" + + ```python + --8<-- + fastcrud/examples/tier/model.py:imports + fastcrud/examples/tier/model.py:model + --8<-- + ``` + + ??? example "`tier/schemas.py`" + + ```python + --8<-- + fastcrud/examples/tier/schemas.py:imports + fastcrud/examples/tier/schemas.py:readschema + --8<-- + ``` + + ??? example "`department/model.py`" + + ```python + --8<-- + fastcrud/examples/department/model.py:imports + fastcrud/examples/department/model.py:model + --8<-- + ``` + + ??? example "`department/schemas.py`" + + ```python + --8<-- + fastcrud/examples/department/schemas.py:imports + fastcrud/examples/department/schemas.py:readschema + --8<-- + ``` + + ??? example "`user/model.py`" + + ```python + --8<-- + fastcrud/examples/user/model.py:imports + fastcrud/examples/user/model.py:model + --8<-- + ``` + + ??? example "`user/schemas.py`" + + ```python + --8<-- + fastcrud/examples/user/schemas.py:imports + fastcrud/examples/user/schemas.py:createschema + fastcrud/examples/user/schemas.py:readschema + fastcrud/examples/user/schemas.py:updateschema + fastcrud/examples/user/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`story/model.py`" + + ```python + --8<-- + fastcrud/examples/story/model.py:imports + fastcrud/examples/story/model.py:model + --8<-- + ``` + + ??? example "`story/schemas.py`" + + ```python + --8<-- + fastcrud/examples/story/schemas.py:imports + fastcrud/examples/story/schemas.py:createschema + fastcrud/examples/story/schemas.py:readschema + fastcrud/examples/story/schemas.py:updateschema + fastcrud/examples/story/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`task/model.py`" + + ```python + --8<-- + fastcrud/examples/task/model.py:imports + fastcrud/examples/task/model.py:model + --8<-- + ``` + + ??? example "`task/schemas.py`" + + ```python + --8<-- + fastcrud/examples/task/schemas.py:imports + fastcrud/examples/task/schemas.py:createschema + fastcrud/examples/task/schemas.py:readschema + fastcrud/examples/task/schemas.py:updateschema + fastcrud/examples/task/schemas.py:deleteschema + --8<-- + ``` + Example 1: Basic Usage ---------------------- + Create a FastCRUD instance for a `User` model and perform basic CRUD operations. + ```python # Assuming you have a User model (either SQLAlchemy or SQLModel) # pydantic schemas for creation, update and deletion and an async session `db` - CRUDUser = FastCRUD[User, UserCreateInternal, UserUpdate, UserUpdateInternal, UserDelete] - user_crud = CRUDUser(User) + UserCRUD = FastCRUD[User, CreateUserSchema, UpdateUserSchema, None, DeleteUserSchema] + user_crud = UserCRUD(User) - # If you don't care about typing, you can also just ignore the CRUDUser part + # If you don't care about typing, you can also just ignore the UserCRUD part # Straight up define user_crud with FastCRUD user_crud = FastCRUD(User) # Create a new user - new_user = await user_crud.create(db, UserCreateSchema(name="Alice")) + new_user = await user_crud.create(db, CreateUserSchema(name="Alice")) # Read a user user = await user_crud.get(db, id=new_user.id) # Update a user - await user_crud.update(db, UserUpdateSchema(email="alice@example.com"), id=new_user.id) + await user_crud.update(db, UpdateUserSchema(email="alice@example.com"), id=new_user.id) # Delete a user await user_crud.delete(db, id=new_user.id) ``` @@ -230,6 +333,7 @@ class Comment(Base): Example 5: Dynamic Filtering and Counting ----------------------------------------- Dynamically filter records based on various criteria and count the results. + ```python task_crud = FastCRUD(Task) completed_tasks = await task_crud.get_multi( @@ -244,8 +348,19 @@ class Comment(Base): Example 6: Using Custom Column Names for Soft Delete ---------------------------------------------------- + If your model uses different column names for indicating a soft delete and its timestamp, you can specify these when creating the `FastCRUD` instance. + ```python + --8<-- + fastcrud/examples/user/model.py:model_common + --8<-- + ... + --8<-- + fastcrud/examples/user/model.py:model_archived + --8<-- + + custom_user_crud = FastCRUD( User, is_deleted_column="archived", @@ -497,9 +612,10 @@ async def select( Examples: Selecting specific columns with filtering and sorting: + ```python - stmt = await crud.select( - schema_to_select=UserReadSchema, + stmt = await user_crud.select( + schema_to_select=ReadUserSchema, sort_columns=['age', 'name'], sort_orders=['asc', 'desc'], age__gt=18, @@ -507,18 +623,21 @@ async def select( ``` Creating a statement to select all users without any filters: + ```python - stmt = await crud.select() + stmt = await user_crud.select() ``` Selecting users with a specific `role`, ordered by `name`: + ```python - stmt = await crud.select( + stmt = await user_crud.select( schema_to_select=UserReadSchema, sort_columns='name', role='admin', ) ``` + Note: This method does not execute the generated SQL statement. Use `db.execute(stmt)` to run the query and fetch results. @@ -563,23 +682,27 @@ async def get( Examples: Fetch a user by ID: + ```python - user = await crud.get(db, id=1) + user = await user_crud.get(db, id=1) ``` Fetch a user with an age greater than 30: + ```python - user = await crud.get(db, age__gt=30) + user = await user_crud.get(db, age__gt=30) ``` Fetch a user with a registration date before Jan 1, 2020: + ```python - user = await crud.get(db, registration_date__lt=datetime(2020, 1, 1)) + user = await user_crud.get(db, registration_date__lt=datetime(2020, 1, 1)) ``` Fetch a user not equal to a specific username: + ```python - user = await crud.get(db, username__ne='admin') + user = await user_crud.get(db, username__ne='admin') ``` """ stmt = await self.select(schema_to_select=schema_to_select, **kwargs) @@ -781,23 +904,27 @@ async def exists(self, db: AsyncSession, **kwargs: Any) -> bool: Examples: Check if a user with a specific ID exists: + ```python - exists = await crud.exists(db, id=1) + exists = await user_crud.exists(db, id=1) ``` Check if any user is older than 30: + ```python - exists = await crud.exists(db, age__gt=30) + exists = await user_crud.exists(db, age__gt=30) ``` Check if any user was registered before Jan 1, 2020: + ```python - exists = await crud.exists(db, registration_date__lt=datetime(2020, 1, 1)) + exists = await user_crud.exists(db, registration_date__lt=datetime(2020, 1, 1)) ``` Check if a username other than `admin` exists: + ```python - exists = await crud.exists(db, username__ne='admin') + exists = await user_crud.exists(db, username__ne='admin') ``` """ filters = self._parse_filters(**kwargs) @@ -829,18 +956,21 @@ async def count( Examples: Count users by ID: + ```python - count = await crud.count(db, id=1) + count = await user_crud.count(db, id=1) ``` Count users older than 30: + ```python - count = await crud.count(db, age__gt=30) + count = await user_crud.count(db, age__gt=30) ``` Count users with a username other than `admin`: + ```python - count = await crud.count(db, username__ne='admin') + count = await user_crud.count(db, username__ne='admin') ``` Count projects with at least one participant (many-to-many relationship): @@ -959,8 +1089,9 @@ async def get_multi( Examples: Fetch the first 10 users: + ```python - users = await crud.get_multi( + users = await user_crud.get_multi( db, 0, 10, @@ -968,8 +1099,9 @@ async def get_multi( ``` Fetch next 10 users with sorted by username: + ```python - users = await crud.get_multi( + users = await user_crud.get_multi( db, 10, 10, @@ -979,8 +1111,9 @@ async def get_multi( ``` Fetch 10 users older than 30, sorted by age in descending order: + ```python - get_multi( + users = await user_crud.get_multi( db, offset=0, limit=10, @@ -992,7 +1125,7 @@ async def get_multi( Fetch 10 users with a registration date before Jan 1, 2020: ```python - get_multi( + users = await user_crud.get_multi( db, offset=0, limit=10, @@ -1001,20 +1134,22 @@ async def get_multi( ``` Fetch 10 users with a username other than `admin`, returning as model instances (ensure appropriate schema is passed): + ```python - get_multi( + users = await user_crud.get_multi( db, offset=0, limit=10, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, return_as_model=True, username__ne='admin', ) ``` Fetch users with filtering and multiple column sorting: + ```python - users = await crud.get_multi( + users = await user_crud.get_multi( db, 0, 10, @@ -1111,82 +1246,90 @@ async def get_joined( Examples: Simple example: Joining `User` and `Tier` models without explicitly providing `join_on` + ```python - result = await crud_user.get_joined( + result = await user_crud.get_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, ) ``` Fetch a user and their associated tier, filtering by user ID: + ```python - result = await crud_user.get_joined( + result = await user_crud.get_joined( db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, id=1, ) ``` Fetch a user and their associated tier, where the user's age is greater than 30: + ```python - result = await crud_user.get_joined( + result = await user_crud.get_joined( db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, age__gt=30, ) ``` Fetch a user and their associated tier, excluding users with the `admin` username: + ```python - result = await crud_user.get_joined( + result = await user_crud.get_joined( db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, username__ne='admin', ) ``` Complex example: Joining with a custom join condition, additional filter parameters, and a prefix + ```python from sqlalchemy import and_ - result = await crud_user.get_joined( + result = await user_crud.get_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, join_on=and_(User.tier_id == Tier.id, User.is_superuser == True), join_prefix="tier_", - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, username="john_doe", ) ``` Example of using `joins_config` for multiple joins: + ```python from fastcrud import JoinConfig - result = await crud_user.get_joined( + # Using same User/Tier/Department models/schemas as above. + + result = await user_crud.get_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, joins_config=[ JoinConfig( model=Tier, join_on=User.tier_id == Tier.id, join_prefix="tier_", - schema_to_select=TierSchema, + schema_to_select=ReadTierSchema, join_type="left", ), JoinConfig( model=Department, join_on=User.department_id == Department.id, join_prefix="dept_", - schema_to_select=DepartmentSchema, + schema_to_select=ReadDepartmentSchema, join_type="inner", ), ], @@ -1246,25 +1389,26 @@ async def get_joined( ``` Example of using `joins_config` for multiple joins with nested joins enabled: + ```python from fastcrud import JoinConfig - result = await crud_user.get_joined( + result = await user_crud.get_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, joins_config=[ JoinConfig( model=Tier, join_on=User.tier_id == Tier.id, join_prefix="tier_", - schema_to_select=TierSchema, + schema_to_select=ReadTierSchema, join_type="left", ), JoinConfig( model=Department, join_on=User.department_id == Department.id, join_prefix="dept_", - schema_to_select=DepartmentSchema, + schema_to_select=ReadDepartmentSchema, join_type="inner", ), ], @@ -1423,25 +1567,27 @@ async def get_multi_joined( Examples: Fetching multiple `User` records joined with `Tier` records, using left join, returning raw data: + ```python - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, join_prefix="tier_", - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, offset=0, limit=10, ) ``` Fetch users joined with their tiers, sorted by username, where user's age is greater than 30: + ```python - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, sort_columns='username', sort_orders='asc', age__gt=30, @@ -1449,25 +1595,27 @@ async def get_multi_joined( ``` Fetch users joined with their tiers, excluding users with `admin` username, returning as model instances: + ```python - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, return_as_model=True, username__ne='admin', ) ``` Fetching and sorting by username in descending order, returning as Pydantic model: + ```python - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, join_prefix="tier_", - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, offset=0, limit=10, sort_columns=['username'], @@ -1477,14 +1625,15 @@ async def get_multi_joined( ``` Fetching with complex conditions and custom join, returning as Pydantic model: + ```python - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_model=Tier, join_on=User.tier_id == Tier.id, join_prefix="tier_", - join_schema_to_select=TierSchema, + join_schema_to_select=ReadTierSchema, offset=0, limit=10, return_as_model=True, @@ -1493,25 +1642,26 @@ async def get_multi_joined( ``` Example using `joins_config` for multiple joins: + ```python from fastcrud import JoinConfig - users = await crud_user.get_multi_joined( + users = await user_crud.get_multi_joined( db=session, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, joins_config=[ JoinConfig( model=Tier, join_on=User.tier_id == Tier.id, join_prefix="tier_", - schema_to_select=TierSchema, + schema_to_select=ReadTierSchema, join_type="left", ), JoinConfig( model=Department, join_on=User.department_id == Department.id, join_prefix="dept_", - schema_to_select=DepartmentSchema, + schema_to_select=ReadDepartmentSchema, join_type="inner", ), ], @@ -1582,24 +1732,26 @@ async def get_multi_joined( ) ``` - Fetching a list of projects, each with nested details of associated tasks and task creators, using nested joins: + Fetching a list of stories, each with nested details of associated tasks and task creators, using nested joins: + ```python - projects = await crud.get_multi_joined( + story_crud = FastCRUD(Story) + stories = await story_crud.get_multi_joined( db=session, - schema_to_select=ProjectSchema, + schema_to_select=ReadStorySchema, joins_config=[ JoinConfig( model=Task, - join_on=Project.id == Task.project_id, + join_on=Story.id == Task.story_id, join_prefix="task_", - schema_to_select=TaskSchema, + schema_to_select=ReadTaskSchema, join_type="left", ), JoinConfig( model=User, join_on=Task.creator_id == User.id, join_prefix="creator_", - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, join_type="left", alias=aliased(User, name="task_creator"), ), @@ -1607,7 +1759,7 @@ async def get_multi_joined( nest_joins=True, offset=0, limit=5, - sort_columns='project_name', + sort_columns='name', sort_orders='asc', ) ``` @@ -1787,29 +1939,31 @@ async def get_multi_by_cursor( A dictionary containing the fetched rows under `"data"` key and the next cursor value under `"next_cursor"`. Examples: - Fetch the first set of records (e.g., the first page in an infinite scrolling scenario) + Fetch the first set of records (e.g., the first page in an infinite scrolling scenario): + ```python - first_page = await crud.get_multi_by_cursor( + first_page = await user_crud.get_multi_by_cursor( db, limit=10, - sort_column='created_at', + sort_column='registration_date', sort_order='desc', ) # Fetch the next set of records using the cursor from the first page next_cursor = first_page['next_cursor'] - second_page = await crud.get_multi_by_cursor( + second_page = await user_crud.get_multi_by_cursor( db, cursor=next_cursor, limit=10, - sort_column='created_at', + sort_column='registration_date', sort_order='desc', ) ``` Fetch records with age greater than 30 using cursor-based pagination: + ```python - first_page = await crud.get_multi_by_cursor( + first_page = await user_crud.get_multi_by_cursor( db, limit=10, sort_column='age', @@ -1819,8 +1973,9 @@ async def get_multi_by_cursor( ``` Fetch records excluding a specific username using cursor-based pagination: + ```python - first_page = await crud.get_multi_by_cursor( + first_page = await user_crud.get_multi_by_cursor( db, limit=10, sort_column='username', @@ -1905,21 +2060,24 @@ async def update( Examples: Update a user's email based on their ID: + ```python await user_crud.update(db, {'email': 'new_email@example.com'}, id=1) ``` - Update users' statuses to `"inactive"` where age is greater than 30 and allow updates to multiple records: + Update users to be inactive where age is greater than 30 and allow updates to multiple records: + ```python await user_crud.update( db, - {'status': 'inactive'}, + {'is_active': False}, allow_multiple=True, age__gt=30, ) ``` Update a user's username excluding specific user ID and prevent multiple updates: + ```python await user_crud.update( db, @@ -1930,11 +2088,12 @@ async def update( ``` Update a user's email and return the updated record as a Pydantic model instance: + ```python - await user_crud.update( + user = await user_crud.update( db, {'email': 'new_email@example.com'}, - schema_to_select=UserSchema, + schema_to_select=ReadUserSchema, return_as_model=True, id=1, ) @@ -1942,7 +2101,7 @@ async def update( Update a user's email and return the updated record as a dictionary: ```python - await user_crud.update( + user = await user_crud.update( db, {'email': 'new_email@example.com'}, return_columns=['id', 'email'], @@ -2067,11 +2226,13 @@ async def db_delete( Examples: Delete a user based on their ID: + ```python await user_crud.db_delete(db, id=1) ``` Delete users older than 30 years and allow deletion of multiple records: + ```python await user_crud.db_delete( db, @@ -2081,6 +2242,7 @@ async def db_delete( ``` Delete a user with a specific username, ensuring only one record is deleted: + ```python await user_crud.db_delete( db, @@ -2129,11 +2291,13 @@ async def delete( Examples: Soft delete a specific user by ID: + ```python await user_crud.delete(db, id=1) ``` - Hard delete users with account creation dates before 2020, allowing deletion of multiple records: + Soft delete users with account registration dates before 2020, allowing deletion of multiple records: + ```python await user_crud.delete( db, @@ -2143,6 +2307,7 @@ async def delete( ``` Soft delete a user with a specific email, ensuring only one record is deleted: + ```python await user_crud.delete( db, diff --git a/fastcrud/endpoint/crud_router.py b/fastcrud/endpoint/crud_router.py index 8e0a8c1..fa6010c 100644 --- a/fastcrud/endpoint/crud_router.py +++ b/fastcrud/endpoint/crud_router.py @@ -169,6 +169,107 @@ def crud_router( --8<-- ``` + --- + + ??? example "`tier/model.py`" + + ```python + --8<-- + fastcrud/examples/tier/model.py:imports + fastcrud/examples/tier/model.py:model + --8<-- + ``` + + ??? example "`tier/schemas.py`" + + ```python + --8<-- + fastcrud/examples/tier/schemas.py:imports + fastcrud/examples/tier/schemas.py:readschema + --8<-- + ``` + + ??? example "`department/model.py`" + + ```python + --8<-- + fastcrud/examples/department/model.py:imports + fastcrud/examples/department/model.py:model + --8<-- + ``` + + ??? example "`department/schemas.py`" + + ```python + --8<-- + fastcrud/examples/department/schemas.py:imports + fastcrud/examples/department/schemas.py:readschema + --8<-- + ``` + + ??? example "`user/model.py`" + + ```python + --8<-- + fastcrud/examples/user/model.py:imports + fastcrud/examples/user/model.py:model + --8<-- + ``` + + ??? example "`user/schemas.py`" + + ```python + --8<-- + fastcrud/examples/user/schemas.py:imports + fastcrud/examples/user/schemas.py:createschema + fastcrud/examples/user/schemas.py:readschema + fastcrud/examples/user/schemas.py:updateschema + fastcrud/examples/user/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`story/model.py`" + + ```python + --8<-- + fastcrud/examples/story/model.py:imports + fastcrud/examples/story/model.py:model + --8<-- + ``` + + ??? example "`story/schemas.py`" + + ```python + --8<-- + fastcrud/examples/story/schemas.py:imports + fastcrud/examples/story/schemas.py:createschema + fastcrud/examples/story/schemas.py:readschema + fastcrud/examples/story/schemas.py:updateschema + fastcrud/examples/story/schemas.py:deleteschema + --8<-- + ``` + + ??? example "`task/model.py`" + + ```python + --8<-- + fastcrud/examples/task/model.py:imports + fastcrud/examples/task/model.py:model + --8<-- + ``` + + ??? example "`task/schemas.py`" + + ```python + --8<-- + fastcrud/examples/task/schemas.py:imports + fastcrud/examples/task/schemas.py:createschema + fastcrud/examples/task/schemas.py:readschema + fastcrud/examples/task/schemas.py:updateschema + fastcrud/examples/task/schemas.py:deleteschema + --8<-- + ``` + Basic Setup: ```python @@ -183,14 +284,15 @@ def crud_router( ``` With Custom Dependencies: + ```python def get_current_user(token: str = Depends(oauth2_scheme)): # Implement user retrieval logic return ... - router = crud_router( + user_router = crud_router( session=async_session, - model=UserModel, + model=User, create_schema=CreateUserSchema, update_schema=UpdateUserSchema, read_deps=[get_current_user], @@ -339,10 +441,11 @@ async def add_routes_to_router(self, ...): ``` Customizing Endpoint Names: + ```python - router = crud_router( + task_router = crud_router( session=async_session, - model=TaskModel, + model=Task, create_schema=CreateTaskSchema, update_schema=UpdateTaskSchema, path="/tasks", diff --git a/fastcrud/examples/department/model.py b/fastcrud/examples/department/model.py new file mode 100644 index 0000000..5f4d418 --- /dev/null +++ b/fastcrud/examples/department/model.py @@ -0,0 +1,20 @@ +# --8<-- [start:imports] +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +# --8<-- [end:imports] + + +# --8<-- [start:model] +class Department(Base): + __tablename__ = "department" + id = Column(Integer, primary_key=True) + name = Column(String) + + +# --8<-- [end:model] diff --git a/fastcrud/examples/department/schemas.py b/fastcrud/examples/department/schemas.py new file mode 100644 index 0000000..d344291 --- /dev/null +++ b/fastcrud/examples/department/schemas.py @@ -0,0 +1,32 @@ +# --8<-- [start:imports] +from pydantic import BaseModel + + +# --8<-- [end:imports] +# --8<-- [start:schemas] +# --8<-- [start:createschema] +class CreateDepartmentSchema(BaseModel): + name: str | None = None + + +# --8<-- [end:createschema] +# --8<-- [start:readschema] +class ReadDepartmentSchema(BaseModel): + id: int + name: str | None = None + + +# --8<-- [end:readschema] +# --8<-- [start:updateschema] +class UpdateDepartmentSchema(BaseModel): + name: str | None = None + + +# --8<-- [end:updateschema] +# --8<-- [start:deleteschema] +class DeleteDepartmentSchema(BaseModel): + pass + + +# --8<-- [end:deleteschema] +# --8<-- [end:schemas] diff --git a/fastcrud/examples/story/model.py b/fastcrud/examples/story/model.py new file mode 100644 index 0000000..1c5a8a0 --- /dev/null +++ b/fastcrud/examples/story/model.py @@ -0,0 +1,20 @@ +# --8<-- [start:imports] +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +# --8<-- [end:imports] + + +# --8<-- [start:model] +class Story(Base): + __tablename__ = "story" + id = Column(Integer, primary_key=True) + name = Column(String) + + +# --8<-- [end:model] diff --git a/fastcrud/examples/story/schemas.py b/fastcrud/examples/story/schemas.py new file mode 100644 index 0000000..4793382 --- /dev/null +++ b/fastcrud/examples/story/schemas.py @@ -0,0 +1,32 @@ +# --8<-- [start:imports] +from pydantic import BaseModel + + +# --8<-- [end:imports] +# --8<-- [start:schemas] +# --8<-- [start:createschema] +class CreateStorySchema(BaseModel): + name: str | None = None + + +# --8<-- [end:createschema] +# --8<-- [start:readschema] +class ReadStorySchema(BaseModel): + id: int + name: str | None = None + + +# --8<-- [end:readschema] +# --8<-- [start:updateschema] +class UpdateStorySchema(BaseModel): + name: str | None = None + + +# --8<-- [end:updateschema] +# --8<-- [start:deleteschema] +class DeleteStorySchema(BaseModel): + pass + + +# --8<-- [end:deleteschema] +# --8<-- [end:schemas] diff --git a/fastcrud/examples/task/model.py b/fastcrud/examples/task/model.py new file mode 100644 index 0000000..61ad32e --- /dev/null +++ b/fastcrud/examples/task/model.py @@ -0,0 +1,25 @@ +# --8<-- [start:imports] +from sqlalchemy import Column, ForeignKey, Integer, String +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +# --8<-- [end:imports] + + +# --8<-- [start:model] +class Task(Base): + __tablename__ = "task" + id = Column(Integer, primary_key=True) + creator_id = Column(Integer, ForeignKey("user.id")) + owner_id = Column(Integer, ForeignKey("user.id")) + assigned_user_id = Column(Integer, ForeignKey("user.id")) + story_id = Column(Integer, ForeignKey("story.id")) + status = Column(String) + priority = Column(String) + + +# --8<-- [end:model] diff --git a/fastcrud/examples/task/schemas.py b/fastcrud/examples/task/schemas.py new file mode 100644 index 0000000..b3379f4 --- /dev/null +++ b/fastcrud/examples/task/schemas.py @@ -0,0 +1,47 @@ +# --8<-- [start:imports] +from pydantic import BaseModel + + +# --8<-- [end:imports] +# --8<-- [start:schemas] +# --8<-- [start:createschema] +class CreateTaskSchema(BaseModel): + creator_id: int | None = None + owner_id: int | None = None + assigned_user_id: int | None = None + story_id: int | None = None + status: str | None = None + priority: str | None = None + + +# --8<-- [end:createschema] +# --8<-- [start:readschema] +class ReadTaskSchema(BaseModel): + id: int + creator_id: int | None = None + owner_id: int | None = None + assigned_user_id: int | None = None + story_id: int | None = None + status: str | None = None + priority: str | None = None + + +# --8<-- [end:readschema] +# --8<-- [start:updateschema] +class UpdateTaskSchema(BaseModel): + creator_id: int | None = None + owner_id: int | None = None + assigned_user_id: int | None = None + story_id: int | None = None + status: str | None = None + priority: str | None = None + + +# --8<-- [end:updateschema] +# --8<-- [start:deleteschema] +class DeleteTaskSchema(BaseModel): + pass + + +# --8<-- [end:deleteschema] +# --8<-- [end:schemas] diff --git a/fastcrud/examples/tier/model.py b/fastcrud/examples/tier/model.py new file mode 100644 index 0000000..eaa0349 --- /dev/null +++ b/fastcrud/examples/tier/model.py @@ -0,0 +1,20 @@ +# --8<-- [start:imports] +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +# --8<-- [end:imports] + + +# --8<-- [start:model] +class Tier(Base): + __tablename__ = "tier" + id = Column(Integer, primary_key=True) + name = Column(String, unique=True) + + +# --8<-- [end:model] diff --git a/fastcrud/examples/tier/schemas.py b/fastcrud/examples/tier/schemas.py new file mode 100644 index 0000000..8de4676 --- /dev/null +++ b/fastcrud/examples/tier/schemas.py @@ -0,0 +1,32 @@ +# --8<-- [start:imports] +from pydantic import BaseModel + + +# --8<-- [end:imports] +# --8<-- [start:schemas] +# --8<-- [start:createschema] +class CreateTierSchema(BaseModel): + name: str | None = None + + +# --8<-- [end:createschema] +# --8<-- [start:readschema] +class ReadTierSchema(BaseModel): + id: int + name: str | None = None + + +# --8<-- [end:readschema] +# --8<-- [start:updateschema] +class UpdateTierSchema(BaseModel): + name: str | None = None + + +# --8<-- [end:updateschema] +# --8<-- [start:deleteschema] +class DeleteTierSchema(BaseModel): + pass + + +# --8<-- [end:deleteschema] +# --8<-- [end:schemas] diff --git a/fastcrud/examples/user/model.py b/fastcrud/examples/user/model.py new file mode 100644 index 0000000..3f688ae --- /dev/null +++ b/fastcrud/examples/user/model.py @@ -0,0 +1,46 @@ +# --8<-- [start:imports] +from sqlalchemy import ( + Boolean, + Column, + DateTime, + ForeignKey, + Integer, + String, + func, +) +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +# --8<-- [end:imports] + + +# --8<-- [start:model] +# --8<-- [start:model_common] +class User(Base): + __tablename__ = "user" + id = Column(Integer, primary_key=True) + name = Column(String) + # --8<-- [end:model_common] + username = Column(String) + email = Column(String) + age = Column(Integer) + role = Column(String) + # --8<-- [start:model_tier] + tier_id = Column(Integer, ForeignKey("tier.id")) + # --8<-- [end:model_tier] + department_id = Column(Integer, ForeignKey("department.id")) + manager_id = Column(Integer, ForeignKey("user.id")) + is_active = Column(Boolean, default=True) + is_superuser = Column(Boolean, default=False) + registration_date = Column(DateTime, default=func.now()) + # --8<-- [start:model_archived] + archived = Column(Boolean, default=False) + archived_at = Column(DateTime) + # --8<-- [end:model_archived] + + +# --8<-- [end:model] diff --git a/fastcrud/examples/user/schemas.py b/fastcrud/examples/user/schemas.py new file mode 100644 index 0000000..18571cf --- /dev/null +++ b/fastcrud/examples/user/schemas.py @@ -0,0 +1,70 @@ +# --8<-- [start:imports] +import datetime + +from pydantic import BaseModel + + +# --8<-- [end:imports] +# --8<-- [start:schemas] +# --8<-- [start:createschema] +# --8<-- [start:createschema_common] +class CreateUserSchema(BaseModel): + name: str | None = None + # --8<-- [end:createschema_common] + username: str | None = None + email: str | None = None + age: int | None = None + role: str | None = None + tier_id: int | None = None + department_id: int | None = None + manager_id: int | None = None + is_active: bool | None = None + is_superuser: bool | None = None + + +# --8<-- [end:createschema] +# --8<-- [start:readschema] +# --8<-- [start:readschema_common] +class ReadUserSchema(BaseModel): + id: int + name: str | None = None + # --8<-- [end:readschema_common] + username: str | None = None + email: str | None = None + age: int | None = None + role: str | None = None + tier_id: int | None = None + department_id: int | None = None + manager_id: int | None = None + is_active: bool + is_superuser: bool + registration_date: datetime.datetime + archived: bool + archived_at: datetime.datetime | None = None + + +# --8<-- [end:readschema] +# --8<-- [start:updateschema] +# --8<-- [start:updateschema_common] +class UpdateUserSchema(BaseModel): + name: str | None = None + # --8<-- [end:updateschema_common] + username: str | None = None + email: str | None = None + age: int | None = None + role: str | None = None + tier_id: int | None = None + department_id: int | None = None + manager_id: int | None = None + is_active: bool | None = None + is_superuser: bool | None = None + + +# --8<-- [end:updateschema] +# --8<-- [start:deleteschema] +class DeleteUserSchema(BaseModel): + pass + + +# --8<-- [end:deleteschema] +# --8<-- [end:schemas]