Skip to content

Releases: igorbenav/fastcrud

0.9.1

19 Mar 04:44
ae42484
Compare
Choose a tag to compare

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

Full Changelog: v0.9.0...v0.9.1

0.9.0

14 Mar 21:54
892a64a
Compare
Choose a tag to compare

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


New Contributors

Full Changelog: v0.8.0...v0.9.0

0.8.0

04 Mar 19:00
61caef3
Compare
Choose a tag to compare

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

20 Feb 09:12
7c947c8
Compare
Choose a tag to compare

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

Full Changelog: v0.6.0...v0.7.0

0.6.0

11 Feb 21:50
648b0bd
Compare
Choose a tag to compare

0.6.0 Summary

Added

  • custom updated_at column name
  • passing crud to crud_router and EndpointCreator now optional
  • exceptions in http_exceptions now also in exceptions 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

03 Feb 23:18
aa6d7b1
Compare
Choose a tag to compare

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

What's Changed

New Contributors

Full Changelog: v0.4.0...v0.5.0

0.4.0

31 Jan 10:29
c42ea9c
Compare
Choose a tag to compare

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

Full Changelog: v0.3.0...v0.4.0

0.3.0

28 Jan 21:56
Compare
Choose a tag to compare

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 using included_methods and deleted_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 supports included_methods and deleted_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 and deleted_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

Full Changelog: v0.2.1...v0.3.0

0.2.1

27 Jan 06:59
Compare
Choose a tag to compare

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

0.2.0

26 Jan 01:06
Compare
Choose a tag to compare