How to Use FastCRUD Relationship Auto-Detection: A Complete Guide
FastCRUD automatically joins SQLAlchemy relationships by passing auto_detect_relationships=True to methods like get_joined or get_multi_joined, with optional include_one_to_many=True for one-to-many relations.
FastCRUD's relationship auto-detection eliminates manual join configuration when querying related data in FastAPI applications. This feature, implemented in the benavlabs/fastcrud repository, inspects your SQLAlchemy models for relationship() definitions and generates safe join conditions automatically. You can enable it on a per-query basis while retaining full control over nesting structure and relationship types.
How Relationship Auto-Detection Works
The auto-detection system operates through three internal phases defined in fastcrud/core/field_management.py:
-
Relationship Discovery – The
discover_model_relationshipsfunction inspects the SQLAlchemy mapper to identify allrelationship()attributes on your model. -
Join Configuration – For each discovered relationship,
build_relationship_joins_configcallsauto_detect_join_conditionto generate safe join clauses. By default, it filters for one-to-one relationships only, unless you explicitly enable one-to-many support. -
Query Execution – In
fastcrud/crud/fast_crud.py, methods likeget_joined(around lines 1818-1843) replace manual join parameters with the auto-generatedJoinConfigobjects whenauto_detect_relationshipsis truthy.
The prepare_joins function in fastcrud/core/query/joins.py then materializes these configurations into actual SQLAlchemy join clauses.
Basic Usage with One-to-One Relationships
By default, FastCRUD only joins one-to-one relationships to prevent accidental Cartesian products. Instantiate your CRUD class and call get_joined with auto_detect_relationships=True:
from fastcrud import FastCRUD
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import Depends, APIRouter
router = APIRouter()
user_crud = FastCRUD(User)
@router.get("/users/{user_id}")
async def read_user(user_id: int, db: AsyncSession = Depends(get_db)):
return await user_crud.get_joined(
db,
schema_to_select=UserReadSchema,
id=user_id,
auto_detect_relationships=True, # Auto-join all one-to-one relations
nest_joins=True, # Return nested dicts instead of flat keys
)
When nest_joins=True, the response structure places related data under nested dictionaries (e.g., {"id": 1, "tier": {"name": "Premium"}} rather than {"id": 1, "tier_name": "Premium"}).
Including One-to-Many Relationships
To include one-to-many relationships (such as User → Posts), pass include_one_to_many=True. Be cautious with this option on large datasets, as it can create large result sets:
@router.get("/users/{user_id}")
async def read_user_with_posts(user_id: int, db: AsyncSession = Depends(get_db)):
return await user_crud.get_joined(
db,
schema_to_select=UserReadSchema,
id=user_id,
auto_detect_relationships=True,
include_one_to_many=True, # Enable one-to-many joins
nest_joins=True,
)
This configuration is documented in the method signature around lines 1582-1584 of fastcrud/crud/fast_crud.py. The filtering logic resides in build_relationship_joins_config (lines 32-53 of fastcrud/core/field_management.py).
Selective Relationship Detection
Pass a list of relationship names to auto_detect_relationships to join specific relations while bypassing the default one-to-one filter:
@router.get("/users/{user_id}")
async def read_user_selective(user_id: int, db: AsyncSession = Depends(get_db)):
return await user_crud.get_joined(
db,
schema_to_select=UserReadSchema,
id=user_id,
auto_detect_relationships=["tier", "posts"], # Explicit list of relation names
nest_joins=True,
)
When you provide a list, the logic in build_relationship_joins_config (lines 38-44) bypasses the include_one_to_many check and joins only the named relationships regardless of their cardinality.
Multi-Record Queries with Auto-Detection
Use get_multi_joined to apply auto-detection to paginated lists. The method reuses the same internal flow as get_joined (see lines 1850-1880 in fastcrud/crud/fast_crud.py):
@router.get("/users")
async def list_users(
db: AsyncSession = Depends(get_db),
limit: int = 10,
offset: int = 0
):
return await user_crud.get_multi_joined(
db,
schema_to_select=UserReadSchema,
limit=limit,
offset=offset,
auto_detect_relationships=True,
include_one_to_many=False, # Keep to one-to-one for list views
nest_joins=True,
)
This returns a paginated response where each user record contains its nested one-to-one relations.
Error Handling and Constraints
FastCRUD prevents ambiguous query construction by raising a clear error when you attempt to mix auto-detection with manual join parameters. The validation logic exists at lines 1828-1832 of fastcrud/crud/fast_crud.py:
# This raises ValueError
await user_crud.get_joined(
db,
schema_to_select=UserReadSchema,
id=1,
join_model=Tier, # Manual join parameter
auto_detect_relationships=True, # Conflicts with manual join
)
The error message states: "Cannot use auto_detect_relationships with manual join parameters." Use either the explicit join_model/joins_config approach or auto-detection, never both.
Summary
- Enable auto-detection by passing
auto_detect_relationships=True(or a list of relationship names) toget_joined,get_multi_joined,get, orget_multi. - Default behavior only joins one-to-one relationships; set
include_one_to_many=Trueto include collection-based relations. - Nest results using
nest_joins=Trueto receive hierarchical dictionaries instead of flat key hierarchies. - Conflict prevention – Never combine
auto_detect_relationshipswith explicitjoin_modelorjoins_configparameters. - Source implementation resides in
fastcrud/core/field_management.pyfor relationship discovery andfastcrud/crud/fast_crud.pyfor query execution.
Frequently Asked Questions
What methods support the auto_detect_relationships parameter?
The auto_detect_relationships parameter is available in get, get_multi, get_joined, and get_multi_joined methods of the FastCRUD class defined in fastcrud/crud/fast_crud.py. These methods delegate relationship discovery to build_relationship_joins_config in the field management module when the parameter is truthy.
Why am I getting a ValueError when using auto_detect_relationships?
FastCRUD raises a ValueError if you mix auto_detect_relationships with explicit join parameters like join_model, joins_config, or join_on. This validation at lines 1828-1832 of fastcrud/crud/fast_crud.py prevents ambiguous query construction. Choose either automatic relationship detection or manual join configuration, not both.
Does auto-detection work with many-to-many relationships?
Many-to-many relationships are treated as one-to-many from the perspective of the source side. They require include_one_to_many=True to be joined automatically. However, for complex many-to-many scenarios with association tables requiring specific join conditions, explicit JoinConfig objects remain the recommended approach according to the source implementation.
How do I control the nesting structure of joined results?
Set nest_joins=True to receive nested dictionaries where one-to-one relationships appear as nested objects and one-to-many appear as lists. When nest_joins=False (the default), joined fields use prefixed keys (e.g., tier_name). The nesting logic is handled by prepare_joins in fastcrud/core/query/joins.py, which processes the JoinConfig objects generated by the auto-detection system.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →