fastapi-sqlalchemy-toolkit 0.7.3.1__tar.gz → 0.7.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/PKG-INFO +2 -2
  2. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/extension.md +1 -3
  3. fastapi_sqlalchemy_toolkit-0.7.4.1/docs/ru/benefits.md +105 -0
  4. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/usage.md +18 -3
  5. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/fastapi_sqlalchemy_toolkit/model_manager.py +4 -1
  6. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/fastapi_sqlalchemy_toolkit/utils.py +2 -3
  7. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/pyproject.toml +1 -1
  8. fastapi_sqlalchemy_toolkit-0.7.3.1/docs/ru/benefits.md +0 -64
  9. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/.flake8 +0 -0
  10. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/.github/workflows/docker-test.yml +0 -0
  11. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/.github/workflows/python-publish.yml +0 -0
  12. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/.gitignore +0 -0
  13. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/.pylintrc +0 -0
  14. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/AUTHORS +0 -0
  15. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/LICENSE +0 -0
  16. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/Makefile +0 -0
  17. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/README.md +0 -0
  18. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/benefits.md +0 -0
  19. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/db_validation.md +0 -0
  20. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/filtering.md +0 -0
  21. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/index.md +0 -0
  22. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/db_validation.md +0 -0
  23. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/extension.md +0 -0
  24. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/filtering.md +0 -0
  25. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/index.md +0 -0
  26. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/sorting.md +0 -0
  27. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/transactions.md +0 -0
  28. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/ru/utils.md +0 -0
  29. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/sorting.md +0 -0
  30. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/transactions.md +0 -0
  31. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/usage.md +0 -0
  32. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/docs/utils.md +0 -0
  33. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/.env +0 -0
  34. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/__init__.py +0 -0
  35. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/api/__init__.py +0 -0
  36. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/api/api.py +0 -0
  37. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/api/deps.py +0 -0
  38. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/api/endpoints/__init__.py +0 -0
  39. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/api/endpoints/child.py +0 -0
  40. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/config.py +0 -0
  41. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/db.py +0 -0
  42. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/main.py +0 -0
  43. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/managers.py +0 -0
  44. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/models.py +0 -0
  45. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/examples/app/schemas.py +0 -0
  46. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/fastapi_sqlalchemy_toolkit/__init__.py +0 -0
  47. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/fastapi_sqlalchemy_toolkit/filters.py +0 -0
  48. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/fastapi_sqlalchemy_toolkit/ordering.py +0 -0
  49. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/mkdocs.yml +0 -0
  50. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/requirements/base.txt +0 -0
  51. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/requirements/docs.txt +0 -0
  52. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/requirements/lint.txt +0 -0
  53. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/requirements/test.txt +0 -0
  54. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/Dockerfile +0 -0
  55. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/__init__.py +0 -0
  56. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/conftest.py +0 -0
  57. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/db.py +0 -0
  58. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/docker-compose.yml +0 -0
  59. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/models.py +0 -0
  60. {fastapi_sqlalchemy_toolkit-0.7.3.1 → fastapi_sqlalchemy_toolkit-0.7.4.1}/tests/test_public_methods.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: fastapi_sqlalchemy_toolkit
3
- Version: 0.7.3.1
3
+ Version: 0.7.4.1
4
4
  Summary: FastAPI SQLAlchemy Toolkit
5
5
  Project-URL: Homepage, https://github.com/e-kondr01/fastapi-sqlalchemy-toolkit
6
6
  Author-email: Egor Kondrashov <e.kondr01@gmail.com>
@@ -53,9 +53,7 @@ class MyModelManager[MyModel, MyModelCreateSchema, MyModelUpdateSchema](ModelMan
53
53
  return created
54
54
  ```
55
55
 
56
- This approach aligns with the "*Fat Models, Skinny Views*" principle from Django.
57
-
58
- ### Using declarative dilters in non-standard list queries
56
+ ### Using declarative filters in non-standard list queries
59
57
  If you need to retrieve not just a list of objects but also other fields (e.g., the number of child objects)
60
58
  or aggregations, and you also need declarative filtering, you can create a new manager method,
61
59
  calling the `super().get_filter_expression` method within it:
@@ -0,0 +1,105 @@
1
+ # Предпосылки
2
+ Необязательный раздел с примером сокращения количества шаблонного кода при использовании `fastapi_sqlalchemy_toolkit`.
3
+
4
+ Если в эндпоинт `FastAPI` с использованием `SQLAlchemy`
5
+ нужно добавить фильтры по значениям полей при получении списка,
6
+ то код будет выглядеть примерно так:
7
+
8
+ ```python
9
+ from uuid import UUID
10
+
11
+ from fastapi_pagination import Page
12
+ from fastapi_pagination.ext.sqlalchemy import paginate
13
+ from sqlalchemy import select
14
+
15
+ from .deps import Session
16
+ from .models import MyModel, ParentModel
17
+ from .schemas import MyObjectListSchema
18
+
19
+
20
+ @router.get("/my-objects")
21
+ async def get_my_objects(
22
+ session: Session,
23
+ user_id: UUID | None = None,
24
+ name: str | None = None,
25
+ parent_name: str | None = None,
26
+ ) -> Page[MyObjectListSchema]:
27
+ stmt = select(MyModel)
28
+ if user_id is not None:
29
+ stmt = stmt.filter_by(user_id=user_id)
30
+ if name is not None:
31
+ stmt = stmt.filter(MyModel.name.ilike == f"%{name}%")
32
+ if parent_name is not None:
33
+ stmt = stmt.join(MyModel.parent)
34
+ stmt = stmt.filter(ParentModel.name.ilike == f"%{parent_name}%")
35
+ return await paginate(session, stmt)
36
+ ```
37
+
38
+ В `fastapi-sqlalchemy-toolkit` этот эндпоинт выглядит так:
39
+
40
+ ```python
41
+ from app.managers import my_object_manager
42
+
43
+ @router.get("/my-objects")
44
+ async def get_my_objects(
45
+ session: Session,
46
+ user_id: UUID | None = None,
47
+ name: str | None = None,
48
+ parent_name: str | None = None,
49
+ ) -> Page[MyObjectListSchema]:
50
+ return await my_object_manager.paginated_list(
51
+ session,
52
+ user_id=user_id,
53
+ filter_expressions={
54
+ MyObject.name: name,
55
+ MyObjectParent.name: parent_name
56
+ }
57
+ )
58
+ ```
59
+
60
+ Теперь рассмотрим создание объекта, который имеет FK и уникальное поле. Без `fastapi-sqlalchemy-toolkit`:
61
+
62
+ ```python
63
+ @router.post("/my-objects")
64
+ async def create_my_object(
65
+ session: Session, in_obj: MyObjectCreateSchema
66
+ ) -> MyObjectListSchema:
67
+ if in_obj.parent_id:
68
+ parent_exists = (
69
+ await session.execute(select(ParentModel.id).filter_by(id=in_obj.parent_id))
70
+ ).first() is not None
71
+ if not parent_exists:
72
+ raise HTTPException(
73
+ status.HTTP_400_BAD_REQUEST,
74
+ detail=f"Parent with id {in_obj.parent_id} does not exist",
75
+ )
76
+
77
+ slug_exists = (
78
+ await session.execute(select(MyModel.id).filter_by(slug=in_obj.slug))
79
+ ).first() is not None
80
+ if slug_exists:
81
+ raise HTTPException(
82
+ status.HTTP_400_BAD_REQUEST,
83
+ detail=f"MyModel with slug {in_obj.slug} already exists",
84
+ )
85
+
86
+ db_obj = MyModel(**in_obj.model_dump())
87
+ session.add(db_obj)
88
+ await session.commit()
89
+ await session.refresh(db_obj)
90
+ return db_obj
91
+ ```
92
+
93
+ С использованием `fastapi-sqlalchemy-toolkit`:
94
+
95
+ ```python
96
+ @router.post("/my-objects")
97
+ async def create_my_object(
98
+ session: Session, in_obj: MyObjectCreateSchema
99
+ ) -> MyObjectListSchema:
100
+ return await my_object_manager.create(session, in_obj=in_obj)
101
+ ```
102
+
103
+ В обоих случаях, использование `fastapi-sqlalchemy-toolkit` значительно сокращает
104
+ код приложения за счёт внутренней реализации в методах `ModelManager` стандартной логики,
105
+ необходимой при создании REST API.
@@ -1,6 +1,9 @@
1
1
  ### Инициализация ModelManager
2
2
 
3
- Для использования `fastapi-sqlaclhemy-toolkit` необходимо создать экземпляр `ModelManager` для своей модели:
3
+ Для взаимодействия с моделью `SQLAlchemy`, `fastapi-sqlalchemy-toolkit` предоставляет
4
+ класс `ModelManager`. Его методы используются для взаимодействия с БД.
5
+
6
+ Создать экземпляр `ModelManager` для конкретной модели можно следующим образом:
4
7
 
5
8
  ```python
6
9
  from fastapi_sqlalchemy_toolkit import ModelManager
@@ -8,10 +11,17 @@ from fastapi_sqlalchemy_toolkit import ModelManager
8
11
  from .models import MyModel
9
12
  from .schemas import MyModelCreateSchema, MyModelUpdateSchema
10
13
 
11
- my_model_manager = ModelManager[MyModel, MyModelCreateSchema, MyModelUpdateSchema](MyModel)
14
+ my_model_manager = ModelManager[MyModel, MyModelCreateSchema, MyModelUpdateSchema](
15
+ MyModel
16
+ )
12
17
  ```
13
18
 
14
- Атрибут `default_ordering` определяет сортировку по умолчанию при получении списка объектов. В него нужно передать поле основной модели.
19
+ В качестве аргумента передаётся модель `SQLAlchemy`. Кроме того, используется параметризация типов
20
+ класса `ModelManager`. В параметры типа передаётся модель `SQLAlchemy`, `Pydantic` модель для
21
+ создания объекта и `Pydantic` модель для обновления объекта.
22
+
23
+ Атрибут `default_ordering` определяет сортировку по умолчанию при получении списка объектов.
24
+ В него можно передать поле модели:
15
25
 
16
26
  ```python
17
27
  from fastapi_sqlalchemy_toolkit import ModelManager
@@ -38,3 +48,8 @@ my_model_manager = ModelManager[MyModel, MyModelCreateSchema, MyModelUpdateSchem
38
48
  - `count` - получение количества объектов
39
49
  - `update` - обновление объекта; выполняет валидацию значений полей на уровне БД
40
50
  - `delete` - удаление объекта
51
+
52
+ Использование методов `paginated_list` и `paginated_filter`, согласно документации
53
+ `fastapi_pagination`, требует применения `fastapi_pagination.add_pagination`
54
+ к приложению `FastAPI`, и использование `fastapi_pagination.Page` в типизации ответа
55
+ эндпоинта FastAPI.
@@ -847,7 +847,10 @@ class ModelManager(Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
847
847
  stmt = stmt.options(option)
848
848
 
849
849
  if where is not None:
850
- stmt = stmt.where(where)
850
+ if isinstance(where, tuple):
851
+ stmt = stmt.where(*where)
852
+ else:
853
+ stmt = stmt.where(where)
851
854
 
852
855
  return stmt
853
856
 
@@ -2,6 +2,7 @@ from copy import deepcopy
2
2
  from typing import Annotated, Any, Optional, TypeVar
3
3
 
4
4
  import pydantic
5
+ import pydantic_core
5
6
  from fastapi import Query
6
7
 
7
8
  BaseModelT = TypeVar("BaseModelT", bound=pydantic.BaseModel)
@@ -12,9 +13,7 @@ def _make_field_optional(
12
13
  ) -> tuple[Any, pydantic.fields.FieldInfo]:
13
14
  new = deepcopy(field)
14
15
  new.default = (
15
- None
16
- if field.default == pydantic.pydantic_core.PydanticUndefined
17
- else field.default
16
+ None if field.default == pydantic_core.PydanticUndefined else field.default
18
17
  )
19
18
  new.annotation = Optional[field.annotation] # type: ignore # noqa: UP007
20
19
  return (new.annotation, new)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi_sqlalchemy_toolkit"
7
- version = "0.7.3.1"
7
+ version = "0.7.4.1"
8
8
  authors = [
9
9
  { name="Egor Kondrashov", email="e.kondr01@gmail.com" },
10
10
  ]
@@ -1,64 +0,0 @@
1
- # Предпосылки
2
- Необязательный раздел с демо сокращения количества шаблонного кода при использовании `fastapi_sqlalchemy_toolkit`.
3
-
4
- Если в эндпоинт `FastAPI` нужно добавить фильтры по значениям полей, то код будет выглядеть примерно так:
5
-
6
- ```python
7
- from typing import Annotated
8
- from uuid import UUID
9
-
10
- from fastapi import APIRouter, Depends, Response, status
11
- from sqlalchemy import select
12
- from sqlalchemy.ext.asyncio import AsyncSession
13
-
14
- from app.deps import get_async_session
15
- from app.models import MyModel, MyParentModel
16
- from app.schemas import MyObjectListSchema
17
-
18
- router = APIRouter()
19
- Session = Annotated[AsyncSession, Depends(get_async_session)]
20
-
21
-
22
- @router.get("/my-objects")
23
- async def get_my_objects(
24
- session: Session,
25
- user_id: UUID | None = None,
26
- name: str | None = None,
27
- parent_name: str | None = None,
28
- ) -> list[MyObjectListSchema]:
29
- stmt = select(MyModel)
30
- if user_id is not None:
31
- stmt = stmt.filter_by(user_id=user_id)
32
- if name is not None:
33
- stmt = stmt.filter(MyModel.name.ilike == f"%{name}%")
34
- if parent_name is not None:
35
- stmt = stmt.join(MyModel.parent)
36
- stmt = stmt.filter(ParentModel.name.ilike == f"%{parent_name}%")
37
- result = await session.execute(stmt)
38
- return result.scalars().all()
39
- ```
40
- Как можно заметить, для реализации фильтрации необходима дубликация шаблонного кода.
41
-
42
- В `fastapi-sqlalchemy-toolkit` этот эндпоинт выглядит так:
43
-
44
- ```python
45
- from fastapi_sqlalchemy_toolkit import FieldFilter
46
-
47
- from app.managers import my_object_manager
48
-
49
- @router.get("/my-objects")
50
- async def get_my_objects(
51
- session: Session,
52
- user_id: UUID | None = None,
53
- name: str | None = None,
54
- parent_name: str | None = None,
55
- ) -> list[MyObjectListSchema]:
56
- return await my_object_manager.list(
57
- session,
58
- user_id=user_id,
59
- filter_expressions={
60
- MyObject.name: name,
61
- MyObjectParent.name: parent_name
62
- }
63
- )
64
- ```