Advanced Use of EndpointCreator¶
Available Automatic Endpoints¶
FastCRUD automates the creation of CRUD (Create, Read, Update, Delete) endpoints for your FastAPI application. Here's an overview of the available automatic endpoints and how they work, based on the automatic endpoints we've generated before:
Create¶
- Endpoint:
/{model} - Method:
POST - Description: Creates a new item in the database.
- Request Body: JSON object based on the
create_schema. - Example Request:
POST /itemswith JSON body.
Read¶
- Endpoint:
/{model}/{id} - Method:
GET - Description: Retrieves a single item by its ID.
- Path Parameters:
id- The ID of the item to retrieve. - Example Request:
GET /items/1. - Example Return:
Read Multiple¶
- Endpoint:
/{model} - Method:
GET - Description: Retrieves multiple items with optional pagination.
- Query Parameters:
offset(optional): The offset from where to start fetching items.limit(optional): The maximum number of items to return.page(optional): The page number, starting from 1.itemsPerPage(optional): The number of items per page.sort(optional): Sort results by one or more fields. Format:field1,-field2where-prefix indicates descending order.
- Example Requests:
GET /items?offset=3&limit=4(pagination)GET /items?sort=name(sort by name ascending)GET /items?sort=-price,name(sort by price descending, then name ascending)
- Example Return:
{ "data": [ { "id": 4, "name": "Item 4", "description": "Description of item 4", "category": "Books", "price": 5.99, "last_sold": null, "created_at": "2024-01-01 12:01:00" }, { "id": 5, "name": "Item 5", "description": "Description of item 5", "category": "Music", "price": 5.99, "last_sold": "2024-04-01 00:00:00", "created_at": "2024-01-01 12:10:00" }, { "id": 6, "name": "Item 6", "description": "Description of item 6", "category": "TV", "price": 5.99, "last_sold": null, "created_at": "2024-01-01 12:15:00" }, { "id": 7, "name": "Item 7", "description": "Description of item 7", "category": "Books", "price": 5.99, "last_sold": null, "created_at": "2024-01-01 13:00:30" } ], "total_count": 50 } - Example Paginated Request:
GET /items?page=1&itemsPerPage=3. - Example Paginated Return:
{ "data": [ { "id": 1, "name": "Item 1", "description": "Description of item 1", "category": "Movies", "price": 5.99, "last_sold": null, "created_at": "2024-01-01 12:00:01" }, { "id": 2, "name": "Item 2", "description": "Description of item 2", "category": "TV", "price": 19.99, "last_sold": null, "created_at": "2024-01-01 12:00:15" }, { "id": 3, "name": "Item 3", "description": "Description of item 3", "category": "Books", "price": 4.99, "last_sold": null, "created_at": "2024-01-01 12:00:16" } ], "total_count": 50, "has_more": true, "page": 1, "items_per_page": 3 }
Note
_read_paginated endpoint was deprecated and mixed into _read_items in the release 0.15.0.
Simple _read_items behaviour persists with no breaking changes.
Read items paginated:
$ curl -X 'GET' \
'http://localhost:8000/users?page=2&itemsPerPage=10' \
-H 'accept: application/json'
Read items unpaginated:
Update¶
- Endpoint:
/{model}/{id} - Method:
PATCH - Description: Updates an existing item by its ID.
- Path Parameters:
id- The ID of the item to update. - Request Body: JSON object based on the
update_schema. - Example Request:
PATCH /items/1with JSON body. - Example Return:
None - Note: If the target item is not found by ID, the generated endpoint returns a 404 Not Found with detail "Item not found".
Delete¶
- Endpoint:
/{model}/{id} - Method:
DELETE - Description: Deletes (soft delete if configured) an item by its ID.
- Path Parameters:
id- The ID of the item to delete. - Example Request:
DELETE /items/1. - Example Return:
None - Note: If the target item is not found by ID, the generated endpoint returns a 404 Not Found with detail "Item not found".
DB Delete (Hard Delete)¶
- Endpoint:
/{model}/db_delete/{id}(Available if adelete_schemais provided) - Method:
DELETE - Description: Permanently deletes an item by its ID, bypassing the soft delete mechanism.
- Path Parameters:
id- The ID of the item to hard delete. - Example Request:
DELETE /items/db_delete/1. - Example Return:
None
Selective CRUD Operations¶
You can control which CRUD operations are exposed by using included_methods and deleted_methods. These parameters allow you to specify exactly which CRUD methods should be included or excluded when setting up the router. By default, all CRUD endpoints are included.
mymodel/model.py
mymodel/schemas.py
Using included_methods¶
Using included_methods you may define exactly the methods you want to be included.
# Using crud_router with selective CRUD methods
my_router = crud_router(
session=get_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
crud=FastCRUD(MyModel),
path="/mymodel",
tags=["MyModel"],
included_methods=["create", "read", "update"], # Only these methods will be included
)
app.include_router(my_router)
Using deleted_methods¶
Using deleted_methods you define the methods that will not be included.
# Using crud_router with selective CRUD methods
my_router = crud_router(
session=get_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
crud=FastCRUD(MyModel),
path="/mymodel",
tags=["MyModel"],
deleted_methods=["update", "delete"], # All but these methods will be included
)
app.include_router(my_router)
Warning
If included_methods and deleted_methods are both provided, a ValueError will be raised.
Customizing Endpoint Names¶
You can customize the names of the auto generated endpoints by passing an endpoint_names dictionary when initializing the EndpointCreator or calling the crud_router function. This dictionary should map the CRUD operation names (create, read, update, delete, db_delete, read_multi) to your desired endpoint names.
Example: Using crud_router¶
Here's how you can customize endpoint names using the crud_router function:
from fastapi import FastAPI
from fastcrud import crud_router
from .database import async_session
from .mymodel.model import MyModel
from .mymodel.schemas import CreateMyModelSchema, UpdateMyModelSchema
app = FastAPI()
# Custom endpoint names
custom_endpoint_names = {
"create": "add",
"read": "fetch",
"update": "modify",
"delete": "remove",
"read_multi": "list",
}
# Setup CRUD router with custom endpoint names
app.include_router(crud_router(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
path="/mymodel",
tags=["MyModel"],
endpoint_names=custom_endpoint_names,
))
In this example, the standard CRUD endpoints will be replaced with /add, /fetch/{id}, /modify/{id}, /remove/{id}, /list, and /paginate.
Example: Using EndpointCreator¶
If you are using EndpointCreator, you can also pass the endpoint_names dictionary to customize the endpoint names similarly:
# Custom endpoint names
custom_endpoint_names = {
"create": "add_new",
"read": "get_single",
"update": "change",
"delete": "erase",
"db_delete": "hard_erase",
"read_multi": "get_all",
"read_paginated": "get_page",
}
# Initialize and use the custom EndpointCreator
endpoint_creator = EndpointCreator(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
path="/mymodel",
tags=["MyModel"],
endpoint_names=custom_endpoint_names,
)
endpoint_creator.add_routes_to_router()
app.include_router(endpoint_creator.router)
Tip
You only need to pass the names of the endpoints you want to change in the endpoint_names dict.
Note
default_endpoint_names for EndpointCreator were changed to empty strings in 0.15.0.
See this issue for more details.
Joined Model Filtering¶
FastCRUD supports filtering on related models using dot notation in filter configurations. This allows you to filter records based on attributes of joined models without manually writing complex queries.
Basic Joined Model Filtering¶
You can filter records based on attributes of related models by using dot notation in your filter configuration:
from fastapi import FastAPI
from fastcrud import EndpointCreator, FilterConfig
# Assuming you have User and Company models with a relationship
app = FastAPI()
endpoint_creator = EndpointCreator(
session=async_session,
model=User,
create_schema=CreateUserSchema,
update_schema=UpdateUserSchema,
filter_config=FilterConfig(
# Regular filters
name=None,
email=None,
# Joined model filters
**{
"company.name": None, # Filter by company name
"company.industry": None, # Filter by company industry
"company.founded_year": None, # Filter by company founded year
}
),
)
endpoint_creator.add_routes_to_router()
app.include_router(endpoint_creator.router, prefix="/users")
Using Joined Model Filters¶
Once configured, you can use joined model filters in your API requests:
# Filter users by company name
GET /users?company.name=TechCorp
# Filter users by company industry
GET /users?company.industry=Technology
# Combine regular and joined filters
GET /users?name=John&company.name=TechCorp
# Use filter operators with joined models
GET /users?company.founded_year__gte=2000
Supported Filter Operators¶
Joined model filters support all the same operators as regular filters:
filter_config=FilterConfig(**{
"company.name__eq": None, # Exact match
"company.name__ne": None, # Not equal
"company.name__in": None, # In list
"company.founded_year__gte": None, # Greater than or equal
"company.founded_year__lt": None, # Less than
"company.revenue__between": None, # Between values
})
Multi-level Relationships¶
You can filter through multiple levels of relationships:
# Assuming User -> Company -> Address relationship
filter_config=FilterConfig(**{
"company.address.city": None,
"company.address.country": None,
})
# Usage:
# GET /users?company.address.city=San Francisco
How It Works¶
When you use joined model filters, FastCRUD automatically:
- Detects joined filters: Identifies filter keys containing dot notation
- Validates relationships: Ensures the relationship path exists in your models
- Generates joins: Automatically creates the necessary SQL joins
- Applies filters: Adds WHERE clauses for the joined model attributes
The system generates efficient SQL queries like:
SELECT user.id, user.name, user.email, user.company_id,
company.id AS company_id_1, company.name AS company_name, company.industry
FROM user
LEFT OUTER JOIN company ON user.company_id = company.id
WHERE company.name = 'TechCorp'
Limitations¶
- Currently supports single-relationship joins (one level of relationship at a time)
- Complex many-to-many relationships may require custom implementation
- Performance considerations apply for deeply nested relationships
Example Models¶
Here's an example of models that work well with joined model filtering:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Company(Base):
__tablename__ = "company"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
industry = Column(String(50))
users = relationship("User", back_populates="company")
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(100), unique=True)
company_id = Column(Integer, ForeignKey("company.id"))
company = relationship("Company", back_populates="users")
Extending EndpointCreator¶
You can create a subclass of EndpointCreator and override or add new methods to define custom routes. Here's an example:
Creating a Custom EndpointCreator¶
from fastcrud import EndpointCreator
# Define the custom EndpointCreator
class MyCustomEndpointCreator(EndpointCreator):
# Add custom routes or override existing methods
def _custom_route(self):
async def custom_endpoint():
# Custom endpoint logic
return {"message": "Custom route"}
return custom_endpoint
# override add_routes_to_router to also add the custom routes
def add_routes_to_router(self, ...):
# First, add standard CRUD routes if you want them
super().add_routes_to_router(...)
# Now, add custom routes
self.router.add_api_route(
path="/custom",
endpoint=self._custom_route(),
methods=["GET"],
tags=self.tags,
# Other parameters as needed
)
Adding custom routes¶
from fastcrud import EndpointCreator
# Define the custom EndpointCreator
class MyCustomEndpointCreator(EndpointCreator):
# Add custom routes or override existing methods
def _custom_route(self):
async def custom_endpoint():
# Custom endpoint logic
return {"message": "Custom route"}
return custom_endpoint
# override add_routes_to_router to also add the custom routes
def add_routes_to_router(self, ...):
# First, add standard CRUD routes if you want them
super().add_routes_to_router(...)
# Now, add custom routes
self.router.add_api_route(
path="/custom",
endpoint=self._custom_route(),
methods=["GET"],
tags=self.tags,
# Other parameters as needed
)
Overriding add_routes_to_router¶
from fastcrud import EndpointCreator
# Define the custom EndpointCreator
class MyCustomEndpointCreator(EndpointCreator):
# Add custom routes or override existing methods
def _custom_route(self):
async def custom_endpoint():
# Custom endpoint logic
return {"message": "Custom route"}
return custom_endpoint
# override add_routes_to_router to also add the custom routes
def add_routes_to_router(self, ...):
# First, add standard CRUD routes if you want them
super().add_routes_to_router(...)
# Now, add custom routes
self.router.add_api_route(
path="/custom",
endpoint=self._custom_route(),
methods=["GET"],
tags=self.tags,
# Other parameters as needed
)
Using the Custom EndpointCreator¶
# Assuming MyCustomEndpointCreator was created
...
# Use the custom EndpointCreator with crud_router
my_router = crud_router(
session=get_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
crud=FastCRUD(MyModel),
path="/mymodel",
tags=["MyModel"],
included_methods=["create", "read", "update"], # Including selective methods
endpoint_creator=MyCustomEndpointCreator,
)
app.include_router(my_router)
Custom Soft Delete¶
To implement custom soft delete columns using EndpointCreator and crud_router in FastCRUD, you need to specify the names of the columns used for indicating deletion status and the deletion timestamp in your model. FastCRUD provides flexibility in handling soft deletes by allowing you to configure these column names directly when setting up CRUD operations or API endpoints.
Here's how to specify custom soft delete columns when utilizing EndpointCreator and crud_router:
Defining Models with Custom Soft Delete Columns¶
First, ensure your SQLAlchemy model is equipped with the custom soft delete columns. Here's an example model with custom columns for soft deletion:
from sqlalchemy import Boolean, Column, DateTime, Integer, String
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class MyModel(Base):
__tablename__ = "my_model"
id = Column(Integer, primary_key=True)
name = Column(String)
archived = Column(Boolean, default=False) # Custom soft delete column
archived_at = Column(DateTime) # Custom timestamp column for soft delete
And a schema necessary to activate the soft delete endpoint:
Using EndpointCreator and crud_router with Custom Soft Delete or Update Columns¶
When initializing crud_router or creating a custom EndpointCreator, you can pass the names of your custom soft delete columns through the FastCRUD initialization. This informs FastCRUD which columns to check and update for soft deletion operations.
Here's an example of using crud_router with custom soft delete columns:
from fastapi import FastAPI
from fastcrud import FastCRUD, crud_router
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
# Assuming async_session is your AsyncSession generator
# and MyModel is your SQLAlchemy model
# Initialize FastCRUD with custom soft delete columns
my_model_crud = FastCRUD(
MyModel,
is_deleted_column='archived', # Custom 'is_deleted' column name
deleted_at_column='archived_at', # Custom 'deleted_at' column name
)
# Setup CRUD router with the FastCRUD instance
app.include_router(crud_router(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
crud=my_model_crud,
delete_schema=DeleteMyModelSchema,
path="/mymodel",
tags=["MyModel"],
))
You may also directly pass the names of the columns to crud_router or EndpointCreator:
app.include_router(endpoint_creator(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
delete_schema=DeleteMyModelSchema,
path="/mymodel",
tags=["MyModel"],
is_deleted_column='archived',
deleted_at_column='archived_at',
))
This setup ensures that the soft delete functionality within your application utilizes the archived and archived_at columns for marking records as deleted, rather than the default is_deleted and deleted_at fields.
By specifying custom column names for soft deletion, you can adapt FastCRUD to fit the design of your database models, providing a flexible solution for handling deleted records in a way that best suits your application's needs.
You can also customize your updated_at column:
class MyModel(Base):
__tablename__ = "my_model"
id = Column(Integer, primary_key=True)
name = Column(String)
archived = Column(Boolean, default=False) # Custom soft delete column
archived_at = Column(DateTime) # Custom timestamp column for soft delete
date_updated = Column(DateTime) # Custom timestamp column for update
app.include_router(endpoint_creator(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
delete_schema=DeleteMyModelSchema,
path="/mymodel",
tags=["MyModel"],
is_deleted_column='archived',
deleted_at_column='archived_at',
updated_at_column='date_updated',
))
Using Filters in FastCRUD¶
FastCRUD provides filtering capabilities, allowing you to filter query results based on various conditions. Filters can be applied to read_multi endpoint. This section explains how to configure and use filters in FastCRUD.
Defining Filters¶
Filters are either defined using the FilterConfig class or just passed as a dictionary. This class allows you to specify default filter values and validate filter types. Here's an example of how to define filters for a model:
from fastcrud import FilterConfig
# Define filter configuration for a model
filter_config = FilterConfig(
tier_id=None, # Default filter value for tier_id
name=None, # Default filter value for name
)
And the same thing using a dict:
filter_config = {
"tier_id": None, # Default filter value for tier_id
"name": None, # Default filter value for name
}
By using FilterConfig you get better error messages.
Applying Filters to Endpoints¶
You can apply filters to your endpoints by passing the filter_config to the crud_router or EndpointCreator. Here's an example:
from fastcrud import crud_router
from .database import async_session
from .yourmodel.model import YourModel
from .yourmodel.schemas import CreateYourModelSchema, UpdateYourModelSchema
# Apply filters using crud_router
app.include_router(
crud_router(
session=async_session,
model=YourModel,
create_schema=CreateYourModelSchema,
update_schema=UpdateYourModelSchema,
path="/yourmodel",
tags=["YourModel"],
filter_config=filter_config, # Apply the filter configuration
),
)
Dependency-Based Filtering¶
FastCRUD also supports dependency-based filtering, allowing you to automatically filter query results based on values from dependencies. This is particularly useful for implementing row-level access control, where users should only see data that belongs to their organization or tenant.
from fastapi import Depends
from fastcrud import crud_router, FilterConfig
# Define a dependency that returns the user's organization ID
async def get_auth_user():
# Your authentication logic here
return UserInfo(organization_id=123)
async def get_org_id(auth: UserInfo = Depends(get_auth_user)):
return auth.organization_id
# Create a router with dependency-based filtering
epc_router = crud_router(
session=async_session,
model=ExternalProviderConfig,
create_schema=ExternalProviderConfigSchema,
update_schema=ExternalProviderConfigSchema,
path="/external_provider_configs",
filter_config=FilterConfig(
organization_id=get_org_id, # This will be resolved at runtime
),
tags=["external_provider_configs"],
)
app.include_router(epc_router)
In this example, the get_org_id dependency will be called for each request, and the returned value will be used to filter the results by organization_id.
For more details on dependency-based filtering, see the Dependency-Based Filtering documentation.
Using Filters in Requests¶
Once filters are configured, you can use them in your API requests. Filters are passed as query parameters. Here's an example of how to use filters in a request to a paginated endpoint:
Custom Filter Validation¶
The FilterConfig class includes a validator to check filter types. If an invalid filter type is provided, a ValueError is raised. You can customize the validation logic by extending the FilterConfig class:
from fastcrud import FilterConfig
from pydantic import ValidationError
class CustomFilterConfig(FilterConfig):
@field_validator("filters")
def check_filter_types(cls, filters: dict[str, Any]) -> dict[str, Any]:
for key, value in filters.items():
if not isinstance(value, (type(None), str, int, float, bool)):
raise ValueError(f"Invalid default value for '{key}': {value}")
return filters
try:
# Example of invalid filter configuration
invalid_filter_config = CustomFilterConfig(invalid_field=[])
except ValidationError as e:
print(e)
Handling Invalid Filter Columns¶
FastCRUD ensures that filters are applied only to valid columns in your model. If an invalid filter column is specified, a ValueError is raised:
try:
# Example of invalid filter column
invalid_filter_config = FilterConfig(non_existent_column=None)
except ValueError as e:
print(e) # Output: Invalid filter column 'non_existent_column': not found in model
Sorting Results¶
FastCRUD automatically provides sorting functionality for the "read multiple" endpoint through the sort query parameter. This allows clients to control the ordering of returned results.
Basic Sorting¶
Sort by a single field in ascending order:
Sort by a single field in descending order (use - prefix):
Multi-field Sorting¶
Sort by multiple fields by separating them with commas:
Mix ascending and descending orders:
This sorts by: 1.category (ascending)
2. price (descending)
3. name (ascending)
Sorting Format¶
The sort parameter accepts the following format:
- Field names separated by commas: field1,field2,field3
- Prefix with - for descending order: -field1,field2,-field3
- No spaces around commas
- Field names must match your model's column names
Error Handling¶
If you specify an invalid column name that doesn't exist in your model, FastCRUD will return a 400 Bad Request error with details about the invalid column.
Combining with Other Parameters¶
Sorting can be combined with pagination and filtering:
This example:
- Sorts by created_at in descending order (newest first)
- Returns the first page with 10 items per page
- Filters for items in the "Books" category
Conclusion¶
The EndpointCreator class in FastCRUD offers flexibility and control over CRUD operations and custom endpoint creation. By extending this class or using the included_methods and deleted_methods parameters, you can tailor your API's functionality to your specific requirements, ensuring a more customizable and streamlined experience.