Releases: igorbenav/fastcrud
0.9.1
0.9.1 Summary
Added
- Now get_joined and get_multi_joined can be used with aliases, making it possible to join the same model multiple times.
This is a fix to #27
Detailed
In complex query scenarios, particularly when you need to join a table to itself or perform multiple joins on the same table for different purposes, aliasing becomes crucial. Aliasing allows you to refer to the same table in different contexts with unique identifiers, avoiding conflicts and ambiguity in your queries.
For both get_joined
and get_multi_joined
methods, when you need to join the same model multiple times, you can utilize the alias
parameter within your JoinConfig
to differentiate between the joins. This parameter expects an instance of AliasedClass
, which can be created using the aliased
function from SQLAlchemy (also in fastcrud for convenience).
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.
Let's start by creating the aliases and passing them to the join configuration. Don't forget to use the alias for join_on
:
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")
# Configure joins with aliases
joins_config = [
JoinConfig(
model=UserModel,
join_on=Task.owner_id == owner_alias.id,
join_prefix="owner_",
schema_to_select=UserSchema,
join_type="inner",
alias=owner_alias # Pass the aliased class instance
),
JoinConfig(
model=UserModel,
join_on=Task.assigned_user_id == assigned_user_alias.id,
join_prefix="assigned_",
schema_to_select=UserSchema,
join_type="inner",
alias=assigned_user_alias # Pass the aliased class instance
)
]
# Initialize your FastCRUD instance for TaskModel
task_crud = FastCRUD(TaskModel)
# Fetch tasks with joined user details
tasks = await task_crud.get_multi_joined(
db=session,
schema_to_select=TaskSchema,
joins_config=joins_config,
offset=0,
limit=10
)
Then just pass this joins_config to get_multi_joined
:
from fastcrud import FastCRUD, JoinConfig, aliased
...
# Configure joins with aliases
joins_config = [
...
]
# Initialize your FastCRUD instance for TaskModel
task_crud = FastCRUD(TaskModel)
# Fetch tasks with joined user details
tasks = await task_crud.get_multi_joined(
db=session,
schema_to_select=TaskSchema,
joins_config=joins_config,
offset=0,
limit=10
)
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.
What's Changed
- Using Aliases by @igorbenav in #29
Full Changelog: v0.9.0...v0.9.1
0.9.0
0.9.0 Summary
Added
- Now get_joined and get_multi_joined can be used with multiple models.
Detailed
To facilitate complex data relationships, get_joined
and get_multi_joined
can be configured to handle joins with multiple models. This is achieved using the joins_config
parameter, where you can specify a list of JoinConfig
instances, each representing a distinct join configuration.
Example: Joining User, Tier, and Department Models
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:
from fastcrud import JoinConfig
joins_config = [
JoinConfig(
model=Tier,
join_on=User.tier_id == Tier.id,
join_prefix="tier_",
schema_to_select=TierSchema,
join_type="left",
),
JoinConfig(
model=Department,
join_on=User.department_id == Department.id,
join_prefix="dept_",
schema_to_select=DepartmentSchema,
join_type="inner",
)
]
users = await user_crud.get_multi_joined(
db=session,
schema_to_select=UserSchema,
joins_config=joins_config,
offset=0,
limit=10,
sort_columns='username',
sort_orders='asc'
)
Then just pass this list to joins_config:
from fastcrud import JoinConfig
joins_config = [
...
]
users = await user_crud.get_multi_joined(
db=session,
schema_to_select=UserSchema,
joins_config=joins_config,
offset=0,
limit=10,
sort_columns='username',
sort_orders='asc'
)
In this example, users are joined with the Tier
and Department
models. The join_on
parameter specifies the condition for the join, join_prefix
assigns a prefix to columns from the joined models (to avoid naming conflicts), and join_type
determines whether it's a left or inner join.
Warning
If both single join parameters and joins_config
are used simultaneously, an error will be raised.
What's Changed
- Update pyproject.toml by @igorbenav in #22
- fix: add necessary package import by @iridescentGray in #23
- Using
get_joined
andget_multi_joined
for multiple models by @igorbenav in #26
New Contributors
- @iridescentGray made their first contribution in #23
Full Changelog: v0.8.0...v0.9.0
0.8.0
0.8.0 Summary
Added
endpoint_names
parameter to customize auto generated endpoints (both to crud_router and EndpointCreator)- Docs updated to reflect it
Detailed
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
, read_paginated
) 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 yourapp.crud import crud_router
from yourapp.models import YourModel
from yourapp.schemas import CreateYourModelSchema, UpdateYourModelSchema
from yourapp.database import async_session
app = FastAPI()
# Custom endpoint names
custom_endpoint_names = {
"create": "add",
"read": "fetch",
"update": "modify",
"delete": "remove",
"read_multi": "list",
"read_paginated": "paginate"
}
# Setup CRUD router with custom endpoint names
app.include_router(crud_router(
session=async_session,
model=YourModel,
create_schema=CreateYourModelSchema,
update_schema=UpdateYourModelSchema,
path="/yourmodel",
tags=["YourModel"],
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=YourModel,
create_schema=CreateYourModelSchema,
update_schema=UpdateYourModelSchema,
path="/yourmodel",
tags=["YourModel"],
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.
0.7.0
0.7.0 Summary
Added
- Automatic get_paginated endpoint
- Module
paginated
to handle utility functions - Docs updated to reflect it
Detailed
get_paginated
endpoint
-
Endpoint:
/get_paginated
-
Method:
GET
-
Description: Retrieves multiple items with pagination.
-
Query Parameters:
page
: The page number, starting from 1.itemsPerPage
: The number of items per page.
-
Example Request:
GET /yourmodel/get_paginated?page=1&itemsPerPage=3
. -
Example Return:
{
"data": [
{"id": 1, "name": "Item 1", "description": "Description of item 1"},
{"id": 2, "name": "Item 2", "description": "Description of item 2"},
{"id": 3, "name": "Item 3", "description": "Description of item 3"}
],
"total_count": 50,
"has_more": true,
"page": 1,
"items_per_page": 10
}
paginated
module
Functions:
- 1. paginated_response:
paginated_response(
crud_data: dict, page: int, items_per_page: int
) -> dict[str, Any]
Usage and Return example:
crud = FastCRUD(MyModel)
paginated_response(crud_data=crud.get_multi(MyModel), page=1, items_per_page=3)
returns
{
"data": [
{"id": 1, "name": "Item 1", "description": "Description of item 1"},
{"id": 2, "name": "Item 2", "description": "Description of item 2"},
{"id": 3, "name": "Item 3", "description": "Description of item 3"}
],
"total_count": 50,
"has_more": true,
"page": 1,
"items_per_page": 10
}
- 2. compute_offset:
compute_offset(page: int, items_per_page: int) -> int
Usage and Return example:
offset(3, 10)
returns
20
What's Changed
- Paginated by @igorbenav in #18
Full Changelog: v0.6.0...v0.7.0
0.6.0
0.6.0 Summary
Added
- custom
updated_at
column name - passing crud to
crud_router
andEndpointCreator
now optional - exceptions in
http_exceptions
now also inexceptions
module
Detailed
Passing custom updated_at
column
You can also customize your `updated_at` column:
```python hl_lines="9"
app.include_router(endpoint_creator(
session=async_session,
model=MyModel,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
delete_schema=DeleteMyModelSchema,
path="/mymodel",
tags=["MyModel"],
updated_at_column='date_updated'
))
What's Changed
- Passing CRUD now optional, Custom updated_at Column name by @igorbenav in #17
Full Changelog: v0.5.0...v0.6.0
0.5.0
0.5.0 Summary
Added
- Advanced filters (Django ORM style)
- Optional Bulk Operations
- Custom soft delete mechanisms in FastCRUD, EndpointCreator and crud_router
- Tests for new features
Detailed
Advanced Filters
FastCRUD supports advanced filtering options, allowing you to query records using operators such as greater than (__gt
), less than (__lt
), and their inclusive counterparts (__gte
, __lte
). These filters can be used in any method that retrieves or operates on records, including get
, get_multi
, exists
, count
, update
, and delete
.
Using Advanced Filters
The following examples demonstrate how to use advanced filters for querying and manipulating data:
Fetching Records with Advanced Filters
# Fetch items priced between $5 and $20
items = await item_crud.get_multi(
db=db,
price__gte=5,
price__lte=20
)
Counting Records
# Count items added in the last month
item_count = await item_crud.count(
db=db,
added_at__gte=datetime.datetime.now() - datetime.timedelta(days=30)
)
Using EndpointCreator
and crud_router
with Custom Soft Delete 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,
crud=my_model_crud,
create_schema=CreateMyModelSchema,
update_schema=UpdateMyModelSchema,
delete_schema=DeleteMyModelSchema,
path="/mymodel",
tags=["MyModel"]
))
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.
Updating Multiple Records
To update multiple records, you can set the allow_multiple=True
parameter in the update
method. This allows FastCRUD to apply the update to all records matching the given filters.
# Assuming setup for FastCRUD instance `item_crud` and SQLAlchemy async session `db`
# Update all items priced below $10 to a new price
await item_crud.update(
db=db,
object={"price": 9.99},
allow_multiple=True,
price__lt=10
)
Deleting Multiple Records
Similarly, you can delete multiple records by using the allow_multiple=True
parameter in the delete
or db_delete
method, depending on whether you're performing a soft or hard delete.
# Soft delete all items not sold in the last year
await item_crud.delete(
db=db,
allow_multiple=True,
last_sold__lt=datetime.datetime.now() - datetime.timedelta(days=365)
)
Relevant Changes in Documentation
- Example docs issue #12 fixed in pr #13 by @YuriiMotov
What's Changed
- Update README.md by @igorbenav in #10
- Crud enhancement by @igorbenav in #14
- Fixed an error in the documentation examples by @YuriiMotov in #13
New Contributors
- @YuriiMotov made their first contribution in #13
Full Changelog: v0.4.0...v0.5.0
0.4.0
0.4.0 Summary
Added
- tests and docs for SQLModel support
- py.typed for typing support
Detailed
Since SQLModel is just a combination of SQLAlchemy and Pydantic, the process simplifies as SQLModel combines the model and schema definitions.
Wherever in the docs you see a SQLAlchemy model or Pydantic schema being used, you may just replace it with SQLModel and it will work. For the quick start:
Define your SQLModel model
from sqlmodel import SQLModel, Field
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
class Item(SQLModel):
__tablename__ = 'items'
id: int = Field(primary_key=True)
name: str
class ItemCreateSchema(SQLModel):
name: str
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
engine = create_async_engine(DATABASE_URL, echo=True)
session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
Then your schemas
from sqlmodel import SQLModel, Field
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
class Item(SQLModel):
__tablename__ = 'items'
id: int = Field(primary_key=True)
name: str
class ItemCreateSchema(SQLModel):
name: str
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
engine = create_async_engine(DATABASE_URL, echo=True)
session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
And, finally, your database connection
from sqlmodel import SQLModel, Field
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession
class Item(SQLModel):
__tablename__ = 'items'
id: int = Field(primary_key=True)
name: str
class ItemCreateSchema(SQLModel):
name: str
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
engine = create_async_engine(DATABASE_URL, echo=True)
session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
Use crud_router
and include it in your FastAPI
application
from fastcrud import FastCRUD, crud_router
app = FastAPI()
item_router = crud_router(
session=session,
model=Item,
crud=FastCRUD(Item),
create_schema=ItemSchema,
update_schema=ItemSchema,
path="/items",
tags=["Items"]
)
app.include_router(item_router)
By following the above setup, FastCRUD auto-generates CRUD endpoints for your model, accessible through the /docs
route of your FastAPI application.
What's Changed
- Sqlmodel support by @igorbenav in #9
Full Changelog: v0.3.0...v0.4.0
0.3.0
0.3.0 Summary
Added
- Ability to create
CustomEndpointCreator
for more advanced and customized route creation. - Feature to include or exclude specific CRUD endpoints in the
crud_router
usingincluded_methods
anddeleted_methods
parameters. - Tests for all the changes.
- Comprehensive documentation on how to use
CustomEndpointCreator
and selectively include or exclude endpoints.
CustomEndpointCreator
- Feature: Introduces the ability to extend the
EndpointCreator
class. This allows for the creation of custom routes and the addition of complex logic to the API endpoints. - Documentation: Detailed examples and guidelines added on how to implement and use
CustomEndpointCreator
in your projects.
Selective CRUD Operations
- Feature:
crud_router
function now supportsincluded_methods
anddeleted_methods
parameters. This allows developers to specify which CRUD methods should be included or excluded when setting up the router. - Use Cases:
included_methods
: Define specific CRUD operations to be included.deleted_methods
: Exclude certain CRUD operations from being created.
- Documentation: Updated the documentation to include examples and best practices for using these new parameters.
Detailed Changes
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=async_session,
model=MyModel,
crud=FastCRUD(MyModel),
create_schema=CreateMyModel,
update_schema=UpdateMyModel,
endpoint_creator=MyCustomEndpointCreator,
path="/mymodel",
tags=["MyModel"],
included_methods=["create", "read", "update"] # Including selective methods
)
app.include_router(my_router)
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.
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=async_session,
model=MyModel,
crud=FastCRUD(MyModel),
create_schema=CreateMyModel,
update_schema=UpdateMyModel,
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=async_session,
model=MyModel,
crud=FastCRUD(MyModel),
create_schema=CreateMyModel,
update_schema=UpdateMyModel,
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.
Relevant Changes in Documentation
- Advanced Use of EndpointCreator: A new section in the documentation explaining how to extend
EndpointCreator
for advanced route customization. - Selective CRUD Operations: Documentation on how to use
included_methods
anddeleted_methods
for more control over the API endpoints. - Examples and Code Snippets: Added practical examples and code snippets to help developers quickly implement these new features.
- Added Installation
- Added Quick-Start
- Turned community stuff into a
Community
page
What's Changed
- Custom endpoints support by @igorbenav in #8
Full Changelog: v0.2.1...v0.3.0
0.2.1
What's Changed
- type hints fix
- docs improved plus fixes
- descriptions added in automatic endpoints
by @igorbenav in #7
Full Changelog: v0.2.0...v0.2.1