fastapi-sqlalchemy-toolkit 0.7.13__tar.gz → 0.7.14__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.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/AUTHORS +1 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/PKG-INFO +1 -1
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/model_manager.py +47 -4
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/pyproject.toml +1 -1
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.flake8 +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.github/workflows/docker-test.yml +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.github/workflows/python-publish.yml +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.gitignore +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.pylintrc +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/LICENSE +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/Makefile +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/README.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/benefits.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/db_validation.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/extension.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/filtering.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/index.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/benefits.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/db_validation.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/extension.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/filtering.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/index.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/sorting.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/transactions.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/usage.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/utils.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/sorting.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/transactions.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/usage.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/utils.md +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/.env +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/api.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/deps.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/endpoints/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/endpoints/child.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/config.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/db.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/main.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/managers.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/models.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/schemas.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/filters.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/ordering.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/utils.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/mkdocs.yml +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/base.txt +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/docs.txt +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/lint.txt +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/test.txt +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/Dockerfile +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/__init__.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/conftest.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/db.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/docker-compose.yml +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/models.py +0 -0
- {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/test_public_methods.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ruff: noqa: UP006
|
|
2
2
|
from collections.abc import Callable, Iterable
|
|
3
|
-
from typing import Any, Generic, List, TypeVar
|
|
3
|
+
from typing import Any, Generic, List, TypeVar # noqa: UP035
|
|
4
4
|
|
|
5
5
|
from fastapi import HTTPException, status
|
|
6
6
|
from fastapi_pagination.bases import BasePage
|
|
@@ -65,13 +65,27 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
65
65
|
self.fk_name_to_model: dict[str, type[ModelT]] = {}
|
|
66
66
|
|
|
67
67
|
self.unique_constraints: List[List[str]] = []
|
|
68
|
+
self.nullable_unique_constraints: List[List[str]] = []
|
|
69
|
+
|
|
68
70
|
if hasattr(self.model, "__table_args__"):
|
|
69
71
|
for table_arg in self.model.__table_args__:
|
|
70
72
|
if isinstance(table_arg, UniqueConstraint):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
# Проверяем наличие атрибута postgresql_nulls_not_distinct
|
|
74
|
+
# и его значение
|
|
75
|
+
if table_arg.dialect_kwargs.get("postgresql_nulls_not_distinct"):
|
|
76
|
+
if table_arg.columns.keys():
|
|
77
|
+
self.nullable_unique_constraints.append(
|
|
78
|
+
table_arg.columns.keys()
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
self.nullable_unique_constraints.append(
|
|
82
|
+
table_arg._pending_colargs
|
|
83
|
+
)
|
|
73
84
|
else:
|
|
74
|
-
|
|
85
|
+
if table_arg.columns.keys():
|
|
86
|
+
self.unique_constraints.append(table_arg.columns.keys())
|
|
87
|
+
else:
|
|
88
|
+
self.unique_constraints.append(table_arg._pending_colargs)
|
|
75
89
|
|
|
76
90
|
self.reverse_relationships: dict[str, type[ModelT]] = {}
|
|
77
91
|
self.m2m_relationships: dict[str, type[ModelT]] = {}
|
|
@@ -864,6 +878,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
864
878
|
await self.handle_m2m_fields(session, in_obj)
|
|
865
879
|
await self.validate_unique_fields(session, in_obj, db_obj=db_obj)
|
|
866
880
|
await self.validate_unique_constraints(session, in_obj)
|
|
881
|
+
await self.validate_nullable_unique_constraints(session, in_obj)
|
|
867
882
|
return in_obj
|
|
868
883
|
|
|
869
884
|
def get_select(self, base_stmt: Select | None = None, **_kwargs: Any) -> Select:
|
|
@@ -1092,6 +1107,34 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
|
|
|
1092
1107
|
),
|
|
1093
1108
|
)
|
|
1094
1109
|
|
|
1110
|
+
async def validate_nullable_unique_constraints(
|
|
1111
|
+
self, session: AsyncSession, in_obj: ModelDict
|
|
1112
|
+
) -> None:
|
|
1113
|
+
"""
|
|
1114
|
+
Проверяет, не нарушаются ли nullable UniqueConstraint модели.
|
|
1115
|
+
Поля со значением NULL игнорируются при проверке и не считаются дублированием.
|
|
1116
|
+
"""
|
|
1117
|
+
for unique_constraint in self.nullable_unique_constraints:
|
|
1118
|
+
query = {}
|
|
1119
|
+
if any(in_obj[field] is None for field in unique_constraint):
|
|
1120
|
+
continue
|
|
1121
|
+
|
|
1122
|
+
for field in unique_constraint:
|
|
1123
|
+
query[field] = in_obj[field]
|
|
1124
|
+
object_exists = await self.exists(
|
|
1125
|
+
session, **query, where=(self.model.id != in_obj.get("id"))
|
|
1126
|
+
)
|
|
1127
|
+
if object_exists:
|
|
1128
|
+
conflicting_fields = ", ".join(unique_constraint)
|
|
1129
|
+
raise HTTPException(
|
|
1130
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
1131
|
+
detail=(
|
|
1132
|
+
f"{self.model.__tablename__} с такими "
|
|
1133
|
+
+ conflicting_fields
|
|
1134
|
+
+ " уже существует."
|
|
1135
|
+
),
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1095
1138
|
async def validate_unique_fields(
|
|
1096
1139
|
self,
|
|
1097
1140
|
session: AsyncSession,
|
|
File without changes
|
|
File without changes
|
|
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.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/db_validation.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/db_validation.md
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/extension.md
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/filtering.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/transactions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/transactions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/__init__.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/__init__.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/api.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/main.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/managers.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/models.py
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/schemas.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/base.txt
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/docs.txt
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/lint.txt
RENAMED
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/test.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/docker-compose.yml
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/test_public_methods.py
RENAMED
|
File without changes
|