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.
Files changed (59) hide show
  1. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/AUTHORS +1 -0
  2. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/PKG-INFO +1 -1
  3. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/model_manager.py +47 -4
  4. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/pyproject.toml +1 -1
  5. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.flake8 +0 -0
  6. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.github/workflows/docker-test.yml +0 -0
  7. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.github/workflows/python-publish.yml +0 -0
  8. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.gitignore +0 -0
  9. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/.pylintrc +0 -0
  10. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/LICENSE +0 -0
  11. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/Makefile +0 -0
  12. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/README.md +0 -0
  13. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/benefits.md +0 -0
  14. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/db_validation.md +0 -0
  15. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/extension.md +0 -0
  16. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/filtering.md +0 -0
  17. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/index.md +0 -0
  18. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/benefits.md +0 -0
  19. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/db_validation.md +0 -0
  20. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/extension.md +0 -0
  21. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/filtering.md +0 -0
  22. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/index.md +0 -0
  23. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/sorting.md +0 -0
  24. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/transactions.md +0 -0
  25. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/usage.md +0 -0
  26. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/ru/utils.md +0 -0
  27. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/sorting.md +0 -0
  28. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/transactions.md +0 -0
  29. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/usage.md +0 -0
  30. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/docs/utils.md +0 -0
  31. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/.env +0 -0
  32. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/__init__.py +0 -0
  33. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/__init__.py +0 -0
  34. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/api.py +0 -0
  35. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/deps.py +0 -0
  36. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/endpoints/__init__.py +0 -0
  37. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/api/endpoints/child.py +0 -0
  38. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/config.py +0 -0
  39. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/db.py +0 -0
  40. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/main.py +0 -0
  41. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/managers.py +0 -0
  42. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/models.py +0 -0
  43. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/examples/app/schemas.py +0 -0
  44. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/__init__.py +0 -0
  45. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/filters.py +0 -0
  46. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/ordering.py +0 -0
  47. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/fastapi_sqlalchemy_toolkit/utils.py +0 -0
  48. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/mkdocs.yml +0 -0
  49. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/base.txt +0 -0
  50. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/docs.txt +0 -0
  51. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/lint.txt +0 -0
  52. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/requirements/test.txt +0 -0
  53. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/Dockerfile +0 -0
  54. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/__init__.py +0 -0
  55. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/conftest.py +0 -0
  56. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/db.py +0 -0
  57. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/docker-compose.yml +0 -0
  58. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/models.py +0 -0
  59. {fastapi_sqlalchemy_toolkit-0.7.13 → fastapi_sqlalchemy_toolkit-0.7.14}/tests/test_public_methods.py +0 -0
@@ -5,3 +5,4 @@ A list of project contributors:
5
5
  Egor Kondrashov <e.kondr01@gmail.com>
6
6
  Natalia Kamysheva <nataliakamysheva@mail.ru>
7
7
  Buned Kholmatov <https://github.com/sfrvn>
8
+ Arina Negodina <https://github.com/negodinaarina>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi_sqlalchemy_toolkit
3
- Version: 0.7.13
3
+ Version: 0.7.14
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>
@@ -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, overload # noqa: UP035
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
- if table_arg.columns.keys():
72
- self.unique_constraints.append(table_arg.columns.keys())
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
- self.unique_constraints.append(table_arg._pending_colargs)
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,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi_sqlalchemy_toolkit"
7
- version = "0.7.13"
7
+ version = "0.7.14"
8
8
  authors = [
9
9
  { name="Egor Kondrashov", email="e.kondr01@gmail.com" },
10
10
  ]