diff --git a/fastcrud/crud/fast_crud.py b/fastcrud/crud/fast_crud.py index ef83018..f882ee2 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) @@ -782,23 +905,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) @@ -830,18 +957,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): @@ -960,8 +1090,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, @@ -969,8 +1100,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, @@ -980,8 +1112,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, @@ -993,7 +1126,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, @@ -1002,20 +1135,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, @@ -1112,82 +1247,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", ), ], @@ -1247,25 +1390,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", ), ], @@ -1424,25 +1568,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, @@ -1450,25 +1596,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'], @@ -1478,14 +1626,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, @@ -1494,25 +1643,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", ), ], @@ -1583,24 +1733,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"), ), @@ -1608,7 +1760,7 @@ async def get_multi_joined( nest_joins=True, offset=0, limit=5, - sort_columns='project_name', + sort_columns='name', sort_orders='asc', ) ``` @@ -1788,29 +1940,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', @@ -1820,8 +1974,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', @@ -1906,21 +2061,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, @@ -1931,11 +2089,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, ) @@ -1943,7 +2102,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'], @@ -2069,11 +2228,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, @@ -2083,6 +2244,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, @@ -2131,11 +2293,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, @@ -2145,6 +2309,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",