fastapi-sqlalchemy-toolkit 0.7.18__tar.gz → 0.7.20__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 (60) hide show
  1. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/PKG-INFO +1 -1
  2. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/fastapi_sqlalchemy_toolkit/__init__.py +1 -1
  3. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/fastapi_sqlalchemy_toolkit/model_manager.py +93 -33
  4. fastapi_sqlalchemy_toolkit-0.7.20/poetry.lock +498 -0
  5. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/pyproject.toml +1 -1
  6. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/conftest.py +2 -1
  7. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/docker-compose.yml +1 -1
  8. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/models.py +26 -1
  9. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/test_public_methods.py +100 -11
  10. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/.flake8 +0 -0
  11. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/.github/workflows/docker-test.yml +0 -0
  12. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/.github/workflows/python-publish.yml +0 -0
  13. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/.gitignore +0 -0
  14. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/.pylintrc +0 -0
  15. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/AUTHORS +0 -0
  16. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/LICENSE +0 -0
  17. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/Makefile +0 -0
  18. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/README.md +0 -0
  19. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/benefits.md +0 -0
  20. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/db_validation.md +0 -0
  21. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/extension.md +0 -0
  22. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/filtering.md +0 -0
  23. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/index.md +0 -0
  24. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/benefits.md +0 -0
  25. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/db_validation.md +0 -0
  26. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/extension.md +0 -0
  27. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/filtering.md +0 -0
  28. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/index.md +0 -0
  29. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/sorting.md +0 -0
  30. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/transactions.md +0 -0
  31. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/usage.md +0 -0
  32. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/ru/utils.md +0 -0
  33. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/sorting.md +0 -0
  34. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/transactions.md +0 -0
  35. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/usage.md +0 -0
  36. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/docs/utils.md +0 -0
  37. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/.env +0 -0
  38. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/__init__.py +0 -0
  39. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/api/__init__.py +0 -0
  40. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/api/api.py +0 -0
  41. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/api/deps.py +0 -0
  42. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/api/endpoints/__init__.py +0 -0
  43. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/api/endpoints/child.py +0 -0
  44. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/config.py +0 -0
  45. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/db.py +0 -0
  46. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/main.py +0 -0
  47. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/managers.py +0 -0
  48. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/models.py +0 -0
  49. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/examples/app/schemas.py +0 -0
  50. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/fastapi_sqlalchemy_toolkit/filters.py +0 -0
  51. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/fastapi_sqlalchemy_toolkit/ordering.py +0 -0
  52. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/fastapi_sqlalchemy_toolkit/utils.py +0 -0
  53. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/mkdocs.yml +0 -0
  54. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/requirements/base.txt +0 -0
  55. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/requirements/docs.txt +0 -0
  56. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/requirements/lint.txt +0 -0
  57. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/requirements/test.txt +0 -0
  58. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/Dockerfile +0 -0
  59. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/__init__.py +0 -0
  60. {fastapi_sqlalchemy_toolkit-0.7.18 → fastapi_sqlalchemy_toolkit-0.7.20}/tests/db.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi_sqlalchemy_toolkit
3
- Version: 0.7.18
3
+ Version: 0.7.20
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,4 +1,4 @@
1
1
  from .filters import NullableQuery
2
- from .model_manager import ModelManager, sqlalchemy_model_to_dict
2
+ from .model_manager import ModelDict, ModelManager, sqlalchemy_model_to_dict
3
3
  from .ordering import ordering_depends
4
4
  from .utils import CommaSepQuery, comma_sep_q_to_list, make_partial_model
@@ -44,6 +44,12 @@ def sqlalchemy_model_to_dict(model: DeclarativeBase) -> dict:
44
44
  return db_obj_dict
45
45
 
46
46
 
47
+ def _get_model_pk(model: type[DeclarativeBase]) -> InstrumentedAttribute:
48
+ """Получить атрибут первичного ключа для произвольной модели."""
49
+ pk_columns = list(model.__table__.primary_key.columns)
50
+ return getattr(model, pk_columns[0].name)
51
+
52
+
47
53
  class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
48
54
  def __init__(
49
55
  self,
@@ -61,6 +67,15 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
61
67
  self.model = model
62
68
  self.default_ordering = default_ordering
63
69
 
70
+ # Introspect primary key
71
+ pk_columns = list(self.model.__table__.primary_key.columns)
72
+ if len(pk_columns) != 1:
73
+ raise ValueError(
74
+ f"{self.model.__name__} has a composite primary key, "
75
+ f"which is not supported by ModelManager."
76
+ )
77
+ self.pk_column: InstrumentedAttribute = getattr(self.model, pk_columns[0].name)
78
+
64
79
  # str() of FK attr to related model
65
80
  # "parent_id": <class app.models.parent.Parent>
66
81
  # Используется для валидации существования FK при создании/обновлении объекта
@@ -79,7 +94,11 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
79
94
  elif isinstance(table_arg, Index) and table_arg.unique is True:
80
95
  self.unique_indexes.append(table_arg)
81
96
 
97
+ # str of reverse relationship to its Model
98
+ # "parents": <class app.models.parent.Parent>
82
99
  self.reverse_relationships: dict[str, type[ModelT]] = {}
100
+ # str of M2M relationship to its Model
101
+ # "parents": <class app.models.parent.Parent>
83
102
  self.m2m_relationships: dict[str, type[ModelT]] = {}
84
103
  # Model to related attr
85
104
  # Parent : Child.parent
@@ -340,7 +359,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
340
359
  :returns: True если объект существует, иначе False
341
360
  """
342
361
  stmt = self.assemble_stmt(
343
- select(self.model.id), None, options, where, **simple_filters
362
+ select(self.pk_column), None, options, where, **simple_filters
344
363
  )
345
364
  result = await session.execute(stmt)
346
365
  return result.first() is not None
@@ -666,8 +685,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
666
685
 
667
686
  :returns: количество объектов по переданным фильтрам
668
687
  """
669
- # TODO: reference primary key instead of hardcode model.id
670
- stmt = select(func.count(self.model.id))
688
+ stmt = select(func.count(self.pk_column))
671
689
  if where is not None:
672
690
  stmt = stmt.where(where)
673
691
  if simple_filters:
@@ -764,7 +782,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
764
782
 
765
783
  stmt = update(self.model).values(update_data)
766
784
  if ids:
767
- stmt = stmt.where(self.model.id.in_(ids))
785
+ stmt = stmt.where(self.pk_column.in_(ids))
768
786
  elif where is not None:
769
787
  stmt = stmt.where(where)
770
788
  if returning:
@@ -820,7 +838,7 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
820
838
  """
821
839
  stmt = delete(self.model)
822
840
  if ids:
823
- stmt = stmt.where(self.model.id.in_(ids))
841
+ stmt = stmt.where(self.pk_column.in_(ids))
824
842
  elif where is not None:
825
843
  stmt = stmt.where(where)
826
844
  await session.execute(stmt)
@@ -993,7 +1011,9 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
993
1011
  value: Any,
994
1012
  ) -> Exists:
995
1013
  relationship: InstrumentedAttribute = getattr(self.model, field_name)
996
- return relationship.any(self.reverse_relationships[field_name].id.in_(value))
1014
+ related_model = self.reverse_relationships[field_name]
1015
+ related_pk = _get_model_pk(related_model)
1016
+ return relationship.any(related_pk.in_(value))
997
1017
 
998
1018
  def assemble_stmt(
999
1019
  self,
@@ -1051,6 +1071,37 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1051
1071
 
1052
1072
  return stmt
1053
1073
 
1074
+ @staticmethod
1075
+ def get_error_msg(
1076
+ tablename: str,
1077
+ fields: List[str],
1078
+ *,
1079
+ exists: bool = True,
1080
+ field_values: List[Any] | None = None,
1081
+ ) -> str:
1082
+ """
1083
+ Формирует сообщение об ошибке, которое будет возвращаться при ошибках валидации.
1084
+ """
1085
+
1086
+ error_msg = tablename
1087
+ error_msg += " с "
1088
+ if field_values:
1089
+ fields_with_values = []
1090
+ for field, value in zip(fields, field_values, strict=True):
1091
+ fields_with_values.append(f"{field} {value}")
1092
+ error_msg += ", ".join(fields_with_values)
1093
+ else:
1094
+ error_msg += "таким"
1095
+ if len(fields) > 1:
1096
+ error_msg += "и"
1097
+ error_msg += " "
1098
+ error_msg += ", ".join(fields)
1099
+ if exists:
1100
+ error_msg += " уже существует."
1101
+ else:
1102
+ error_msg += " не существует."
1103
+ return error_msg
1104
+
1054
1105
  async def validate_fk_exists(
1055
1106
  self, session: AsyncSession, in_obj: ModelDict
1056
1107
  ) -> None:
@@ -1060,17 +1111,21 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1060
1111
 
1061
1112
  for key in in_obj:
1062
1113
  if key in self.fk_name_to_model and in_obj[key] is not None:
1114
+ related_model = self.fk_name_to_model[key]
1115
+ related_pk = _get_model_pk(related_model)
1063
1116
  related_object_exists = await session.get(
1064
- self.fk_name_to_model[key],
1117
+ related_model,
1065
1118
  in_obj[key],
1066
- options=[load_only(self.fk_name_to_model[key].id)],
1119
+ options=[load_only(related_pk)],
1067
1120
  )
1068
1121
  if not related_object_exists:
1069
1122
  raise HTTPException(
1070
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
1071
- detail=(
1072
- f"{self.fk_name_to_model[key].__tablename__} с id "
1073
- f"{in_obj[key]} не существует."
1123
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
1124
+ detail=self.get_error_msg(
1125
+ related_model.__tablename__,
1126
+ [related_pk.name],
1127
+ field_values=[in_obj[key]],
1128
+ exists=False,
1074
1129
  ),
1075
1130
  )
1076
1131
 
@@ -1093,16 +1148,15 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1093
1148
  continue
1094
1149
  if query:
1095
1150
  object_exists = await self.exists(
1096
- session, **query, where=(self.model.id != in_obj.get("id"))
1151
+ session,
1152
+ **query,
1153
+ where=(self.pk_column != in_obj.get(self.pk_column.name)),
1097
1154
  )
1098
1155
  if object_exists:
1099
- conflicting_fields = ", ".join(unique_constraint)
1100
1156
  raise HTTPException(
1101
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
1102
- detail=(
1103
- f"{self.model.__tablename__} с такими "
1104
- + conflicting_fields
1105
- + " уже существует."
1157
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
1158
+ detail=self.get_error_msg(
1159
+ self.model.__tablename__, unique_constraint
1106
1160
  ),
1107
1161
  )
1108
1162
 
@@ -1127,14 +1181,15 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1127
1181
  object_exists = await self.exists(
1128
1182
  session,
1129
1183
  **attrs_to_check,
1130
- where=(self.model.id != in_obj.get("id")),
1184
+ where=(self.pk_column != in_obj.get(self.pk_column.name)),
1131
1185
  )
1132
1186
  if object_exists:
1133
1187
  raise HTTPException(
1134
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
1135
- detail=(
1136
- f"{self.model.__tablename__} c {column.name} "
1137
- f"{in_obj[column.name]} уже существует"
1188
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
1189
+ detail=self.get_error_msg(
1190
+ self.model.__tablename__,
1191
+ [column.name],
1192
+ field_values=[in_obj[column.name]],
1138
1193
  ),
1139
1194
  )
1140
1195
 
@@ -1161,15 +1216,18 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1161
1216
 
1162
1217
  object_exists = await self.exists(
1163
1218
  session,
1164
- where=and_(*filters, condition, self.model.id != in_obj.get("id")),
1219
+ where=and_(
1220
+ *filters,
1221
+ condition,
1222
+ self.pk_column != in_obj.get(self.pk_column.name),
1223
+ ),
1165
1224
  )
1166
1225
  if object_exists:
1167
1226
  raise HTTPException(
1168
1227
  status_code=status.HTTP_400_BAD_REQUEST,
1169
- detail=(
1170
- f"{self.model.__tablename__} с такими "
1171
- + ", ".join([column.name for column in index.columns])
1172
- + " уже существует."
1228
+ detail=self.get_error_msg(
1229
+ self.model.__tablename__,
1230
+ [column.name for column in index.columns],
1173
1231
  ),
1174
1232
  )
1175
1233
 
@@ -1182,10 +1240,12 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
1182
1240
  related_object = await session.get(related_model, related_object_id)
1183
1241
  if not related_object:
1184
1242
  raise HTTPException(
1185
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
1186
- detail=(
1187
- f"{related_model.__tablename__} с id "
1188
- f"{related_object_id} не существует."
1243
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
1244
+ detail=self.get_error_msg(
1245
+ related_model.__tablename__,
1246
+ ["id"],
1247
+ field_values=[related_object_id],
1248
+ exists=False,
1189
1249
  ),
1190
1250
  )
1191
1251
  related_objects.append(related_object)
@@ -0,0 +1,498 @@
1
+ # This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
2
+
3
+ [[package]]
4
+ name = "annotated-doc"
5
+ version = "0.0.4"
6
+ description = "Document parameters, class attributes, return types, and variables inline, with Annotated."
7
+ optional = false
8
+ python-versions = ">=3.8"
9
+ groups = ["main"]
10
+ files = [
11
+ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"},
12
+ {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"},
13
+ ]
14
+
15
+ [[package]]
16
+ name = "annotated-types"
17
+ version = "0.7.0"
18
+ description = "Reusable constraint types to use with typing.Annotated"
19
+ optional = false
20
+ python-versions = ">=3.8"
21
+ groups = ["main"]
22
+ files = [
23
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
24
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
25
+ ]
26
+
27
+ [[package]]
28
+ name = "anyio"
29
+ version = "4.13.0"
30
+ description = "High-level concurrency and networking framework on top of asyncio or Trio"
31
+ optional = false
32
+ python-versions = ">=3.10"
33
+ groups = ["main"]
34
+ files = [
35
+ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"},
36
+ {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"},
37
+ ]
38
+
39
+ [package.dependencies]
40
+ idna = ">=2.8"
41
+ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
42
+
43
+ [package.extras]
44
+ trio = ["trio (>=0.32.0)"]
45
+
46
+ [[package]]
47
+ name = "fastapi"
48
+ version = "0.135.2"
49
+ description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
50
+ optional = false
51
+ python-versions = ">=3.10"
52
+ groups = ["main"]
53
+ files = [
54
+ {file = "fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5"},
55
+ {file = "fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56"},
56
+ ]
57
+
58
+ [package.dependencies]
59
+ annotated-doc = ">=0.0.2"
60
+ pydantic = ">=2.9.0"
61
+ starlette = ">=0.46.0"
62
+ typing-extensions = ">=4.8.0"
63
+ typing-inspection = ">=0.4.2"
64
+
65
+ [package.extras]
66
+ all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "uvicorn[standard] (>=0.12.0)"]
67
+ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
68
+ standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
69
+
70
+ [[package]]
71
+ name = "fastapi-pagination"
72
+ version = "0.15.12"
73
+ description = "FastAPI pagination"
74
+ optional = false
75
+ python-versions = ">=3.10"
76
+ groups = ["main"]
77
+ files = [
78
+ {file = "fastapi_pagination-0.15.12-py3-none-any.whl", hash = "sha256:758e21157b2844feecb2409072f1433e24f2dc9526ae7906aa1a1b28622a970a"},
79
+ {file = "fastapi_pagination-0.15.12.tar.gz", hash = "sha256:914b41e07b8556de34c12d3568c9b7137eb62a3558420061a4acbebf7e729a08"},
80
+ ]
81
+
82
+ [package.dependencies]
83
+ fastapi = ">=0.93.0"
84
+ pydantic = ">=1.9.1"
85
+ typing-extensions = ">=4.8.0"
86
+
87
+ [package.extras]
88
+ aiosqlite = ["aiosqlite (>=0.19.0,!=0.22.0,!=0.22.1)"]
89
+ asyncpg = ["asyncpg (>=0.24.0)", "sqlalchemy (>=1.3.20)"]
90
+ beanie = ["beanie (>=2.1.0)"]
91
+ bunnet = ["bunnet (>=1.1.0)"]
92
+ databases = ["databases (>=0.6.0)"]
93
+ django = ["databases (>=0.6.0)", "django (<6.0.0)"]
94
+ elasticsearch = ["elasticsearch-dsl (>=8.13.0)"]
95
+ firestore = ["google-cloud-firestore (>=2.19.0)"]
96
+ mongoengine = ["mongoengine (>=0.23.1)"]
97
+ motor = ["motor (>=3.6.0)"]
98
+ odmantic = ["odmantic (>=1.0.2)"]
99
+ orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"]
100
+ ormar = ["ormar (>=0.21.0)"]
101
+ piccolo = ["piccolo (>=0.89)"]
102
+ psycopg = ["psycopg[binary] (>=3.3.2)"]
103
+ scylla-driver = ["scylla-driver (>=3.25.6)"]
104
+ sqlalchemy = ["sqlakeyset (>=2.0.1680321678)", "sqlalchemy (>=1.3.20)"]
105
+ sqlmodel = ["sqlakeyset (>=2.0.1680321678)", "sqlmodel (>=0.0.22)"]
106
+ tortoise = ["tortoise-orm (>=0.22.0)"]
107
+
108
+ [[package]]
109
+ name = "greenlet"
110
+ version = "3.3.2"
111
+ description = "Lightweight in-process concurrent programming"
112
+ optional = false
113
+ python-versions = ">=3.10"
114
+ groups = ["main"]
115
+ markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""
116
+ files = [
117
+ {file = "greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d"},
118
+ {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13"},
119
+ {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e"},
120
+ {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7"},
121
+ {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f"},
122
+ {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef"},
123
+ {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca"},
124
+ {file = "greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f"},
125
+ {file = "greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86"},
126
+ {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f"},
127
+ {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55"},
128
+ {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2"},
129
+ {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358"},
130
+ {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99"},
131
+ {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be"},
132
+ {file = "greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5"},
133
+ {file = "greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd"},
134
+ {file = "greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd"},
135
+ {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd"},
136
+ {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac"},
137
+ {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb"},
138
+ {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070"},
139
+ {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79"},
140
+ {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395"},
141
+ {file = "greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f"},
142
+ {file = "greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643"},
143
+ {file = "greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4"},
144
+ {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986"},
145
+ {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92"},
146
+ {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd"},
147
+ {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab"},
148
+ {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a"},
149
+ {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b"},
150
+ {file = "greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124"},
151
+ {file = "greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327"},
152
+ {file = "greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab"},
153
+ {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082"},
154
+ {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9"},
155
+ {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"},
156
+ {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506"},
157
+ {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce"},
158
+ {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5"},
159
+ {file = "greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492"},
160
+ {file = "greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71"},
161
+ {file = "greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54"},
162
+ {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4"},
163
+ {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff"},
164
+ {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf"},
165
+ {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4"},
166
+ {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727"},
167
+ {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e"},
168
+ {file = "greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a"},
169
+ {file = "greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2"},
170
+ ]
171
+
172
+ [package.extras]
173
+ docs = ["Sphinx", "furo"]
174
+ test = ["objgraph", "psutil", "setuptools"]
175
+
176
+ [[package]]
177
+ name = "idna"
178
+ version = "3.11"
179
+ description = "Internationalized Domain Names in Applications (IDNA)"
180
+ optional = false
181
+ python-versions = ">=3.8"
182
+ groups = ["main"]
183
+ files = [
184
+ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
185
+ {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
186
+ ]
187
+
188
+ [package.extras]
189
+ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
190
+
191
+ [[package]]
192
+ name = "pydantic"
193
+ version = "2.12.5"
194
+ description = "Data validation using Python type hints"
195
+ optional = false
196
+ python-versions = ">=3.9"
197
+ groups = ["main"]
198
+ files = [
199
+ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"},
200
+ {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"},
201
+ ]
202
+
203
+ [package.dependencies]
204
+ annotated-types = ">=0.6.0"
205
+ pydantic-core = "2.41.5"
206
+ typing-extensions = ">=4.14.1"
207
+ typing-inspection = ">=0.4.2"
208
+
209
+ [package.extras]
210
+ email = ["email-validator (>=2.0.0)"]
211
+ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
212
+
213
+ [[package]]
214
+ name = "pydantic-core"
215
+ version = "2.41.5"
216
+ description = "Core functionality for Pydantic validation and serialization"
217
+ optional = false
218
+ python-versions = ">=3.9"
219
+ groups = ["main"]
220
+ files = [
221
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"},
222
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"},
223
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"},
224
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"},
225
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"},
226
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"},
227
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"},
228
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"},
229
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"},
230
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"},
231
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"},
232
+ {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"},
233
+ {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"},
234
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"},
235
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"},
236
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"},
237
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"},
238
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"},
239
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"},
240
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"},
241
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"},
242
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"},
243
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"},
244
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"},
245
+ {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"},
246
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"},
247
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"},
248
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"},
249
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"},
250
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"},
251
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"},
252
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"},
253
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"},
254
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"},
255
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"},
256
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"},
257
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"},
258
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"},
259
+ {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"},
260
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"},
261
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"},
262
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"},
263
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"},
264
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"},
265
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"},
266
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"},
267
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"},
268
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"},
269
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"},
270
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"},
271
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"},
272
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"},
273
+ {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"},
274
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"},
275
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"},
276
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"},
277
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"},
278
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"},
279
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"},
280
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"},
281
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"},
282
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"},
283
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"},
284
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"},
285
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"},
286
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"},
287
+ {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"},
288
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"},
289
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"},
290
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"},
291
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"},
292
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"},
293
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"},
294
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"},
295
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"},
296
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"},
297
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"},
298
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"},
299
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"},
300
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"},
301
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"},
302
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"},
303
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"},
304
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"},
305
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"},
306
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"},
307
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"},
308
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"},
309
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"},
310
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"},
311
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"},
312
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"},
313
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"},
314
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"},
315
+ {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"},
316
+ {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"},
317
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"},
318
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"},
319
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"},
320
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"},
321
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"},
322
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"},
323
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"},
324
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"},
325
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"},
326
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"},
327
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"},
328
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"},
329
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"},
330
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"},
331
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"},
332
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"},
333
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"},
334
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"},
335
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"},
336
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"},
337
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"},
338
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"},
339
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"},
340
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"},
341
+ {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"},
342
+ ]
343
+
344
+ [package.dependencies]
345
+ typing-extensions = ">=4.14.1"
346
+
347
+ [[package]]
348
+ name = "sqlalchemy"
349
+ version = "2.0.48"
350
+ description = "Database Abstraction Library"
351
+ optional = false
352
+ python-versions = ">=3.7"
353
+ groups = ["main"]
354
+ files = [
355
+ {file = "sqlalchemy-2.0.48-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89"},
356
+ {file = "sqlalchemy-2.0.48-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0"},
357
+ {file = "sqlalchemy-2.0.48-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd"},
358
+ {file = "sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29"},
359
+ {file = "sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0"},
360
+ {file = "sqlalchemy-2.0.48-cp310-cp310-win32.whl", hash = "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018"},
361
+ {file = "sqlalchemy-2.0.48-cp310-cp310-win_amd64.whl", hash = "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76"},
362
+ {file = "sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc"},
363
+ {file = "sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c"},
364
+ {file = "sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7"},
365
+ {file = "sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d"},
366
+ {file = "sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571"},
367
+ {file = "sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617"},
368
+ {file = "sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c"},
369
+ {file = "sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b"},
370
+ {file = "sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb"},
371
+ {file = "sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894"},
372
+ {file = "sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9"},
373
+ {file = "sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e"},
374
+ {file = "sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99"},
375
+ {file = "sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a"},
376
+ {file = "sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4"},
377
+ {file = "sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f"},
378
+ {file = "sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed"},
379
+ {file = "sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658"},
380
+ {file = "sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8"},
381
+ {file = "sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131"},
382
+ {file = "sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2"},
383
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae"},
384
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb"},
385
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b"},
386
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121"},
387
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485"},
388
+ {file = "sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79"},
389
+ {file = "sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd"},
390
+ {file = "sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f"},
391
+ {file = "sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b"},
392
+ {file = "sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0"},
393
+ {file = "sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2"},
394
+ {file = "sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6"},
395
+ {file = "sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0"},
396
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241"},
397
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0"},
398
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3"},
399
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b"},
400
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f"},
401
+ {file = "sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933"},
402
+ {file = "sqlalchemy-2.0.48-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8649a14caa5f8a243628b1d61cf530ad9ae4578814ba726816adb1121fc493e"},
403
+ {file = "sqlalchemy-2.0.48-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bb85c546591569558571aa1b06aba711b26ae62f111e15e56136d69920e1616"},
404
+ {file = "sqlalchemy-2.0.48-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6b764fb312bd35e47797ad2e63f0d323792837a6ac785a4ca967019357d2bc7"},
405
+ {file = "sqlalchemy-2.0.48-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7c998f2ace8bf76b453b75dbcca500d4f4b9dd3908c13e89b86289b37784848b"},
406
+ {file = "sqlalchemy-2.0.48-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d64177f443594c8697369c10e4bbcac70ef558e0f7921a1de7e4a3d1734bcf67"},
407
+ {file = "sqlalchemy-2.0.48-cp38-cp38-win32.whl", hash = "sha256:01f6bbd4308b23240cf7d3ef117557c8fd097ec9549d5d8a52977544e35b40ad"},
408
+ {file = "sqlalchemy-2.0.48-cp38-cp38-win_amd64.whl", hash = "sha256:858e433f12b0e5b3ed2f8da917433b634f4937d0e8793e5cb33c54a1a01df565"},
409
+ {file = "sqlalchemy-2.0.48-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4599a95f9430ae0de82b52ff0d27304fe898c17cb5f4099f7438a51b9998ac77"},
410
+ {file = "sqlalchemy-2.0.48-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f27f9da0a7d22b9f981108fd4b62f8b5743423388915a563e651c20d06c1f457"},
411
+ {file = "sqlalchemy-2.0.48-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fcccbbc0c13c13702c471da398b8cd72ba740dca5859f148ae8e0e8e0d3e7e"},
412
+ {file = "sqlalchemy-2.0.48-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a5b429eb84339f9f05e06083f119ad814e6d85e27ecbdf9c551dfdbb128eaf8a"},
413
+ {file = "sqlalchemy-2.0.48-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bcb8ebbf2e2c36cfe01a94f2438012c6a9d494cf80f129d9753bcdf33bfc35a6"},
414
+ {file = "sqlalchemy-2.0.48-cp39-cp39-win32.whl", hash = "sha256:e214d546c8ecb5fc22d6e6011746082abf13a9cf46eefb45769c7b31407c97b5"},
415
+ {file = "sqlalchemy-2.0.48-cp39-cp39-win_amd64.whl", hash = "sha256:b8fc3454b4f3bd0a368001d0e968852dad45a873f8b4babd41bc302ec851a099"},
416
+ {file = "sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096"},
417
+ {file = "sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7"},
418
+ ]
419
+
420
+ [package.dependencies]
421
+ greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
422
+ typing-extensions = ">=4.6.0"
423
+
424
+ [package.extras]
425
+ aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"]
426
+ aioodbc = ["aioodbc", "greenlet (>=1)"]
427
+ aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"]
428
+ asyncio = ["greenlet (>=1)"]
429
+ asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"]
430
+ mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"]
431
+ mssql = ["pyodbc"]
432
+ mssql-pymssql = ["pymssql"]
433
+ mssql-pyodbc = ["pyodbc"]
434
+ mypy = ["mypy (>=0.910)"]
435
+ mysql = ["mysqlclient (>=1.4.0)"]
436
+ mysql-connector = ["mysql-connector-python"]
437
+ oracle = ["cx_oracle (>=8)"]
438
+ oracle-oracledb = ["oracledb (>=1.0.1)"]
439
+ postgresql = ["psycopg2 (>=2.7)"]
440
+ postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"]
441
+ postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
442
+ postgresql-psycopg = ["psycopg (>=3.0.7)"]
443
+ postgresql-psycopg2binary = ["psycopg2-binary"]
444
+ postgresql-psycopg2cffi = ["psycopg2cffi"]
445
+ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
446
+ pymysql = ["pymysql"]
447
+ sqlcipher = ["sqlcipher3_binary"]
448
+
449
+ [[package]]
450
+ name = "starlette"
451
+ version = "1.0.0"
452
+ description = "The little ASGI library that shines."
453
+ optional = false
454
+ python-versions = ">=3.10"
455
+ groups = ["main"]
456
+ files = [
457
+ {file = "starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"},
458
+ {file = "starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149"},
459
+ ]
460
+
461
+ [package.dependencies]
462
+ anyio = ">=3.6.2,<5"
463
+ typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""}
464
+
465
+ [package.extras]
466
+ full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
467
+
468
+ [[package]]
469
+ name = "typing-extensions"
470
+ version = "4.15.0"
471
+ description = "Backported and Experimental Type Hints for Python 3.9+"
472
+ optional = false
473
+ python-versions = ">=3.9"
474
+ groups = ["main"]
475
+ files = [
476
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
477
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
478
+ ]
479
+
480
+ [[package]]
481
+ name = "typing-inspection"
482
+ version = "0.4.2"
483
+ description = "Runtime typing introspection tools"
484
+ optional = false
485
+ python-versions = ">=3.9"
486
+ groups = ["main"]
487
+ files = [
488
+ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"},
489
+ {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"},
490
+ ]
491
+
492
+ [package.dependencies]
493
+ typing-extensions = ">=4.12.0"
494
+
495
+ [metadata]
496
+ lock-version = "2.1"
497
+ python-versions = ">=3.11"
498
+ content-hash = "5ea624ba4e2cdec1dac2c2727a97078def10ed0e614e716d8bb925e8cbd7245f"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi_sqlalchemy_toolkit"
7
- version = "0.7.18"
7
+ version = "0.7.20"
8
8
  authors = [
9
9
  { name="Egor Kondrashov", email="e.kondr01@gmail.com" },
10
10
  ]
@@ -4,13 +4,14 @@ import pytest
4
4
  from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession, AsyncTransaction
5
5
 
6
6
  from tests.db import async_session_factory, engine
7
- from tests.models import Base
7
+ from tests.models import Base, CustomPKBase
8
8
 
9
9
 
10
10
  @pytest.fixture(autouse=True)
11
11
  async def create_metadata():
12
12
  async with engine.begin() as conn:
13
13
  await conn.run_sync(Base.metadata.create_all)
14
+ await conn.run_sync(CustomPKBase.metadata.create_all)
14
15
 
15
16
 
16
17
  @pytest.fixture(scope="session")
@@ -5,7 +5,7 @@ services:
5
5
  postgres:
6
6
  image: postgres:15.2
7
7
  expose:
8
- - "5430:5432"
8
+ - "5432"
9
9
  environment:
10
10
  - POSTGRES_DB=toolkit-test
11
11
  - POSTGRES_USER=postgres
@@ -1,7 +1,6 @@
1
1
  from datetime import datetime
2
2
  from uuid import UUID, uuid4
3
3
 
4
- from fastapi_sqlalchemy_toolkit.model_manager import ModelManager
5
4
  from pydantic import BaseModel
6
5
  from sqlalchemy import Column, DateTime, ForeignKey, Table, UniqueConstraint, func
7
6
  from sqlalchemy.orm import (
@@ -12,6 +11,8 @@ from sqlalchemy.orm import (
12
11
  relationship,
13
12
  )
14
13
 
14
+ from fastapi_sqlalchemy_toolkit.model_manager import ModelManager
15
+
15
16
 
16
17
  class Base(DeclarativeBase):
17
18
  id: Mapped[UUID] = mapped_column(
@@ -87,3 +88,27 @@ child_manager = ModelManager[Child, ChildSchema, ChildSchema](
87
88
  )
88
89
  parent_manager = ModelManager[Parent, ParentSchema, ParentSchema](Parent)
89
90
  category_manager = ModelManager[Category, CategorySchema, CategorySchema](Category)
91
+
92
+
93
+ class CustomPKBase(DeclarativeBase):
94
+ @declared_attr.directive
95
+ def __tablename__(cls) -> str:
96
+ return cls.__name__.lower()
97
+
98
+
99
+ class Item(CustomPKBase):
100
+ item_id: Mapped[UUID] = mapped_column(
101
+ primary_key=True,
102
+ default=uuid4,
103
+ server_default=func.gen_random_uuid(),
104
+ )
105
+ name: Mapped[str] = mapped_column(unique=True)
106
+ description: Mapped[str | None]
107
+
108
+
109
+ class ItemSchema(BaseModel):
110
+ name: str
111
+ description: str | None = None
112
+
113
+
114
+ item_manager = ModelManager[Item, ItemSchema, ItemSchema](Item)
@@ -11,10 +11,12 @@ from tests.models import (
11
11
  Category,
12
12
  CategorySchema,
13
13
  Child,
14
+ ItemSchema,
14
15
  Parent,
15
16
  ParentSchema,
16
17
  category_manager,
17
18
  child_manager,
19
+ item_manager,
18
20
  parent_manager,
19
21
  )
20
22
 
@@ -29,9 +31,9 @@ async def test_get(session: AsyncSession):
29
31
  category_to_check = await session.execute(
30
32
  select(Category).where(Category.title == "test-get-category-title")
31
33
  )
32
- assert (
33
- category == category_to_check.scalars().first()
34
- ), "Got not equal to object in database"
34
+ assert category == category_to_check.scalars().first(), (
35
+ "Got not equal to object in database"
36
+ )
35
37
 
36
38
  nonexistent = await session.execute(
37
39
  select(Category).where(Category.title == "nonexistent-test-get-category-title")
@@ -277,9 +279,9 @@ async def test_create(session: AsyncSession):
277
279
  category_to_check = await session.execute(
278
280
  select(Category).where(Category.title == "test-create-category-title")
279
281
  )
280
- assert (
281
- created == category_to_check.scalars().first()
282
- ), "Created not equal to object in database"
282
+ assert created == category_to_check.scalars().first(), (
283
+ "Created not equal to object in database"
284
+ )
283
285
 
284
286
 
285
287
  async def test_create_unique_filed_validation(session: AsyncSession):
@@ -288,7 +290,7 @@ async def test_create_unique_filed_validation(session: AsyncSession):
288
290
  )
289
291
  with pytest.raises(
290
292
  HTTPException,
291
- match="422: category c title test-create-category-title уже существует",
293
+ match="422: category с title test-create-category-title уже существует.",
292
294
  ):
293
295
  await category_manager.create(
294
296
  session=session, title="test-create-category-title"
@@ -337,9 +339,9 @@ async def test_update(session: AsyncSession):
337
339
  category_to_check = await session.execute(
338
340
  select(Category).where(Category.title == "UPDATED-test-update-category-title")
339
341
  )
340
- assert (
341
- updated == category_to_check.scalars().first()
342
- ), "Updated not equal to object in database"
342
+ assert updated == category_to_check.scalars().first(), (
343
+ "Updated not equal to object in database"
344
+ )
343
345
 
344
346
 
345
347
  async def test_update_unique_filed_validation(session: AsyncSession):
@@ -353,7 +355,7 @@ async def test_update_unique_filed_validation(session: AsyncSession):
353
355
  )
354
356
  with pytest.raises(
355
357
  HTTPException,
356
- match="422: category c title test-category-title1 уже существует",
358
+ match="422: category с title test-category-title1 уже существует.",
357
359
  ):
358
360
  await category_manager.update(
359
361
  session=session,
@@ -867,3 +869,90 @@ async def test_null_filtration(session: AsyncSession):
867
869
 
868
870
  assert len(parents) == 2
869
871
  assert parents == parents_list.scalars().all()
872
+
873
+
874
+ # ########################################################################
875
+ # Tests for models with non-standard primary key name
876
+ # ########################################################################
877
+
878
+
879
+ async def test_custom_pk_create(session: AsyncSession):
880
+ item = await item_manager.create(
881
+ session, ItemSchema(name="item-1", description="desc")
882
+ )
883
+ assert item.item_id is not None
884
+ assert item.name == "item-1"
885
+
886
+
887
+ async def test_custom_pk_get(session: AsyncSession):
888
+ item = await item_manager.create(session, ItemSchema(name="item-get"))
889
+ fetched = await item_manager.get(session, name="item-get")
890
+ assert fetched.item_id == item.item_id
891
+
892
+
893
+ async def test_custom_pk_get_or_404(session: AsyncSession):
894
+ item = await item_manager.create(session, ItemSchema(name="item-404"))
895
+ fetched = await item_manager.get_or_404(session, name="item-404")
896
+ assert fetched.item_id == item.item_id
897
+
898
+ with pytest.raises(HTTPException) as exc_info:
899
+ await item_manager.get_or_404(session, name="nonexistent")
900
+ assert exc_info.value.status_code == 404
901
+
902
+
903
+ async def test_custom_pk_exists(session: AsyncSession):
904
+ await item_manager.create(session, ItemSchema(name="item-exists"))
905
+ assert await item_manager.exists(session, name="item-exists") is True
906
+ assert await item_manager.exists(session, name="nonexistent") is False
907
+
908
+
909
+ async def test_custom_pk_count(session: AsyncSession):
910
+ await item_manager.create(session, ItemSchema(name="item-count-1"))
911
+ await item_manager.create(session, ItemSchema(name="item-count-2"))
912
+ count = await item_manager.count(session)
913
+ assert count == 2
914
+
915
+
916
+ async def test_custom_pk_update(session: AsyncSession):
917
+ item = await item_manager.create(session, ItemSchema(name="item-upd"))
918
+ updated = await item_manager.update(session, item, ItemSchema(name="item-upd-new"))
919
+ assert updated.name == "item-upd-new"
920
+ assert updated.item_id == item.item_id
921
+
922
+
923
+ async def test_custom_pk_delete(session: AsyncSession):
924
+ item = await item_manager.create(session, ItemSchema(name="item-del"))
925
+ await item_manager.delete(session, item)
926
+ assert await item_manager.exists(session, name="item-del") is False
927
+
928
+
929
+ async def test_custom_pk_bulk_update(session: AsyncSession):
930
+ item1 = await item_manager.create(session, ItemSchema(name="bulk-upd-1"))
931
+ item2 = await item_manager.create(session, ItemSchema(name="bulk-upd-2"))
932
+ results = await item_manager.bulk_update(
933
+ session, ids=[item1.item_id, item2.item_id], description="bulk-desc"
934
+ )
935
+ assert len(results) == 2
936
+ assert all(r.description == "bulk-desc" for r in results)
937
+
938
+
939
+ async def test_custom_pk_bulk_delete(session: AsyncSession):
940
+ item1 = await item_manager.create(session, ItemSchema(name="bulk-del-1"))
941
+ item2 = await item_manager.create(session, ItemSchema(name="bulk-del-2"))
942
+ await item_manager.bulk_delete(session, ids=[item1.item_id, item2.item_id])
943
+ assert await item_manager.count(session) == 0
944
+
945
+
946
+ async def test_custom_pk_unique_validation(session: AsyncSession):
947
+ await item_manager.create(session, ItemSchema(name="unique-item"))
948
+ with pytest.raises(HTTPException) as exc_info:
949
+ await item_manager.create(session, ItemSchema(name="unique-item"))
950
+ assert exc_info.value.status_code == 422
951
+
952
+
953
+ async def test_custom_pk_unique_validation_on_update(session: AsyncSession):
954
+ item1 = await item_manager.create(session, ItemSchema(name="upd-unique-1"))
955
+ await item_manager.create(session, ItemSchema(name="upd-unique-2"))
956
+ with pytest.raises(HTTPException) as exc_info:
957
+ await item_manager.update(session, item1, ItemSchema(name="upd-unique-2"))
958
+ assert exc_info.value.status_code == 422