Skip to content

Releases: igorbenav/fastcrud

0.15.5

26 Dec 02:23
e995a3c
Compare
Choose a tag to compare
  • Get multi with return_as_model is now properly typed
  • Filter with a UUID that is not a primary key now working
  • Update with not found record now raise error as previously defined by warning
  • response model working properly in swagger

What's Changed

Full Changelog: v0.15.4...v0.15.5

0.15.4

23 Dec 09:19
7b9a4d1
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.15.3...v0.15.4

0.15.3

23 Dec 03:39
3984fc4
Compare
Choose a tag to compare

Fix get multi joined issue with repetition and wrong count

What's Changed

Full Changelog: v0.15.2...v0.15.3

0.15.2

15 Dec 03:13
5a124d4
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.15.1...v0.15.2

0.15.1

19 Sep 01:55
ee19a65
Compare
Choose a tag to compare

0.15.1 Summary

Added

  • Support for fastapi >=0.100

What's Changed

Full Changelog: v0.15.0...v0.15.1

0.15.0

18 Sep 06:04
5c442ec
Compare
Choose a tag to compare

0.15.0 Summary

Added

  • Models and Schemas for Task Management (Batch 3) by @slaarti
  • Models and Schemas for Articles, Authors, and Profiles (Batch 4) by @slaarti
  • update_override Argument to upsert_multi Method by @feluelle
  • Configurable is_deleted Field in Soft Delete Logic by @gal-dahan

Improved

  • Fixed Complex Parameter Filter with between Operator by @wu-clan
  • Fixed Cryptography Package Vulnerability by @igorbenav
  • Resolved Update Column Name Collision in Update Method by @igorbenav

Fixed

  • Vulnerability in cryptography Package updated to cryptography = "^43.0.1" by @igorbenav
  • Update Column Name Collision in the update method by @igorbenav

Documentation Updates

  • Added Documentation for New Models and Schemas by @slaarti
  • Updated upsert_multi Method Documentation with update_override Usage by @feluelle
  • Clarified Endpoint Simplification and Deprecation Notices by @igorbenav

Warnings

  • Deprecation Notice: The _read_paginated endpoint has been removed. Please transition to using _read_items with pagination parameters. Docs here.
  • Deprecation Notice: Handling of Depends is now only callable within _inject_depend. Update your code accordingly.
  • Configuration Change Alert: Endpoints are simplified by default. Adjust your configurations to align with the new defaults. Docs here.

Details


Endpoint Simplification and Deprecation of _read_paginated

Description

To streamline API endpoint configurations, endpoints with empty strings as names are now the standard. Additionally, the _read_paginated endpoint has been removed, with its functionality merged into _read_items.

Changes

  • Simplified Endpoint Configuration: Endpoints can now be defined with empty strings to create cleaner paths.
  • Removed _read_paginated Endpoint: Pagination is now handled via optional parameters in _read_items.

Usage Examples

Paginated Read Example:

curl -X 'GET' \
  'http://localhost:8000/items?page=2&itemsPerPage=10' \
  -H 'accept: application/json'

Non-Paginated Read Example:

curl -X 'GET' \
  'http://localhost:8000/items?offset=0&limit=100' \
  -H 'accept: application/json'

Warnings

Warning

Deprecation Warning: The _read_paginated endpoint is deprecated. Use _read_items with pagination parameters instead.

Warning

Configuration Change: Default endpoint names are now empty strings. Adjust your configurations to match the new defaults.


update_override Argument in upsert_multi Method

Description

The upsert_multi method now includes an update_override argument, giving developers the ability to override the default update logic during upsert operations. This enhancement provides greater flexibility for custom update scenarios, such as utilizing SQL CASE statements or other complex expressions.

Changes

  • update_override Argument: Allows custom update logic in upsert_multi.
  • Dialect Support: Implemented for PostgreSQL, SQLite, and MySQL.
  • Tests: Added comprehensive tests to ensure functionality across different SQL dialects.

Usage Example

from fastcrud import FastCRUD
from sqlalchemy import case
from .models.item import Item
from .database import session as db

crud_items = FastCRUD(Item)

await crud_items.upsert_multi(
    db=db,
    instances=[
        ItemCreateSchema(id=1, name="Item A", price=10),
        ItemCreateSchema(id=2, name="Item B", price=20),
    ],
    update_override={
        "price": case(
            (Item.price.is_(None), db.excluded.price),
            else_=Item.price,
        )
    }
)

Configurable is_deleted Field in Soft Delete Logic

Description

The is_deleted field in the soft delete logic is now optional and configurable. This change allows developers to customize the soft delete behavior per model, providing flexibility in how deletion states are handled.


What's Changed

New Contributors

Full Changelog: v0.14.0...v0.15.0

You may also see this in the docs.

0.14.0

29 Jul 04:24
c056ab2
Compare
Choose a tag to compare

0.14.0 Summary

Added

  • Type-checking support for SQLModel types by @kdcokenny
  • Returning clause to update operations by @feluelle
  • Upsert_multi functionality by @feluelle
  • Simplified endpoint configurations by @JakNowy, streamlining path generation and merging pagination functionalities into a unified _read_items endpoint, promoting more efficient API structure and usage. Details in #105

Improved

  • Comprehensive tests for paginated retrieval of items, maintaining 100% coverage
  • Docker client check before running tests that require Docker by @feluelle

Fixed

  • Vulnerability associated with an outdated cryptography package
  • Return type inconsistency in async session fixtures by @slaarti

Documentation Updates

  • Cleanup of documentation formatting by @slaarti
  • Replacement of the Contributing section in docs with an include to file in repo root by @slaarti
  • Correction of links to advanced filters in docstrings by @slaarti
  • Backfill of docstring fixes across various modules by @slaarti
  • Enhanced filter documentation with new AND and OR clause examples, making complex queries more accessible and understandable.

Models and Schemas Enhancements

  • Introduction of simple and one-off models (Batch 1) by @slaarti
  • Expansion to include models and schemas for Customers, Products, and Orders (Batch 2) by @slaarti

Code Refinements

  • Resolution of missing type specifications in kwargs by @slaarti
  • Collapsed space adjustments for models/schemas in fast_crud.py by @slaarti

Warnings

  • Deprecation Notice: _read_paginated endpoint is set to be deprecated and merged into _read_items. Users are encouraged to transition to the latter, utilizing optional pagination parameters. Full details and usage instructions provided to ensure a smooth transition.
  • Future Changes Alert: Default endpoint names in EndpointCreator are anticipated to be set to empty strings in a forthcoming major release, aligning with simplification efforts. Refer to #67 for more information.

Detailed


Simplified Endpoint Configurations

In an effort to streamline FastCRUD’s API, we have reconfigured endpoint paths to avoid redundancy (great work by @JakNowy). This change allows developers to specify empty strings for endpoint names in the crud_router setup, which prevents the generation of unnecessary // in the paths. The following configurations illustrate how endpoints can now be defined more succinctly:

endpoint_names = {
    "create": "",
    "read": "",
    "update": "",
    "delete": "",
    "db_delete": "",
    "read_multi": "",
    "read_paginated": "get_paginated",
}

Moreover, the _read_paginated logic has been integrated into the _read_items endpoint. This integration means that pagination can now be controlled via page and items_per_page query parameters, offering a unified method for both paginated and non-paginated reads:

  • Paginated read example:
curl -X 'GET' \
  'http://localhost:8000/users/get_multi?page=2&itemsPerPage=10' \
  -H 'accept: application/json'
  • Non-paginated read example:
curl -X 'GET' \
  'http://localhost:8000/users/get_multi?offset=0&limit=100' \
  -H 'accept: application/json'

Warnings

  • Deprecation Warning: The _read_paginated endpoint is slated for deprecation. Developers should transition to using _read_items with the relevant pagination parameters.
  • Configuration Change Alert: In a future major release, default endpoint names in EndpointCreator will be empty strings by default, as discussed in Issue #67.

Advanced Filters Documentation Update

Documentation for advanced filters has been expanded to include comprehensive examples of AND and OR clauses, enhancing the utility and accessibility of complex query constructions.

  • OR clause example:
# Fetch items priced under $5 or above $20
items = await item_crud.get_multi(
    db=db,
    price__or={'lt': 5, 'gt': 20},
)
  • AND clause example:
# Fetch items priced under $20 and over 2 years of warranty
items = await item_crud.get_multi(
    db=db,
    price__lt=20,
    warranty_years__gt=2,
)

Returning Clauses in Update Operations

Description

Users can now retrieve updated records immediately following an update operation. This feature streamlines the process, reducing the need for subsequent retrieval calls and increasing efficiency.

Changes

  • Return Columns: Specify the columns to be returned after the update via the return_columns argument.
  • Schema Selection: Optionally select a Pydantic schema to format the returned data using the schema_to_select argument.
  • Return as Model: Decide if the returned data should be converted into a model using the return_as_model argument.
  • Single or None: Utilize the one_or_none argument to ensure that either a single record is returned or none, in case the conditions do not match any records.

These additions are aligned with existing CRUD API functions, enhancing consistency across the library and making the new features intuitive for users.

Usage Example

Returning Updated Fields

from fastcrud import FastCRUD
from .models.item import Item
from .database import session as db

crud_items = FastCRUD(Item)
updated_item = await crud_items.update(
    db=db,
    object={"price": 9.99},
    price__lt=10,
    return_columns=["price"]
)
# This returns the updated price of the item directly.

Returning Data as a Model

from fastcrud import FastCRUD
from .models.item import Item
from .schemas.item import ItemSchema
from .database import session as db

crud_items = FastCRUD(Item)
updated_item_schema = await crud_items.update(
    db=db,
    object={"price": 9.99},
    price__lt=10,
    schema_to_select=ItemSchema,
    return_as_model=True
)
# This returns the updated item data formatted as an ItemSchema model.

Bulk Upsert Operations with upsert_multi

The upsert_multi method provides the ability to perform bulk upsert operations, which are optimized for different SQL dialects.

Changes

  • Dialect-Optimized SQL: Uses the most efficient SQL commands based on the database's SQL dialect.
  • Support for Multiple Dialects: Includes custom implementations for PostgreSQL, SQLite, and MySQL, with appropriate handling for each's capabilities and limitations.

Usage Example

Upserting Multiple Records

from fastcrud import FastCRUD
from .models.item import Item
from .schemas.item import ItemCreateSchema, ItemSchema
from .database import session as db

crud_items = FastCRUD(Item)
items = await crud_items.upsert_multi(
    db=db,
    instances=[
        ItemCreateSchema(price=9.99),
    ],
    schema_to_select=ItemSchema,
    return_as_model=True,
)
# This will return the upserted data in the form of ItemSchema.

Implementation Details

upsert_multi handles different database dialects:

  • PostgreSQL: Uses ON CONFLICT DO UPDATE.
  • SQLite: Utilizes ON CONFLICT DO UPDATE.
  • MySQL: Implements ON DUPLICATE KEY UPDATE.

Notes

  • MySQL and MariaDB do not support certain advanced features used in other dialects, such as returning values directly after an insert or update operation. This limitation is clearly documented to prevent misuse.

What's Changed

New Contributors

Full Changelog: v0.13.1...v0.14.0

You may also see this in the docs.

0.13.1

22 Jun 05:26
e675910
Compare
Choose a tag to compare

0.13.1 Summary

Added

Fixed

  • Bug where object with null primary key are returned with all fields set to None in nested joins #102

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.

Single parameter filters

Most filter operators require a single string or integer value.

# Fetch items priced between above $5
items = await item_crud.get_multi(
    db=db,
    price__gte=5,
)

Currently supported single parameter filters are:

  • __gt - greater than
  • __lt - less than
  • __gte - greater than or equal to
  • __lte - less than or equal to
  • __ne - not equal
  • __is - used to test True, False and None identity
  • __is_not - negation of "is"
  • __like - SQL "like" search for specific text pattern
  • __notlike - negation of "like"
  • __ilike - case insensitive "like"
  • __notilike - case insensitive "notlike"
  • __startswith - text starts with given string
  • __endswith - text ends with given string
  • __contains - text contains given string
  • __match - database-specific match expression

Complex parameter filters

Some operators require multiple values. They must be passed as a python tuple, list or set.

# Fetch items priced between $5 and $20
items = await item_crud.get_multi(
    db=db,
    price__between=(5, 20),
)
  • __between - between 2 numeric values
  • __in - included in
  • __not_in - not included in

OR parameter filters

More complex OR filters are supported. They must be passed as dictionary, where each key is a library-supported operator to be used in OR expression and values is what get's passed as the parameter.

# Fetch items priced under $5 or above $20
items = await item_crud.get_multi(
    db=db,
    price__or={'lt': 5, 'gt': 20},
)

What's Changed

Full Changelog: v0.13.0...v0.13.1

You may also see this in the docs.

0.13.0

28 May 06:37
fdc3a67
Compare
Choose a tag to compare

0.13.0 Summary

Added

  • Filters in Automatic Endpoints 🎉
  • One-to-many support in joins
  • upsert method in FastCRUD class by @dubusster

Detailed


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 and read_paginated endpoints. 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 yourapp.models import YourModel
from yourapp.schemas import CreateYourModelSchema, UpdateYourModelSchema
from yourapp.database import async_session

# Apply filters using crud_router
app.include_router(
    crud_router(
        session=async_session,
        model=YourModel,
        create_schema=CreateYourModelSchema,
        update_schema=UpdateYourModelSchema,
        filter_config=filter_config,  # Apply the filter configuration
        path="/yourmodel",
        tags=["YourModel"]
    )
)

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:

GET /yourmodel/get_paginated?page=1&itemsPerPage=3&tier_id=1&name=Alice

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

Handling One-to-One and One-to-Many Joins in FastCRUD

FastCRUD provides flexibility in handling one-to-one and one-to-many relationships through get_joined and get_multi_joined methods, along with the ability to specify how joined data should be structured using both the relationship_type (default one-to-one) and the nest_joins (default False) parameters.

One-to-One Relationships

  • get_joined: Fetch a single record and its directly associated record (e.g., a user and their profile).
  • get_multi_joined (with nest_joins=False): Retrieve multiple records, each linked to a single related record from another table (e.g., users and their profiles).
Example

Let's define two tables:

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    tier_id = Column(Integer, ForeignKey("tier.id"))

class Tier(Base):
    __tablename__ = "tier"
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True)

Fetch a user and their tier:

user_tier = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_on=User.tier_id == Tier.id,
    join_type="left",
    join_prefix="tier_",
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example",
    "tier_id": 1,
    "tier_name": "Free"
}
One-to-One Relationship with Nested Joins

To get the joined data in a nested dictionary:

user_tier = await user_crud.get_joined(
    db=db,
    join_model=Tier,
    join_on=User.tier_id == Tier.id,
    join_type="left",
    join_prefix="tier_",
    nest_joins=True,
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example",
    "tier": {
        "id": 1,
        "name": "Free"
    }
}

One-to-Many Relationships

  • get_joined (with nest_joins=True): Retrieve a single record with all its related records nested within it (e.g., a user and all their blog posts).
  • get_multi_joined (with nest_joins=True): Fetch multiple primary records, each with their related records nested (e.g., multiple users and all their blog posts).

Warning

When using nest_joins=True, the performance will always be a bit worse than when using nest_joins=False. For cases where more performance is necessary, consider using nest_joins=False and remodeling your database.

Example

To demonstrate a one-to-many relationship, let's assume User and Post tables:

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary key=True)
    name = Column(String)

class Post(Base):
    __tablename__ = "post"
    id = Column(Integer, primary key=True)
    user_id = Column(Integer, ForeignKey("user.id"))
    content = Column(String)

Fetch a user and all their posts:

user_posts = await user_crud.get_joined(
    db=db,
    join_model=Post,
    join_on=User.id == Post.user_id,
    join_type="left",
    join_prefix="post_",
    nest_joins=True,
    id=1
)

The result will be:

{
    "id": 1,
    "name": "Example User",
    "posts": [
        {
            "id": 101,
            "user_id": 1,
            "content": "First post content"
        },
        {
            "id": 102,
            "user_id": 1,
            "content": "Second post content"
        }
    ]
}

What's Changed

Full Changelog: v0.12.1...v0.13.0

0.12.1

10 May 05:19
8b23064
Compare
Choose a tag to compare

0.12.1 Summary

Added

  • Deprecation Warning for dependency handling.

Detailed


If you pass a sequence of params.Depends type variables to any *_deps parameter in EndpointCreator and crud_router, you will get a warning. Support will be completely removed in 0.15.0.

What's Changed

Full Changelog: v0.12.0...v0.12.1