Fastcrud

Latest version: v0.11.1

Safety actively analyzes 623368 Python packages for vulnerabilities to keep your Python projects secure.

Scan your dependencies

Page 1 of 3

0.11.1

Added
- `one_or_none` parameter to FastCRUD `get` method (default `False`)
- `nest_joins` parameter to FastCRUD `get_joined` and `get_multi_joined` (default `False`)

Detailed
___
`get`
By default, the `get` method in `FastCRUD` returns the `first` object matching all the filters it finds.

If you want to ensure the `one_or_none` behavior, you may pass the parameter as `True`:

python
crud.get(
async_session,
one_or_none=True,
category_id=1
)


`get_joined` and `get_multi_joined`
By default, `FastCRUD` joins all the data and returns it in a single dictionary.
Let's define two tables:
python
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)


And join them with `FastCRUD`:

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


We'll get:

javascript
{
"id": 1,
"name": "Example",
"tier_id": 1,
"tier_name": "Free",
}


Now, if you want the joined data in a nested dictionary instead, you may just pass `nest_joins=True`:

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


And you will get:

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


This works for both `get_joined` and `get_multi_joined`.

> \[!WARNING\]
> Note that the final `"_"` in the passed `"tier_"` is stripped.

What's Changed
* Skip count call when possible by JakNowy in https://github.com/igorbenav/fastcrud/pull/51
* refactor: :recycle: reuse of select method in FastCRUD by dubusster in https://github.com/igorbenav/fastcrud/pull/55
* feat: :sparkles: add strict parameter to FastCRUD `get` method by dubusster in https://github.com/igorbenav/fastcrud/pull/54
* Nested responses by igorbenav in https://github.com/igorbenav/fastcrud/pull/56

New Contributors
* JakNowy made their first contribution in https://github.com/igorbenav/fastcrud/pull/51 🎉

**Full Changelog**: https://github.com/igorbenav/fastcrud/compare/v0.11.0...v0.11.1

0.11.0

Added
- multiple primary keys support by dubusster in 31 🎉
- count made optional in `get_multi` and `get_multi_joined` methods
- validation bug when `return_as_model` is `True` fixed
- bug when passing `db_row` to methods fixed
- `valid_methods` bug fixed (raising wrong error type)
- `FastAPI` raised up to `0.111.0`
- test coverage at 100%, workflow and badge added
- changelog in docs

Detailed
___
multiple primary keys support

Now, for a model such as :
python
class MultiPkModel(Base):
__tablename__ = "multi_pk"
id = Column(Integer, primary_key=True)
uuid = Column(String(32), primary_key=True)
name = Column(String, unique=True)

the endpoint creator should give a path like `/multi_pk/get/{id}/{uuid}` for the different endpoints (get, update and delete) that need primary keys to interact with the database. The order of path parameter is given by the column order.

Primary keys named other than `id` are now supported as well.
___
Optional Count

Now, in `get_multi` and `get_multi_joined` you may pass `return_total_count=False` to get the data only, and not the count, like this:

python
crud.get_multi(
db=db,
return_total_count=False,
)


Returning

python
{"data": [...]}


Standard behavior is still `return_total_count=True`, with:

python
crud.get_multi(
db=db
)


Returning

python
{"data": [...], "total_count": 999}


What's Changed
* changelog docs by igorbenav in https://github.com/igorbenav/fastcrud/pull/40
* feat: ✨ add multiple primary keys support by dubusster in https://github.com/igorbenav/fastcrud/pull/31
* Test Coverage at 100% by igorbenav in https://github.com/igorbenav/fastcrud/pull/42
* coverage report workflow by igorbenav in https://github.com/igorbenav/fastcrud/pull/43
* Update README.md code coverage badge by igorbenav in https://github.com/igorbenav/fastcrud/pull/44
* Optional count by igorbenav in https://github.com/igorbenav/fastcrud/pull/45
* Docs update for 0.11.0 by igorbenav in https://github.com/igorbenav/fastcrud/pull/46
* 0.11.0 added to changelog by igorbenav in https://github.com/igorbenav/fastcrud/pull/47
* coverage badge in docs by igorbenav in https://github.com/igorbenav/fastcrud/pull/48


**Full Changelog**: https://github.com/igorbenav/fastcrud/compare/v0.10.0...v0.11.0

0.10.0

Added
- `select` statement by dubusster in 28 🚀
- Support for joined models in `count` method (passing `joins_config`)
- Filters added for joined models (as `filters` parameter in `JoinConfig`)
- type checking workflow (with `mypy`) plus fixes for typing
- linting workflow (with `ruff`)

Detailed
___
Select

python
async def select(
db: AsyncSession,
schema_to_select: Optional[type[BaseModel]] = None,
sort_columns: Optional[Union[str, list[str]]] = None,
sort_orders: Optional[Union[str, list[str]]] = None,
**kwargs: Any
) -> Selectable


This method constructs a SQL Alchemy `Select` statement, offering optional column selection, filtering, and sorting. It's designed for flexibility, allowing you to chain additional SQLAlchemy methods for even more complex queries.

Features:

- **Column Selection**: Specify columns with a Pydantic schema.
- **Sorting**: Define one or more columns for sorting, along with their sort order.
- **Filtering**: Apply filters directly through keyword arguments.
- **Chaining**: Chain with other SQLAlchemy methods for advanced query construction.

Usage Example:

python
stmt = await my_model_crud.select(schema_to_select=MySchema, sort_columns='name', name__like='%example%')
stmt = stmt.where(additional_conditions).limit(10)
results = await db.execute(stmt)


___
Improved Joins

`JoinConfig` is a detailed configuration mechanism for specifying joins between models in FastCRUD queries. It contains the following key attributes:

- **`model`**: The SQLAlchemy model to join.
- **`join_on`**: The condition defining how the join connects to other models.
- **`join_prefix`**: An optional prefix for the joined columns to avoid column name conflicts.
- **`schema_to_select`**: An optional Pydantic schema for selecting specific columns from the joined model.
- **`join_type`**: The type of join (e.g., "left", "inner").
- **`alias`**: An optional SQLAlchemy `AliasedClass` for complex scenarios like self-referential joins or multiple joins on the same model.
- `filters`: An optional dictionary to apply filters directly to the joined model.

Applying Joins in FastCRUD Methods

The `count` Method with Joins

The `count` method can be enhanced with join operations to perform complex aggregate queries. While `count` primarily returns the number of records matching a given condition, introducing joins allows for counting records across related models based on specific relationships and conditions.

Using `JoinConfig`

For join requirements, the `count` method can be invoked with join parameters passed as a list of `JoinConfig` to the `joins_config` parameter:

python
from fastcrud import JoinConfig
Count the number of tasks assigned to users in a specific department
task_count = await task_crud.count(
db=db,
joins_config=[
JoinConfig(
model=User,
join_on=Task.assigned_user_id == User.id
),
JoinConfig(
model=Department,
join_on=User.department_id == Department.id,
filters={"name": "Engineering"}
)
]
)


Fetching Data with `get_joined` and `get_multi_joined`

These methods are essential for retrieving records from a primary model while including related data from one or more joined models. They support both simple and complex joining scenarios, including self-referential joins and many-to-many relationships.

Simple Joins Using Base Parameters

For simpler join requirements, FastCRUD allows specifying join parameters directly:

- **`model`**: The target model to join.
- **`join_on`**: The join condition.
- **`join_type`**: Specifies the SQL join type.
- **`aliased`**: When `True`, uses an alias for the model in the join.
- **`join_prefix`**: Optional prefix for columns from the joined model.
- **`filters`**: Additional filters for the joined model.

Examples of Simple Joining

python
Fetch tasks with user details, specifying a left join
tasks_with_users = await task_crud.get_joined(
db=db,
model=User,
join_on=Task.user_id == User.id,
join_type="left"
)


Complex Joins Using `JoinConfig`

When dealing with more complex join conditions, such as multiple joins, self-referential joins, or needing to specify aliases and filters, `JoinConfig` instances become the norm. They offer granular control over each join's aspects, enabling precise and efficient data retrieval.

python
Fetch users with details from related departments and roles, using aliases for self-referential joins
users = await user_crud.get_multi_joined(
db=db,
schema_to_select=UserSchema,
joins_config=[
JoinConfig(
model=Department,
join_on=User.department_id == Department.id,
join_prefix="dept_"
),
JoinConfig(
model=Role,
join_on=User.role_id == Role.id,
join_prefix="role_"
),
JoinConfig(
model=User,
alias=manager_alias,
join_on=User.manager_id == manager_alias.id,
join_prefix="manager_"
)
]
)


Many-to-Many Relationships with `get_multi_joined`

FastCRUD simplifies dealing with many-to-many relationships by allowing easy fetch operations with joined models. Here, we demonstrate using `get_multi_joined` to handle a many-to-many relationship between `Project` and `Participant` models, linked through an association table.

**Note on Handling Many-to-Many Relationships:**

When using `get_multi_joined` for many-to-many relationships, it's essential to maintain a specific order in your `joins_config`:

1. **First**, specify the main table you're querying from.
2. **Next**, include the association table that links your main table to the other table involved in the many-to-many relationship.
3. **Finally**, specify the other table that is connected via the association table.

This order ensures that the SQL joins are structured correctly to reflect the many-to-many relationship and retrieve the desired data accurately.

!!! TIP

Note that the first one can be the model defined in `FastCRUD(Model)`.

Scenario

Imagine a scenario where projects have multiple participants, and participants can be involved in multiple projects. This many-to-many relationship is facilitated through an association table.

Models

Our models include `Project`, `Participant`, and an association model `ProjectsParticipantsAssociation`:

python
from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

Association table for the many-to-many relationship
projects_participants_association = Table('projects_participants_association', Base.metadata,
Column('project_id', Integer, ForeignKey('projects.id'), primary_key=True),
Column('participant_id', Integer, ForeignKey('participants.id'), primary_key=True)
)

class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
name = Column(String)
description = Column(String)
Relationship to Participant through the association table
participants = relationship("Participant", secondary=projects_participants_association)

class Participant(Base):
__tablename__ = 'participants'
id = Column(Integer, primary_key=True)
name = Column(String)
role = Column(String)
Relationship to Project through the association table
projects = relationship("Project", secondary=projects_participants_association)


Fetching Data with `get_multi_joined`

To fetch projects along with their participants, we utilize `get_multi_joined` with appropriate `JoinConfig` settings:

python
from fastcrud import FastCRUD, JoinConfig

Initialize FastCRUD for the Project model
crud_project = FastCRUD(Project)

Define join conditions and configuration
joins_config = [
JoinConfig(
model=ProjectsParticipantsAssociation,
join_on=Project.id == ProjectsParticipantsAssociation.project_id,
join_type="inner",
join_prefix="pp_"
),
JoinConfig(
model=Participant,
join_on=ProjectsParticipantsAssociation.participant_id == Participant.id,
join_type="inner",
join_prefix="participant_"
)
]

Fetch projects with their participants
projects_with_participants = await crud_project.get_multi_joined(
db_session,
joins_config=joins_config
)

Now, `projects_with_participants['data']` will contain projects along with their participant information.


Practical Tips for Advanced Joins

- **Prefixing**: Always use the `join_prefix` attribute to avoid column name collisions, especially in complex joins involving multiple models or self-referential joins.
- **Aliasing**: Utilize the `alias` attribute for disambiguating joins on the same model or for self-referential joins.
- **Filtering Joined Models**: Apply filters directly to joined models using the `filters` attribute in `JoinConfig` to refine the data set returned by the query.
- **Ordering Joins**: In many-to-many relationships or complex join scenarios, carefully sequence your `JoinConfig` entries to ensure logical and efficient SQL join construction.

What's Changed
* feat: :sparkles: add select statement in FastCRUD by dubusster in https://github.com/igorbenav/fastcrud/pull/28
* Select method changes and docs by igorbenav in https://github.com/igorbenav/fastcrud/pull/32
* Improved Joins by igorbenav in https://github.com/igorbenav/fastcrud/pull/35
* Type checking by igorbenav in https://github.com/igorbenav/fastcrud/pull/36
* Linting by igorbenav in https://github.com/igorbenav/fastcrud/pull/37
* version in pyproject bumped by igorbenav in https://github.com/igorbenav/fastcrud/pull/38

New Contributors
* dubusster made their first contribution in https://github.com/igorbenav/fastcrud/pull/28

**Full Changelog**: https://github.com/igorbenav/fastcrud/compare/v0.9.1...v0.10.0

0.9.1

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`:

python hl_lines="4-5 11 15 19 23" title="Join Configurations with Aliases"
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`:

python hl_lines="17" title="Passing 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 https://github.com/igorbenav/fastcrud/pull/29


**Full Changelog**: https://github.com/igorbenav/fastcrud/compare/v0.9.0...v0.9.1

0.9.0

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:

python hl_lines="1 3-10 12-19" title="Join Configurations"
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:

python hl_lines="10" title="Passing to get_multi_joined"
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 https://github.com/igorbenav/fastcrud/pull/22
* fix: add necessary package import by iridescentGray in https://github.com/igorbenav/fastcrud/pull/23
* Using `get_joined` and `get_multi_joined` for multiple models by igorbenav in https://github.com/igorbenav/fastcrud/pull/26

New Contributors
* iridescentGray made their first contribution in https://github.com/igorbenav/fastcrud/pull/23

**Full Changelog**: https://github.com/igorbenav/fastcrud/compare/v0.8.0...v0.9.0

0.8.0

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:

python
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:

python
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.

Page 1 of 3

© 2024 Safety CLI Cybersecurity Inc. All Rights Reserved.