fastapi-sqlalchemy-toolkit 0.6.14__tar.gz → 0.7.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/PKG-INFO +34 -18
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/README.md +33 -17
- fastapi_sqlalchemy_toolkit-0.7.0.1/examples/app/api/deps.py +14 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/api/endpoints/child.py +11 -10
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/config.py +0 -2
- fastapi_sqlalchemy_toolkit-0.7.0.1/examples/app/db.py +7 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/models.py +5 -5
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/fastapi_sqlalchemy_toolkit/__init__.py +1 -1
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/fastapi_sqlalchemy_toolkit/model_manager.py +31 -26
- fastapi_sqlalchemy_toolkit-0.7.0.1/fastapi_sqlalchemy_toolkit/ordering.py +68 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/pyproject.toml +1 -1
- fastapi_sqlalchemy_toolkit-0.6.14/examples/app/db.py +0 -10
- fastapi_sqlalchemy_toolkit-0.6.14/examples/app/deps.py +0 -13
- fastapi_sqlalchemy_toolkit-0.6.14/fastapi_sqlalchemy_toolkit/ordering.py +0 -71
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/.flake8 +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/.github/workflows/python-publish.yml +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/.gitignore +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/.pylintrc +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/AUTHORS +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/LICENSE +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/.env +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/api/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/api/api.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/api/endpoints/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/main.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/managers.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/schemas.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/fastapi_sqlalchemy_toolkit/filters.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/fastapi_sqlalchemy_toolkit/utils.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi_sqlalchemy_toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0.1
|
|
4
4
|
Summary: FastAPI SQLAlchemy Toolkit
|
|
5
5
|
Project-URL: Homepage, https://github.com/e-kondr01/fastapi-sqlalchemy-toolkit
|
|
6
6
|
Author-email: Egor Kondrashov <e.kondr01@gmail.com>
|
|
@@ -34,7 +34,7 @@ REST API и взаимодействии с СУБД через `SQLAlchemy`;
|
|
|
34
34
|
|
|
35
35
|
- Фильтрация с обработкой необязательных параметров запроса (см. раздел **Фильтрация**)
|
|
36
36
|
|
|
37
|
-
- Декларативная сортировка с помощью `
|
|
37
|
+
- Декларативная сортировка с помощью `ordering_depends` (см. раздел **Сортировка**)
|
|
38
38
|
|
|
39
39
|
- Валидация существования внешних ключей
|
|
40
40
|
|
|
@@ -426,37 +426,49 @@ async def get_my_objects(
|
|
|
426
426
|
связанных моделей join'ы будут сделаны автоматически.
|
|
427
427
|
|
|
428
428
|
Для применения декларативной сортировки нужно:
|
|
429
|
-
1. Определить
|
|
430
|
-
|
|
431
|
-
|
|
429
|
+
1. Определить поля, по которым доступна фильтрация.
|
|
430
|
+
|
|
431
|
+
Это может быть либо списком/кортежем полей основной модели:
|
|
432
432
|
|
|
433
433
|
```python
|
|
434
|
-
from app.models import
|
|
434
|
+
from app.models import Child
|
|
435
435
|
|
|
436
436
|
child_ordering_fields = (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
Parent.title,
|
|
440
|
-
Parent.created_at
|
|
437
|
+
Child.title,
|
|
438
|
+
Child.created_at
|
|
441
439
|
)
|
|
442
440
|
```
|
|
443
441
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
442
|
+
В таком случае, будут доступны следующий параметря для сортировки:
|
|
443
|
+
`title`, `-title`, `created_at`, `-created_at`.
|
|
444
|
+
|
|
445
|
+
Дефис первым символом означает направление сортировки по убыванию.
|
|
446
|
+
|
|
447
|
+
Либо можно определить маппинг строковых полей для сортировки
|
|
448
|
+
на соответствующие поля моделей:
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
from app.models import Child, Parent
|
|
452
|
+
|
|
453
|
+
child_ordering_fields = (
|
|
454
|
+
"title": MyModel.title,
|
|
455
|
+
"parent_title": ParentModel.title
|
|
456
|
+
)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
В таком случае, будут доступны следующий параметря для сортировки:
|
|
460
|
+
`title`, `-title`, `parent_title`, `-parent_title`.
|
|
449
461
|
|
|
450
462
|
2. В параметрах энпдоинта передать определённый выше список
|
|
451
|
-
в `
|
|
463
|
+
в `ordering_depends`
|
|
452
464
|
|
|
453
465
|
```python
|
|
454
|
-
from fastapi_sqlalchemy_toolkit import
|
|
466
|
+
from fastapi_sqlalchemy_toolkit import ordering_depends
|
|
455
467
|
|
|
456
468
|
@router.get("/children")
|
|
457
469
|
async def get_child_objects(
|
|
458
470
|
session: Session,
|
|
459
|
-
order_by:
|
|
471
|
+
order_by: ordering_depends(child_ordering_fields)
|
|
460
472
|
) -> list[ChildListSchema]
|
|
461
473
|
...
|
|
462
474
|
```
|
|
@@ -467,6 +479,10 @@ async def get_child_objects(
|
|
|
467
479
|
return await child_manager.list(session=session, order_by=order_by)
|
|
468
480
|
```
|
|
469
481
|
|
|
482
|
+
Если `order_by` передаётся в методы `list` или `paginated_list`,
|
|
483
|
+
и поле для сортировки относится к модели, напрямую связанную с основной,
|
|
484
|
+
то будет выполнен необходимый `join` для применения сортировки.
|
|
485
|
+
|
|
470
486
|
## Транзакции
|
|
471
487
|
|
|
472
488
|
`fastapi-sqlalchemy-toolkit` поддерживает оба подхода к работе с транзакциями `SQAlchemy`.
|
|
@@ -17,7 +17,7 @@ REST API и взаимодействии с СУБД через `SQLAlchemy`;
|
|
|
17
17
|
|
|
18
18
|
- Фильтрация с обработкой необязательных параметров запроса (см. раздел **Фильтрация**)
|
|
19
19
|
|
|
20
|
-
- Декларативная сортировка с помощью `
|
|
20
|
+
- Декларативная сортировка с помощью `ordering_depends` (см. раздел **Сортировка**)
|
|
21
21
|
|
|
22
22
|
- Валидация существования внешних ключей
|
|
23
23
|
|
|
@@ -409,37 +409,49 @@ async def get_my_objects(
|
|
|
409
409
|
связанных моделей join'ы будут сделаны автоматически.
|
|
410
410
|
|
|
411
411
|
Для применения декларативной сортировки нужно:
|
|
412
|
-
1. Определить
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
1. Определить поля, по которым доступна фильтрация.
|
|
413
|
+
|
|
414
|
+
Это может быть либо списком/кортежем полей основной модели:
|
|
415
415
|
|
|
416
416
|
```python
|
|
417
|
-
from app.models import
|
|
417
|
+
from app.models import Child
|
|
418
418
|
|
|
419
419
|
child_ordering_fields = (
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
Parent.title,
|
|
423
|
-
Parent.created_at
|
|
420
|
+
Child.title,
|
|
421
|
+
Child.created_at
|
|
424
422
|
)
|
|
425
423
|
```
|
|
426
424
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
425
|
+
В таком случае, будут доступны следующий параметря для сортировки:
|
|
426
|
+
`title`, `-title`, `created_at`, `-created_at`.
|
|
427
|
+
|
|
428
|
+
Дефис первым символом означает направление сортировки по убыванию.
|
|
429
|
+
|
|
430
|
+
Либо можно определить маппинг строковых полей для сортировки
|
|
431
|
+
на соответствующие поля моделей:
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
from app.models import Child, Parent
|
|
435
|
+
|
|
436
|
+
child_ordering_fields = (
|
|
437
|
+
"title": MyModel.title,
|
|
438
|
+
"parent_title": ParentModel.title
|
|
439
|
+
)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
В таком случае, будут доступны следующий параметря для сортировки:
|
|
443
|
+
`title`, `-title`, `parent_title`, `-parent_title`.
|
|
432
444
|
|
|
433
445
|
2. В параметрах энпдоинта передать определённый выше список
|
|
434
|
-
в `
|
|
446
|
+
в `ordering_depends`
|
|
435
447
|
|
|
436
448
|
```python
|
|
437
|
-
from fastapi_sqlalchemy_toolkit import
|
|
449
|
+
from fastapi_sqlalchemy_toolkit import ordering_depends
|
|
438
450
|
|
|
439
451
|
@router.get("/children")
|
|
440
452
|
async def get_child_objects(
|
|
441
453
|
session: Session,
|
|
442
|
-
order_by:
|
|
454
|
+
order_by: ordering_depends(child_ordering_fields)
|
|
443
455
|
) -> list[ChildListSchema]
|
|
444
456
|
...
|
|
445
457
|
```
|
|
@@ -450,6 +462,10 @@ async def get_child_objects(
|
|
|
450
462
|
return await child_manager.list(session=session, order_by=order_by)
|
|
451
463
|
```
|
|
452
464
|
|
|
465
|
+
Если `order_by` передаётся в методы `list` или `paginated_list`,
|
|
466
|
+
и поле для сортировки относится к модели, напрямую связанную с основной,
|
|
467
|
+
то будет выполнен необходимый `join` для применения сортировки.
|
|
468
|
+
|
|
453
469
|
## Транзакции
|
|
454
470
|
|
|
455
471
|
`fastapi-sqlalchemy-toolkit` поддерживает оба подхода к работе с транзакциями `SQAlchemy`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Annotated, AsyncGenerator
|
|
2
|
+
|
|
3
|
+
from app.db import async_session_factory
|
|
4
|
+
from fastapi import Depends
|
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
|
9
|
+
async_session = async_session_factory() # type: ignore
|
|
10
|
+
async with async_session:
|
|
11
|
+
yield async_session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Session = Annotated[AsyncSession, Depends(get_async_session)]
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from datetime import date
|
|
2
|
-
from typing import Annotated
|
|
3
2
|
from uuid import UUID
|
|
4
3
|
|
|
5
|
-
from app.deps import
|
|
4
|
+
from app.api.deps import Session
|
|
6
5
|
from app.managers import child_manager
|
|
7
6
|
from app.models import Child, Parent
|
|
8
7
|
from app.schemas import (
|
|
@@ -12,26 +11,28 @@ from app.schemas import (
|
|
|
12
11
|
HTTPErrorSchema,
|
|
13
12
|
PatchChildSchema,
|
|
14
13
|
)
|
|
15
|
-
from fastapi import APIRouter,
|
|
16
|
-
from fastapi_pagination import Page
|
|
14
|
+
from fastapi import APIRouter, Response, status
|
|
15
|
+
from fastapi_pagination import Page
|
|
17
16
|
from sqlalchemy import func
|
|
18
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
19
17
|
from sqlalchemy.orm import joinedload
|
|
20
18
|
|
|
21
|
-
from fastapi_sqlalchemy_toolkit import
|
|
19
|
+
from fastapi_sqlalchemy_toolkit import ordering_depends
|
|
22
20
|
|
|
23
21
|
router = APIRouter()
|
|
24
22
|
|
|
25
|
-
Session = Annotated[AsyncSession, Depends(get_async_session)]
|
|
26
|
-
PaginationParams = Annotated[Params, Depends()]
|
|
27
23
|
|
|
28
|
-
children_ordering_fields =
|
|
24
|
+
children_ordering_fields = {
|
|
25
|
+
"title": Child.title,
|
|
26
|
+
"created_at": Child.created_at,
|
|
27
|
+
"parent_title": Parent.title,
|
|
28
|
+
"parent_created_at": Parent.created_at,
|
|
29
|
+
}
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
@router.get("")
|
|
32
33
|
async def get_list(
|
|
33
34
|
session: Session,
|
|
34
|
-
order_by:
|
|
35
|
+
order_by: ordering_depends(children_ordering_fields),
|
|
35
36
|
title: str | None = None,
|
|
36
37
|
slug: str | None = None,
|
|
37
38
|
parent_title: str | None = None,
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/config.py
RENAMED
|
@@ -18,8 +18,6 @@ class Settings(BaseSettings):
|
|
|
18
18
|
POSTGRES_DB: str
|
|
19
19
|
SQLALCHEMY_DATABASE_URL: str | None = None
|
|
20
20
|
|
|
21
|
-
SENTRY_DSN: str | None = None
|
|
22
|
-
|
|
23
21
|
@field_validator("SQLALCHEMY_DATABASE_URL", mode="before")
|
|
24
22
|
def assemble_db_connection_string(
|
|
25
23
|
cls, value: PostgresDsn | None, info: FieldValidationInfo
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
2
|
+
|
|
3
|
+
from .config import settings
|
|
4
|
+
|
|
5
|
+
engine = create_async_engine(settings.SQLALCHEMY_DATABASE_URL, future=True, echo=False)
|
|
6
|
+
|
|
7
|
+
async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/models.py
RENAMED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
from uuid import UUID as _py_uuid
|
|
4
|
-
from uuid import uuid4
|
|
2
|
+
from uuid import UUID, uuid4
|
|
5
3
|
|
|
6
4
|
from sqlalchemy import DateTime, ForeignKey, func
|
|
7
5
|
from sqlalchemy.orm import (
|
|
@@ -14,8 +12,10 @@ from sqlalchemy.orm import (
|
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
class Base(DeclarativeBase):
|
|
17
|
-
id: Mapped[
|
|
18
|
-
|
|
15
|
+
id: Mapped[UUID] = mapped_column(
|
|
16
|
+
primary_key=True,
|
|
17
|
+
default=uuid4,
|
|
18
|
+
server_default=func.gen_random_uuid(),
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
created_at: Mapped[datetime] = mapped_column(
|
|
@@ -15,7 +15,6 @@ from sqlalchemy.sql.functions import Function
|
|
|
15
15
|
from sqlalchemy.sql.selectable import Exists
|
|
16
16
|
|
|
17
17
|
from .filters import null_query_values
|
|
18
|
-
from .ordering import OrderingField
|
|
19
18
|
|
|
20
19
|
ModelT = TypeVar("ModelT", bound=DeclarativeBase)
|
|
21
20
|
CreateSchemaT = TypeVar("CreateSchemaT", bound=BaseModel)
|
|
@@ -137,7 +136,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
137
136
|
self,
|
|
138
137
|
session: AsyncSession,
|
|
139
138
|
options: List[Any] | Any | None = None,
|
|
140
|
-
order_by:
|
|
139
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
141
140
|
where: Any | None = None,
|
|
142
141
|
base_stmt: Select | None = None,
|
|
143
142
|
**simple_filters: Any,
|
|
@@ -149,7 +148,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
149
148
|
|
|
150
149
|
:param options: параметры для метода .options() загрузчика SQLAlchemy
|
|
151
150
|
|
|
152
|
-
:param order_by: поле для сортировки
|
|
151
|
+
:param order_by: поле для сортировки
|
|
153
152
|
|
|
154
153
|
:param where: выражение, которое будет передано в метод .where() SQLAlchemy
|
|
155
154
|
|
|
@@ -174,7 +173,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
174
173
|
self,
|
|
175
174
|
session: AsyncSession,
|
|
176
175
|
options: List[Any] | Any | None = None,
|
|
177
|
-
order_by:
|
|
176
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
178
177
|
where: Any | None = None,
|
|
179
178
|
base_stmt: Select | None = None,
|
|
180
179
|
**simple_filters: Any,
|
|
@@ -186,7 +185,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
186
185
|
|
|
187
186
|
:param options: параметры для метода .options() загрузчика SQLAlchemy
|
|
188
187
|
|
|
189
|
-
:param order_by: поле для сортировки
|
|
188
|
+
:param order_by: поле для сортировки
|
|
190
189
|
|
|
191
190
|
:param where: выражение, которое будет передано в метод .where() SQLAlchemy
|
|
192
191
|
|
|
@@ -294,7 +293,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
294
293
|
async def paginated_filter(
|
|
295
294
|
self,
|
|
296
295
|
session: AsyncSession,
|
|
297
|
-
order_by:
|
|
296
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
298
297
|
options: List[Any] | Any | None = None,
|
|
299
298
|
where: Any | None = None,
|
|
300
299
|
base_stmt: Select | None = None,
|
|
@@ -306,7 +305,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
306
305
|
|
|
307
306
|
:param session: сессия SQLAlchemy
|
|
308
307
|
|
|
309
|
-
:param order_by: поле для сортировки
|
|
308
|
+
:param order_by: поле для сортировки
|
|
310
309
|
|
|
311
310
|
:param options: параметры для метода .options() загрузчика SQLAlchemy
|
|
312
311
|
|
|
@@ -330,7 +329,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
330
329
|
async def paginated_list(
|
|
331
330
|
self,
|
|
332
331
|
session: AsyncSession,
|
|
333
|
-
order_by:
|
|
332
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
334
333
|
filter_expressions: dict[InstrumentedAttribute | Callable, Any] | None = None,
|
|
335
334
|
nullable_filter_expressions: dict[InstrumentedAttribute | Callable, Any]
|
|
336
335
|
| None = None,
|
|
@@ -346,7 +345,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
346
345
|
|
|
347
346
|
:param session: сессия SQLAlchemy
|
|
348
347
|
|
|
349
|
-
:param order_by: поле для сортировки
|
|
348
|
+
:param order_by: поле для сортировки
|
|
350
349
|
|
|
351
350
|
:param filter_expressions: словарь, отображающий поля для фильтрации
|
|
352
351
|
на их значения. Фильтрация по None не применяется. См. раздел "фильтрация"
|
|
@@ -405,7 +404,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
405
404
|
async def filter(
|
|
406
405
|
self,
|
|
407
406
|
session: AsyncSession,
|
|
408
|
-
order_by:
|
|
407
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
409
408
|
options: List[Any] | Any | None = None,
|
|
410
409
|
where: Any | None = None,
|
|
411
410
|
unique: bool = False,
|
|
@@ -417,7 +416,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
417
416
|
|
|
418
417
|
:param session: сессия SQLAlchemy
|
|
419
418
|
|
|
420
|
-
:param order_by: поле для сортировки
|
|
419
|
+
:param order_by: поле для сортировки
|
|
421
420
|
|
|
422
421
|
:param options: параметры для метода .options() загрузчика SQLAlchemy
|
|
423
422
|
|
|
@@ -446,7 +445,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
446
445
|
async def list(
|
|
447
446
|
self,
|
|
448
447
|
session: AsyncSession,
|
|
449
|
-
order_by:
|
|
448
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
450
449
|
filter_expressions: dict[InstrumentedAttribute | Callable, Any] | None = None,
|
|
451
450
|
nullable_filter_expressions: dict[InstrumentedAttribute | Callable, Any]
|
|
452
451
|
| None = None,
|
|
@@ -462,7 +461,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
462
461
|
|
|
463
462
|
:param session: сессия SQLAlchemy
|
|
464
463
|
|
|
465
|
-
:param order_by: поле для сортировки
|
|
464
|
+
:param order_by: поле для сортировки
|
|
466
465
|
|
|
467
466
|
:param filter_expressions: словарь, отображающий поля для фильтрации
|
|
468
467
|
на их значения. Фильтрация по None не применяется. См. раздел "фильтрация"
|
|
@@ -525,7 +524,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
525
524
|
self,
|
|
526
525
|
session: AsyncSession,
|
|
527
526
|
options: List[Any] | Any | None = None,
|
|
528
|
-
order_by:
|
|
527
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
529
528
|
where: Any | None = None,
|
|
530
529
|
**simple_filters: Any,
|
|
531
530
|
) -> int:
|
|
@@ -536,7 +535,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
536
535
|
|
|
537
536
|
:param options: параметры для метода .options() загрузчика SQLAlchemy
|
|
538
537
|
|
|
539
|
-
:param order_by: поле для сортировки
|
|
538
|
+
:param order_by: поле для сортировки
|
|
540
539
|
|
|
541
540
|
:param where: выражение, которое будет передано в метод .where() SQLAlchemy
|
|
542
541
|
|
|
@@ -682,7 +681,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
682
681
|
base_query: Select,
|
|
683
682
|
filter_expressions: dict[InstrumentedAttribute | Callable, Any],
|
|
684
683
|
options: List[Any] | None = None,
|
|
685
|
-
order_by:
|
|
684
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
686
685
|
) -> Select:
|
|
687
686
|
"""
|
|
688
687
|
Делает необходимые join'ы при фильтрации и сортировке по полям
|
|
@@ -697,12 +696,16 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
697
696
|
|
|
698
697
|
joined_query = base_query
|
|
699
698
|
models_to_join = set()
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
699
|
+
|
|
700
|
+
if order_by is not None:
|
|
701
|
+
if isinstance(order_by, InstrumentedAttribute):
|
|
702
|
+
ordering_model = order_by.parent.class_
|
|
703
|
+
else:
|
|
704
|
+
ordering_model = order_by._propagate_attrs[
|
|
705
|
+
"plugin_subject"
|
|
706
|
+
]._identity_class
|
|
707
|
+
if ordering_model != self.model:
|
|
708
|
+
models_to_join.add(ordering_model)
|
|
706
709
|
|
|
707
710
|
for filter_expression in filter_expressions.keys():
|
|
708
711
|
if isinstance(filter_expression, InstrumentedAttribute):
|
|
@@ -731,11 +734,13 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
731
734
|
options.append(contains_eager(self.models_to_relationship_attrs[model]))
|
|
732
735
|
return joined_query
|
|
733
736
|
|
|
734
|
-
def get_order_by_expression(
|
|
737
|
+
def get_order_by_expression(
|
|
738
|
+
self, order_by: InstrumentedAttribute | UnaryExpression | None
|
|
739
|
+
):
|
|
735
740
|
if order_by is not None:
|
|
736
741
|
if self.default_ordering is not None:
|
|
737
|
-
return order_by
|
|
738
|
-
return order_by
|
|
742
|
+
return order_by, self.default_ordering
|
|
743
|
+
return order_by
|
|
739
744
|
return self.default_ordering
|
|
740
745
|
|
|
741
746
|
@staticmethod
|
|
@@ -783,7 +788,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
783
788
|
def assemble_stmt(
|
|
784
789
|
self,
|
|
785
790
|
base_stmt: Select | None = None,
|
|
786
|
-
order_by:
|
|
791
|
+
order_by: InstrumentedAttribute | UnaryExpression | None = None,
|
|
787
792
|
options: List[Any] | Any | None = None,
|
|
788
793
|
where: Any | None = None,
|
|
789
794
|
**simple_filters: Any,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Annotated, Sequence
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends
|
|
6
|
+
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
7
|
+
from sqlalchemy.sql.elements import UnaryExpression
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_ordering_enum(
|
|
11
|
+
ordering_fields_mapping: dict[str, InstrumentedAttribute],
|
|
12
|
+
) -> Enum:
|
|
13
|
+
"""
|
|
14
|
+
Собирает Enum из возможных значений сортировки для документации OpenAPI
|
|
15
|
+
"""
|
|
16
|
+
enum_attrs = {}
|
|
17
|
+
for field_name in ordering_fields_mapping:
|
|
18
|
+
enum_attrs[field_name] = field_name
|
|
19
|
+
enum_attrs[f"desc_{field_name}"] = "-" + field_name
|
|
20
|
+
return Enum(str(uuid4()), enum_attrs)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ordering_depends(
|
|
24
|
+
ordering_fields: Sequence[InstrumentedAttribute] | dict[str, InstrumentedAttribute]
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Создаёт fastapi.Depends для квери параметра сортировки по переданным полям модели.
|
|
28
|
+
|
|
29
|
+
:ordering_fields: поля для сортировки.
|
|
30
|
+
Может быть последовательностью полей основной модели:
|
|
31
|
+
ordering_fields=(MyModel.title, MyModel.created_at)
|
|
32
|
+
В таком случае будут доступны параметры сортировки "title", "-title",
|
|
33
|
+
"created_at", "-created_at".
|
|
34
|
+
Дефис первым символом означает сортировку по убыванию.
|
|
35
|
+
Либо может быть маппингом строковых полей для сортировки
|
|
36
|
+
на соответствующие поля моделей:
|
|
37
|
+
ordering_fields={
|
|
38
|
+
"title": MyModel.title,
|
|
39
|
+
"parent_title": ParentModel.title
|
|
40
|
+
}
|
|
41
|
+
В таком случае будут доступны параметры сортировки "title", "-title",
|
|
42
|
+
"parent_title", "-parent_title".
|
|
43
|
+
Если order_by передаётся в методы list или paginated_list,
|
|
44
|
+
и поле для сортировки относится к модели, напрямую связанную с основной,
|
|
45
|
+
то будет выполнен необходимый join для применения сортировки.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if isinstance(ordering_fields, dict):
|
|
49
|
+
ordering_fields_mapping = ordering_fields
|
|
50
|
+
|
|
51
|
+
else:
|
|
52
|
+
ordering_fields_mapping = {field.name: field for field in ordering_fields}
|
|
53
|
+
|
|
54
|
+
def get_ordering_field(
|
|
55
|
+
order_by: get_ordering_enum(ordering_fields_mapping) = None,
|
|
56
|
+
) -> InstrumentedAttribute | UnaryExpression | None:
|
|
57
|
+
if order_by:
|
|
58
|
+
desc = order_by.value.startswith("-")
|
|
59
|
+
field = ordering_fields_mapping[order_by.value.lstrip("-")]
|
|
60
|
+
if desc:
|
|
61
|
+
return field.desc()
|
|
62
|
+
return field
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
return Annotated[
|
|
66
|
+
InstrumentedAttribute | UnaryExpression | None,
|
|
67
|
+
Depends(get_ordering_field),
|
|
68
|
+
]
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
2
|
-
from sqlalchemy.orm import sessionmaker
|
|
3
|
-
|
|
4
|
-
from .config import settings
|
|
5
|
-
|
|
6
|
-
engine = create_async_engine(settings.SQLALCHEMY_DATABASE_URL, future=True, echo=False)
|
|
7
|
-
|
|
8
|
-
async_session_factory = sessionmaker(
|
|
9
|
-
engine, class_=AsyncSession, expire_on_commit=False
|
|
10
|
-
)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from typing import AsyncGenerator
|
|
2
|
-
|
|
3
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
|
-
|
|
5
|
-
from .db import async_session_factory
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
|
9
|
-
async_session: AsyncSession = async_session_factory() # type: ignore
|
|
10
|
-
try:
|
|
11
|
-
yield async_session
|
|
12
|
-
finally:
|
|
13
|
-
await async_session.close()
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from enum import Enum
|
|
3
|
-
from typing import Annotated, Sequence, Type
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
from fastapi import Depends
|
|
7
|
-
from sqlalchemy.orm import DeclarativeBase
|
|
8
|
-
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class OrderingField:
|
|
13
|
-
"""
|
|
14
|
-
Класс для направленный сортировки по полям модели SQLAlchemy
|
|
15
|
-
или полям связанных с ней моделей.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
field: InstrumentedAttribute | str
|
|
19
|
-
desc: bool = False
|
|
20
|
-
|
|
21
|
-
def get_directed_field(self, model: Type[DeclarativeBase]):
|
|
22
|
-
if isinstance(self.field, str):
|
|
23
|
-
field = getattr(model, self.field)
|
|
24
|
-
else:
|
|
25
|
-
field = self.field
|
|
26
|
-
if self.desc:
|
|
27
|
-
return field.desc()
|
|
28
|
-
return field
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_ordering_enum(
|
|
32
|
-
ordering_fields: Sequence[str | InstrumentedAttribute],
|
|
33
|
-
) -> Type[Enum]:
|
|
34
|
-
"""
|
|
35
|
-
Собирает Enum из возможных значений сортировки для документации OpenAPI
|
|
36
|
-
"""
|
|
37
|
-
enum_attrs = {}
|
|
38
|
-
for field in ordering_fields:
|
|
39
|
-
if isinstance(field, str):
|
|
40
|
-
field_name = field
|
|
41
|
-
else:
|
|
42
|
-
# Если передан атрибут модели
|
|
43
|
-
field_name = str(field).lower().replace(".", "_")
|
|
44
|
-
enum_attrs[field_name] = field_name
|
|
45
|
-
enum_attrs[f"desc_{field_name}"] = "-" + field_name
|
|
46
|
-
return Enum(str(uuid4()), enum_attrs)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def ordering_dep(ordering_fields: Sequence[str | InstrumentedAttribute]):
|
|
50
|
-
"""
|
|
51
|
-
Создаёт Depends из FastAPI для квери параметра сортировки по переданным полям.
|
|
52
|
-
Поля могут быть строками (поле основной модели) либо атрибутами моделей SQLAlchemy,
|
|
53
|
-
связанных с основной.
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
def get_ordering_field(
|
|
57
|
-
order_by: get_ordering_enum(ordering_fields) = None,
|
|
58
|
-
) -> OrderingField | None:
|
|
59
|
-
if order_by:
|
|
60
|
-
desc = False
|
|
61
|
-
if order_by.value.startswith("-"):
|
|
62
|
-
desc = True
|
|
63
|
-
for field in ordering_fields:
|
|
64
|
-
if str(field).lower().replace(".", "_") == order_by.value.lstrip("-"):
|
|
65
|
-
return OrderingField(field=field, desc=desc)
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
return Annotated[
|
|
69
|
-
OrderingField | None,
|
|
70
|
-
Depends(get_ordering_field),
|
|
71
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/api/api.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/main.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/managers.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.6.14 → fastapi_sqlalchemy_toolkit-0.7.0.1}/examples/app/schemas.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|