Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 15 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,25 @@
## 0. About
**FastAPI boilerplate** creates an extendable async API using FastAPI, Pydantic V2, SQLAlchemy 2.0 and PostgreSQL:
- [`FastAPI`](https://fastapi.tiangolo.com): modern Python web framework for building APIs
- [`Pydantic V2`](https://docs.pydantic.dev/2.4/): the most widely used data validation library for Python, rewritten in Rust [`(5x-50x faster)`](https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/)
- [`Pydantic V2`](https://docs.pydantic.dev/2.4/): the most widely used data Python validation library, rewritten in Rust [`(5x-50x faster)`](https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/)
- [`SQLAlchemy 2.0`](https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html): Python SQL toolkit and Object Relational Mapper
- [`PostgreSQL`](https://www.postgresql.org): The World's Most Advanced Open Source Relational Database
- [`Redis`](https://redis.io): Open source, in-memory data store used by millions as a database, cache, streaming engine, and message broker
- [`Redis`](https://redis.io): Open source, in-memory data store used by millions as a cache, message broker and more.
- [`ARQ`](https://arq-docs.helpmanual.io) Job queues and RPC in python with asyncio and redis.
- [`Docker Compose`](https://docs.docker.com/compose/) With a single command, create and start all the services from your configuration.

## 1. Features
- Fully async
- Pydantic V2 and SQLAlchemy 2.0
- User authentication with JWT
- Easy redis caching
- Easy client-side caching
- ARQ integration for task queue
- Efficient querying (only queries what's needed)
- Easily extendable
- Flexible
- Easy running with docker compose
- ⚡️ Fully async
- 🚀 Pydantic V2 and SQLAlchemy 2.0
- 🔐 User authentication with JWT
- 🏬 Easy redis caching
- 👜 Easy client-side caching
- 🚦 ARQ integration for task queue
- ⚙️ Efficient querying (only queries what's needed)
- 👮 FastAPI docs behind authentication and hidden based on the environment
- 🦾 Easily extendable
- 🤸‍♂️ Flexible
- 🚚 Easy running with docker compose

### 1.1 To Do
#### API
Expand All @@ -64,13 +65,7 @@
#### Features
- [ ] Add a Rate Limiter decorator
- [ ] Add mongoDB support
- [ ] Add support in schema_to_select for dict as well as Pydantic Schema

#### Security
- [x] FastAPI docs behind authentication and hidden based on the environment

#### Structure
- [x] Remove python-decouple in favor of starlette.config
- [x] Add support in schema_to_select for a list of column names

#### Tests
- [ ] Add Ruff linter
Expand Down Expand Up @@ -634,7 +629,7 @@ crud_users.db_delete(db=db, username="myusername")
```

#### More Efficient Selecting
For the `get` and `get_multi` methods we have the option to define a `schema_to_select` attribute, which is what actually makes the queries more efficient. When you pass a pydantic schema in `schema_to_select` to the `get` or `get_multi` methods, only the attributes in the schema will be selected.
For the `get` and `get_multi` methods we have the option to define a `schema_to_select` attribute, which is what actually makes the queries more efficient. When you pass a `pydantic schema` (preferred) or a list of the names of the attributes in `schema_to_select` to the `get` or `get_multi` methods, only the attributes in the schema will be selected.
```python
from app.schemas.user import UserRead
# Here it's selecting all of the user's data
Expand Down
8 changes: 4 additions & 4 deletions src/app/crud/crud_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def create(
async def get(
self,
db: AsyncSession,
schema_to_select: Type[BaseModel] | None = None,
schema_to_select: Union[Type[BaseModel], List, None] = None,
**kwargs
) -> Row | None:
"""
Expand All @@ -65,7 +65,7 @@ async def get(
----------
db : AsyncSession
The SQLAlchemy async session.
schema_to_select : Type[BaseModel] | None, optional
schema_to_select : Union[Type[BaseModel], List, None], optional
Pydantic schema for selecting specific columns. Default is None to select all columns.
kwargs : dict
Filters to apply to the query.
Expand All @@ -87,7 +87,7 @@ async def get_multi(
db: AsyncSession,
offset: int = 0,
limit: int = 100,
schema_to_select: Type[BaseModel] | None = None,
schema_to_select: Union[Type[BaseModel], List, None] = None,
**kwargs
) -> List[Row]:
"""
Expand All @@ -101,7 +101,7 @@ async def get_multi(
Number of rows to skip before fetching. Default is 0.
limit : int, optional
Maximum number of rows to fetch. Default is 100.
schema_to_select : Type[BaseModel] | None, optional
schema_to_select : Union[Type[BaseModel], List, None], optional
Pydantic schema for selecting specific columns. Default is None to select all columns.
kwargs : dict
Filters to apply to the query.
Expand Down
10 changes: 7 additions & 3 deletions src/app/crud/helper.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Any, List, Type
from typing import Any, List, Type, Union, Dict

from pydantic import BaseModel

from app.core.database import Base

def _extract_matching_columns_from_schema(model: Type[Base], schema: Type[BaseModel] | None) -> List[Any]:
def _extract_matching_columns_from_schema(model: Type[Base], schema: Union[Type[BaseModel], List, None]) -> List[Any]:
"""
Retrieves a list of ORM column objects from a SQLAlchemy model that match the field names in a given Pydantic schema.

Expand All @@ -22,7 +22,11 @@ def _extract_matching_columns_from_schema(model: Type[Base], schema: Type[BaseMo
"""
column_list = list(model.__table__.columns)
if schema is not None:
schema_fields = schema.model_fields.keys()
if isinstance(schema, list):
schema_fields = schema
else:
schema_fields = schema.model_fields.keys()

column_list = []
for column_name in schema_fields:
if hasattr(model, column_name):
Expand Down