fastapi-basekit 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/PKG-INFO +23 -19
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/README.md +16 -10
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/repository/base.py +167 -63
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/service/base.py +81 -30
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/__init__.py +2 -1
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/service/base.py +8 -1
- fastapi_basekit-0.3.2/fastapi_basekit/aio/sqlalchemy/session.py +67 -0
- fastapi_basekit-0.3.2/fastapi_basekit/exceptions/__init__.py +3 -0
- fastapi_basekit-0.3.2/fastapi_basekit/exceptions/domain.py +57 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/database.py +7 -20
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/PKG-INFO +23 -19
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/SOURCES.txt +2 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/requires.txt +6 -8
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/pyproject.toml +7 -9
- fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/LICENSE +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/controller/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/controller/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/repository/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/service/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/controller/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/controller/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/permissions/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/permissions/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/controller/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/controller/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/repository/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/repository/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/service/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/controller/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/controller/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/repository/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/repository/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/service/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlmodel/service/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/cli/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/cli/main.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/exceptions/api_exceptions.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/exceptions/handler.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/exceptions → fastapi_basekit-0.3.2/fastapi_basekit/schema}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/schema/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/schema/jwt.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/schema/schema.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/servicios/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/servicios/thrid/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/servicios/thrid/jwt.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/cookiecutter.json +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/hooks/post_gen_project.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/hooks/pre_gen_project.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.env.example +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.gitignore +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Dockerfile +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/LICENSE +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Makefile +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/README.md +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/env.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/script.py.mako +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic.ini +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/schema → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app}/__init__.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api}/__init__.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1}/__init__.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1 → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/auth.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/user.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/routers.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/settings.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/main.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/auth.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/permissions.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/auth.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/enums.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/types.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/user.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories}/__init__.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/repository.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/auth.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/base.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/user.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init_admin.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/auth_service.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/dependency.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/user_service.py +0 -0
- {fastapi_basekit-0.3.0/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services → fastapi_basekit-0.3.2/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils}/__init__.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/exception_handlers.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/security.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/docker-compose.yml +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/pytest.ini +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/requirements.txt +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/dependency_links.txt +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/entry_points.txt +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit.egg-info/top_level.txt +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/setup.cfg +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_api_exceptions.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_base_response.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_base_service.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_controller_auto_permissions.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_crud_beanie_controller.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_crud_controller.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_jwt_service.py +0 -0
- {fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/tests/test_sqlalchemy_base_service_order.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi-basekit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Utilities and base classes for FastAPI async projects (Beanie, SQLAlchemy or SQLModel)
|
|
5
5
|
Author-email: Jerson Moreno <jerson.ml820@hotmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -16,14 +16,13 @@ Requires-Python: >=3.11
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: fastapi>=0.116.1
|
|
19
|
-
Requires-Dist: pydantic<3,>=2.
|
|
19
|
+
Requires-Dist: pydantic<3,>=2.13.0
|
|
20
20
|
Requires-Dist: fastapi-restful[all]>=0.6.0
|
|
21
|
-
Requires-Dist: pyjwt>=2.
|
|
21
|
+
Requires-Dist: pyjwt>=2.12.1
|
|
22
22
|
Provides-Extra: beanie
|
|
23
|
-
Requires-Dist: beanie
|
|
24
|
-
Requires-Dist: motor>=3.3.0; extra == "beanie"
|
|
23
|
+
Requires-Dist: beanie<3,>=2.0; extra == "beanie"
|
|
25
24
|
Provides-Extra: sqlalchemy
|
|
26
|
-
Requires-Dist: SQLAlchemy[asyncio]
|
|
25
|
+
Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "sqlalchemy"
|
|
27
26
|
Requires-Dist: psycopg2>=2.9.0; extra == "sqlalchemy"
|
|
28
27
|
Provides-Extra: sqlmodel
|
|
29
28
|
Requires-Dist: sqlmodel>=0.0.37; extra == "sqlmodel"
|
|
@@ -36,9 +35,8 @@ Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "docs"
|
|
|
36
35
|
Requires-Dist: pymdown-extensions>=10.10; extra == "docs"
|
|
37
36
|
Requires-Dist: mike>=2.1.0; extra == "docs"
|
|
38
37
|
Provides-Extra: all
|
|
39
|
-
Requires-Dist: beanie
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist: SQLAlchemy[asyncio]>=2.0.0; extra == "all"
|
|
38
|
+
Requires-Dist: beanie<3,>=2.0; extra == "all"
|
|
39
|
+
Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "all"
|
|
42
40
|
Requires-Dist: psycopg2>=2.9.0; extra == "all"
|
|
43
41
|
Requires-Dist: sqlmodel>=0.0.37; extra == "all"
|
|
44
42
|
Requires-Dist: cookiecutter>=2.5.0; extra == "all"
|
|
@@ -48,18 +46,24 @@ Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "all"
|
|
|
48
46
|
|
|
49
47
|
<div align="center">
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
<img src="docs/assets/logo-mark.svg" width="120" alt="fastapi-basekit">
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
# fastapi-basekit
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-

|
|
58
|
-

|
|
59
|
-

|
|
60
|
-

|
|
53
|
+
**FastAPI toolkit con repos, services y controllers listos para producción.**
|
|
54
|
+
**Multi-ORM · CLI scaffolder · plugin Claude Code.**
|
|
61
55
|
|
|
62
|
-
[
|
|
56
|
+
[](https://pypi.org/project/fastapi-basekit/)
|
|
57
|
+
[](https://pypi.org/project/fastapi-basekit/)
|
|
58
|
+
[](https://fastapi.tiangolo.com/)
|
|
59
|
+
[](https://www.sqlalchemy.org/)
|
|
60
|
+
[](https://beanie-odm.dev/)
|
|
61
|
+
[](./LICENSE)
|
|
62
|
+
|
|
63
|
+
[**Docs**](https://mundobien2025.github.io/fastapi-basekit) ·
|
|
64
|
+
[**PyPI**](https://pypi.org/project/fastapi-basekit/) ·
|
|
65
|
+
[**Issues**](https://github.com/mundobien2025/fastapi-basekit/issues) ·
|
|
66
|
+
[**Changelog**](./CHANGELOG.md)
|
|
63
67
|
|
|
64
68
|
</div>
|
|
65
69
|
|
|
@@ -89,7 +93,7 @@ Abre http://localhost:8000/docs. Login con `admin@example.com` / `ChangeMe2026!`
|
|
|
89
93
|
|
|
90
94
|
## Documentación
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
**[mundobien2025.github.io/fastapi-basekit](https://mundobien2025.github.io/fastapi-basekit)**
|
|
93
97
|
|
|
94
98
|
| Sección | Contenido |
|
|
95
99
|
|---|---|
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="docs/assets/logo-mark.svg" width="120" alt="fastapi-basekit">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# fastapi-basekit
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-

|
|
10
|
-

|
|
11
|
-

|
|
12
|
-

|
|
7
|
+
**FastAPI toolkit con repos, services y controllers listos para producción.**
|
|
8
|
+
**Multi-ORM · CLI scaffolder · plugin Claude Code.**
|
|
13
9
|
|
|
14
|
-
[
|
|
10
|
+
[](https://pypi.org/project/fastapi-basekit/)
|
|
11
|
+
[](https://pypi.org/project/fastapi-basekit/)
|
|
12
|
+
[](https://fastapi.tiangolo.com/)
|
|
13
|
+
[](https://www.sqlalchemy.org/)
|
|
14
|
+
[](https://beanie-odm.dev/)
|
|
15
|
+
[](./LICENSE)
|
|
16
|
+
|
|
17
|
+
[**Docs**](https://mundobien2025.github.io/fastapi-basekit) ·
|
|
18
|
+
[**PyPI**](https://pypi.org/project/fastapi-basekit/) ·
|
|
19
|
+
[**Issues**](https://github.com/mundobien2025/fastapi-basekit/issues) ·
|
|
20
|
+
[**Changelog**](./CHANGELOG.md)
|
|
15
21
|
|
|
16
22
|
</div>
|
|
17
23
|
|
|
@@ -41,7 +47,7 @@ Abre http://localhost:8000/docs. Login con `admin@example.com` / `ChangeMe2026!`
|
|
|
41
47
|
|
|
42
48
|
## Documentación
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
**[mundobien2025.github.io/fastapi-basekit](https://mundobien2025.github.io/fastapi-basekit)**
|
|
45
51
|
|
|
46
52
|
| Sección | Contenido |
|
|
47
53
|
|---|---|
|
{fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/beanie/repository/base.py
RENAMED
|
@@ -2,9 +2,9 @@ from typing import Any, Dict, List, Optional, Type, Union
|
|
|
2
2
|
from typing import get_args, get_origin
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
-
from bson import ObjectId
|
|
5
|
+
from bson import ObjectId
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
-
from beanie import Document
|
|
7
|
+
from beanie import Document, Link
|
|
8
8
|
from beanie.odm.queries.find import FindMany
|
|
9
9
|
from beanie.operators import Or, RegEx
|
|
10
10
|
|
|
@@ -154,18 +154,42 @@ class BaseRepository:
|
|
|
154
154
|
|
|
155
155
|
raw_filters: Dict[str, Any] = {}
|
|
156
156
|
|
|
157
|
+
def _coerce_objectid(value: Any) -> Any:
|
|
158
|
+
"""str/UUID → ObjectId para queries Mongo sobre Link.id."""
|
|
159
|
+
if isinstance(value, ObjectId):
|
|
160
|
+
return value
|
|
161
|
+
if isinstance(value, str):
|
|
162
|
+
try:
|
|
163
|
+
return ObjectId(value)
|
|
164
|
+
except Exception:
|
|
165
|
+
return value
|
|
166
|
+
return value
|
|
167
|
+
|
|
157
168
|
for k, v in (filters or {}).items():
|
|
158
169
|
# MongoDB-style keys (dot-notation like "user.$id" or operators like "$or")
|
|
159
170
|
# cannot be resolved via hasattr — pass them as a raw dict to find()
|
|
160
171
|
if "." in k or k.startswith("$"):
|
|
161
172
|
raw_filters[k] = v
|
|
162
|
-
|
|
163
|
-
field_attr = getattr(self.model, k)
|
|
173
|
+
continue
|
|
164
174
|
|
|
175
|
+
if hasattr(self.model, k):
|
|
176
|
+
field_attr = getattr(self.model, k)
|
|
165
177
|
if _is_link_field(k):
|
|
166
|
-
exprs.append(field_attr.id == v)
|
|
178
|
+
exprs.append(field_attr.id == _coerce_objectid(v))
|
|
167
179
|
else:
|
|
168
180
|
exprs.append(field_attr == v)
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
# `<field>_id` alias para Link fields. Permite filtros con
|
|
184
|
+
# `customer_id`, `user_id`, etc. sin que el caller deba conocer
|
|
185
|
+
# la sintaxis Mongo nested. Sólo se activa si <field> existe en
|
|
186
|
+
# el modelo y es un Link[X].
|
|
187
|
+
if k.endswith("_id"):
|
|
188
|
+
base = k[:-3]
|
|
189
|
+
if hasattr(self.model, base) and _is_link_field(base):
|
|
190
|
+
field_attr = getattr(self.model, base)
|
|
191
|
+
exprs.append(field_attr.id == _coerce_objectid(v))
|
|
192
|
+
continue
|
|
169
193
|
|
|
170
194
|
# Raw MongoDB filters go first so Beanie processes them as a dict condition
|
|
171
195
|
query_args: list = ([raw_filters] if raw_filters else []) + exprs
|
|
@@ -183,27 +207,42 @@ class BaseRepository:
|
|
|
183
207
|
# Apply ordering if provided and not already applied
|
|
184
208
|
if order_by:
|
|
185
209
|
query = query.sort(order_by)
|
|
186
|
-
|
|
210
|
+
|
|
187
211
|
total = await query.count()
|
|
188
212
|
items = await query.skip(count * (page - 1)).limit(count).to_list()
|
|
189
213
|
return items, total
|
|
190
|
-
|
|
191
|
-
|
|
214
|
+
|
|
215
|
+
def build_list_queryset(
|
|
216
|
+
self,
|
|
217
|
+
search: Optional[str] = None,
|
|
218
|
+
search_fields: Optional[List[str]] = None,
|
|
219
|
+
filters: Optional[dict] = None,
|
|
220
|
+
order_by: Optional[List[tuple]] = None,
|
|
221
|
+
**kwargs,
|
|
222
|
+
) -> FindMany[Document]:
|
|
223
|
+
"""Hook: returns the FindMany query used by `list` endpoints.
|
|
224
|
+
|
|
225
|
+
Beanie equivalent of SQLAlchemy `build_list_queryset`. Override at
|
|
226
|
+
repository OR service level (`BaseService.build_list_queryset`) to
|
|
227
|
+
customize filters, projections, or query options before pagination.
|
|
228
|
+
Default implementation delegates to `build_filter_query`.
|
|
229
|
+
"""
|
|
230
|
+
return self.build_filter_query(
|
|
231
|
+
search=search,
|
|
232
|
+
search_fields=search_fields or [],
|
|
233
|
+
filters=filters or {},
|
|
234
|
+
order_by=order_by,
|
|
235
|
+
**kwargs,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _build_match_stage(
|
|
192
239
|
self,
|
|
193
240
|
search: Optional[str],
|
|
194
|
-
search_fields: List[str],
|
|
195
|
-
filters: dict,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
**kwargs
|
|
200
|
-
) -> tuple[List[Document], int]:
|
|
201
|
-
"""List with support for nested ordering using aggregation ($facet optimized)."""
|
|
202
|
-
field_path, direction, is_nested = self._parse_order_field(order_by)
|
|
203
|
-
pipeline = []
|
|
204
|
-
|
|
205
|
-
# 1. Match stage (same as before)
|
|
206
|
-
match_conditions = {}
|
|
241
|
+
search_fields: Optional[List[str]],
|
|
242
|
+
filters: Optional[dict],
|
|
243
|
+
) -> Dict[str, Any]:
|
|
244
|
+
"""Build a `$match` stage dict (without the `$match` wrapper)."""
|
|
245
|
+
match_conditions: Dict[str, Any] = {}
|
|
207
246
|
if filters:
|
|
208
247
|
for key, value in filters.items():
|
|
209
248
|
if isinstance(value, ObjectId):
|
|
@@ -212,7 +251,7 @@ class BaseRepository:
|
|
|
212
251
|
match_conditions[f"{key}.$id"] = value.id
|
|
213
252
|
else:
|
|
214
253
|
match_conditions[key] = value
|
|
215
|
-
|
|
254
|
+
|
|
216
255
|
if search and search_fields:
|
|
217
256
|
search_conditions = [
|
|
218
257
|
{field: {"$regex": f".*{search}.*", "$options": "i"}}
|
|
@@ -220,22 +259,47 @@ class BaseRepository:
|
|
|
220
259
|
]
|
|
221
260
|
if search_conditions:
|
|
222
261
|
if match_conditions:
|
|
223
|
-
match_conditions = {
|
|
262
|
+
match_conditions = {
|
|
263
|
+
"$and": [
|
|
264
|
+
match_conditions,
|
|
265
|
+
{"$or": search_conditions},
|
|
266
|
+
]
|
|
267
|
+
}
|
|
224
268
|
else:
|
|
225
269
|
match_conditions = {"$or": search_conditions}
|
|
226
|
-
|
|
270
|
+
return match_conditions
|
|
271
|
+
|
|
272
|
+
def build_list_pipeline(
|
|
273
|
+
self,
|
|
274
|
+
search: Optional[str] = None,
|
|
275
|
+
search_fields: Optional[List[str]] = None,
|
|
276
|
+
filters: Optional[dict] = None,
|
|
277
|
+
order_by: Optional[str] = None,
|
|
278
|
+
**kwargs,
|
|
279
|
+
) -> List[Dict[str, Any]]:
|
|
280
|
+
"""Hook: returns the aggregation pipeline used by `list` endpoints.
|
|
281
|
+
|
|
282
|
+
Beanie equivalent of SQL subqueries / JOINs. Override at repository
|
|
283
|
+
OR service level (`BaseService.build_list_pipeline`) to add `$lookup`,
|
|
284
|
+
`$project`, `$group`, etc. Default builds `$match` + optional `$sort`
|
|
285
|
+
(with auto `$lookup` for nested-link ordering). The `$facet` pagination
|
|
286
|
+
stage is appended later by `paginate_pipeline`.
|
|
287
|
+
"""
|
|
288
|
+
pipeline: List[Dict[str, Any]] = []
|
|
289
|
+
|
|
290
|
+
match_conditions = self._build_match_stage(search, search_fields, filters)
|
|
227
291
|
if match_conditions:
|
|
228
292
|
pipeline.append({"$match": match_conditions})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
293
|
+
|
|
294
|
+
if not order_by:
|
|
295
|
+
return pipeline
|
|
296
|
+
|
|
297
|
+
field_path, direction, is_nested = self._parse_order_field(order_by)
|
|
298
|
+
|
|
234
299
|
if is_nested:
|
|
235
300
|
parts = field_path.split(".")
|
|
236
301
|
first_field = parts[0]
|
|
237
302
|
collection_name = self._get_collection_name_from_field(first_field)
|
|
238
|
-
|
|
239
303
|
if collection_name:
|
|
240
304
|
pipeline.extend([
|
|
241
305
|
{
|
|
@@ -243,65 +307,105 @@ class BaseRepository:
|
|
|
243
307
|
"from": collection_name,
|
|
244
308
|
"localField": f"{first_field}.$id",
|
|
245
309
|
"foreignField": "_id",
|
|
246
|
-
"as": f"{first_field}_data"
|
|
310
|
+
"as": f"{first_field}_data",
|
|
247
311
|
}
|
|
248
312
|
},
|
|
249
313
|
{
|
|
250
314
|
"$unwind": {
|
|
251
315
|
"path": f"${first_field}_data",
|
|
252
|
-
"preserveNullAndEmptyArrays": True
|
|
316
|
+
"preserveNullAndEmptyArrays": True,
|
|
253
317
|
}
|
|
254
|
-
}
|
|
318
|
+
},
|
|
255
319
|
])
|
|
256
320
|
remaining_path = ".".join(parts[1:]) if len(parts) > 1 else ""
|
|
257
|
-
sort_field =
|
|
321
|
+
sort_field = (
|
|
322
|
+
f"{first_field}_data.{remaining_path}"
|
|
323
|
+
if remaining_path
|
|
324
|
+
else f"{first_field}_data"
|
|
325
|
+
)
|
|
258
326
|
else:
|
|
259
327
|
sort_field = field_path
|
|
260
328
|
else:
|
|
261
329
|
sort_field = field_path
|
|
262
|
-
|
|
330
|
+
|
|
263
331
|
pipeline.append({"$sort": {sort_field: direction}})
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
332
|
+
return pipeline
|
|
333
|
+
|
|
334
|
+
async def paginate_pipeline(
|
|
335
|
+
self,
|
|
336
|
+
pipeline: List[Dict[str, Any]],
|
|
337
|
+
page: int,
|
|
338
|
+
count: int,
|
|
339
|
+
validate: bool = True,
|
|
340
|
+
) -> tuple[List[Any], int]:
|
|
341
|
+
"""Run an aggregation pipeline with `$facet` pagination.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
pipeline: Pipeline stages (without the final `$facet`).
|
|
345
|
+
page, count: Pagination params.
|
|
346
|
+
validate: If True, validates each row against `self.model`.
|
|
347
|
+
Set False when the pipeline projects a non-model shape (e.g.
|
|
348
|
+
joined columns) — the raw dicts are returned untouched.
|
|
349
|
+
"""
|
|
350
|
+
full_pipeline = list(pipeline) + [
|
|
351
|
+
{
|
|
352
|
+
"$facet": {
|
|
353
|
+
"metadata": [{"$count": "total"}],
|
|
354
|
+
"data": [
|
|
355
|
+
{"$skip": count * (page - 1)},
|
|
356
|
+
{"$limit": count},
|
|
357
|
+
],
|
|
358
|
+
}
|
|
275
359
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if project_exclusion:
|
|
280
|
-
facet_stage["$facet"]["data"].append({"$project": project_exclusion})
|
|
281
|
-
|
|
282
|
-
pipeline.append(facet_stage)
|
|
283
|
-
|
|
284
|
-
# Execute Pipeline
|
|
285
|
-
results = await self.model.aggregate(pipeline).to_list()
|
|
286
|
-
|
|
287
|
-
# Process Results
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
results = await self.model.aggregate(full_pipeline).to_list()
|
|
288
363
|
if not results or not results[0].get("metadata"):
|
|
289
364
|
return [], 0
|
|
290
|
-
|
|
365
|
+
|
|
291
366
|
data = results[0]
|
|
292
367
|
total = data["metadata"][0]["total"] if data["metadata"] else 0
|
|
293
368
|
items_raw = data["data"]
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
369
|
+
|
|
370
|
+
if not validate:
|
|
371
|
+
return items_raw, total
|
|
372
|
+
|
|
373
|
+
items: List[Any] = []
|
|
297
374
|
for raw_item in items_raw:
|
|
298
375
|
try:
|
|
299
376
|
items.append(self.model.model_validate(raw_item))
|
|
300
377
|
except Exception:
|
|
301
378
|
continue
|
|
302
|
-
|
|
303
379
|
return items, total
|
|
304
380
|
|
|
381
|
+
async def list_with_aggregation(
|
|
382
|
+
self,
|
|
383
|
+
search: Optional[str],
|
|
384
|
+
search_fields: List[str],
|
|
385
|
+
filters: dict,
|
|
386
|
+
order_by: str,
|
|
387
|
+
page: int,
|
|
388
|
+
count: int,
|
|
389
|
+
**kwargs,
|
|
390
|
+
) -> tuple[List[Document], int]:
|
|
391
|
+
"""Backward-compat wrapper: delegates to `build_list_pipeline` +
|
|
392
|
+
`paginate_pipeline`. Kept for callers that hit it directly.
|
|
393
|
+
"""
|
|
394
|
+
pipeline = self.build_list_pipeline(
|
|
395
|
+
search=search,
|
|
396
|
+
search_fields=search_fields,
|
|
397
|
+
filters=filters,
|
|
398
|
+
order_by=order_by,
|
|
399
|
+
**kwargs,
|
|
400
|
+
)
|
|
401
|
+
# Strip join artifacts when default sort-lookup added them
|
|
402
|
+
field_path, _, is_nested = self._parse_order_field(order_by) if order_by else ("", 0, False)
|
|
403
|
+
if is_nested:
|
|
404
|
+
first_field = field_path.split(".")[0]
|
|
405
|
+
if self._get_collection_name_from_field(first_field):
|
|
406
|
+
pipeline.append({"$project": {f"{first_field}_data": 0}})
|
|
407
|
+
return await self.paginate_pipeline(pipeline, page, count, validate=True)
|
|
408
|
+
|
|
305
409
|
async def get_by_id(
|
|
306
410
|
self,
|
|
307
411
|
obj_id: Union[str, ObjectId],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
|
+
from beanie import Document
|
|
4
|
+
from beanie.odm.queries.find import FindMany
|
|
3
5
|
from fastapi import Request
|
|
4
6
|
from pydantic import BaseModel
|
|
5
7
|
|
|
@@ -20,6 +22,8 @@ class BaseService:
|
|
|
20
22
|
kwargs_query: Dict[str, Union[str, int]] = {}
|
|
21
23
|
action: str = ""
|
|
22
24
|
order_by: Optional[List[tuple]] = None
|
|
25
|
+
use_aggregation: bool = False
|
|
26
|
+
aggregation_validate: bool = True
|
|
23
27
|
|
|
24
28
|
def __init__(
|
|
25
29
|
self, repository: BaseRepository, request: Optional[Request] = None
|
|
@@ -69,61 +73,108 @@ class BaseService:
|
|
|
69
73
|
raise NotFoundException(f"id={id} no encontrado")
|
|
70
74
|
return obj
|
|
71
75
|
|
|
76
|
+
def build_list_queryset(
|
|
77
|
+
self,
|
|
78
|
+
search: Optional[str] = None,
|
|
79
|
+
search_fields: Optional[List[str]] = None,
|
|
80
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
81
|
+
order_by: Optional[List[tuple]] = None,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> FindMany[Document]:
|
|
84
|
+
"""Service-level hook over `repository.build_list_queryset`.
|
|
85
|
+
|
|
86
|
+
Override here to compose query options across repositories or to
|
|
87
|
+
decorate the FindMany before pagination.
|
|
88
|
+
"""
|
|
89
|
+
return self.repository.build_list_queryset(
|
|
90
|
+
search=search,
|
|
91
|
+
search_fields=search_fields or self.search_fields,
|
|
92
|
+
filters=filters,
|
|
93
|
+
order_by=order_by,
|
|
94
|
+
**kwargs,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def build_list_pipeline(
|
|
98
|
+
self,
|
|
99
|
+
search: Optional[str] = None,
|
|
100
|
+
search_fields: Optional[List[str]] = None,
|
|
101
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
102
|
+
order_by: Optional[str] = None,
|
|
103
|
+
**kwargs,
|
|
104
|
+
) -> List[Dict[str, Any]]:
|
|
105
|
+
"""Service-level hook over `repository.build_list_pipeline`.
|
|
106
|
+
|
|
107
|
+
Override here to add `$lookup`, `$project`, `$group`, etc. for
|
|
108
|
+
cross-collection joins (subquery-like). Set `use_aggregation = True`
|
|
109
|
+
on the service to force the pipeline path even without nested order.
|
|
110
|
+
Set `aggregation_validate = False` if the projection produces a
|
|
111
|
+
non-model shape (joined columns / flattened rows).
|
|
112
|
+
"""
|
|
113
|
+
return self.repository.build_list_pipeline(
|
|
114
|
+
search=search,
|
|
115
|
+
search_fields=search_fields or self.search_fields,
|
|
116
|
+
filters=filters,
|
|
117
|
+
order_by=order_by,
|
|
118
|
+
**kwargs,
|
|
119
|
+
)
|
|
120
|
+
|
|
72
121
|
async def list(
|
|
73
122
|
self,
|
|
74
123
|
search: Optional[str] = None,
|
|
75
124
|
page: int = 1,
|
|
76
125
|
count: int = 25,
|
|
77
126
|
filters: Optional[Dict[str, Any]] = None,
|
|
78
|
-
order_by: Optional[str] = None, #
|
|
127
|
+
order_by: Optional[str] = None, # Dynamic ordering (e.g., "-created_at" or "tool__name")
|
|
79
128
|
):
|
|
80
129
|
kwargs = self.get_kwargs_query()
|
|
81
130
|
applied_filters = self.get_filters(filters)
|
|
82
|
-
|
|
83
|
-
#
|
|
131
|
+
|
|
132
|
+
# Resolve ordering
|
|
84
133
|
if order_by:
|
|
85
|
-
# Dynamic ordering from parameter (takes precedence)
|
|
86
134
|
order_str = order_by
|
|
87
135
|
else:
|
|
88
|
-
# Fall back to service-level ordering
|
|
89
136
|
default_order = self.get_order()
|
|
90
137
|
if default_order:
|
|
91
|
-
# Convert list of tuples to string format
|
|
92
|
-
# e.g., [("created_at", -1)] -> "-created_at"
|
|
93
138
|
field, direction = default_order[0]
|
|
94
139
|
order_str = f"{'-' if direction == -1 else ''}{field}"
|
|
95
140
|
else:
|
|
96
141
|
order_str = None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
|
|
143
|
+
nested_order = bool(order_str and ("__" in order_str or "." in order_str))
|
|
144
|
+
use_pipeline = self.use_aggregation or nested_order
|
|
145
|
+
|
|
146
|
+
if use_pipeline:
|
|
147
|
+
pipeline = self.build_list_pipeline(
|
|
102
148
|
search=search,
|
|
103
149
|
search_fields=self.search_fields,
|
|
104
150
|
filters=applied_filters,
|
|
105
151
|
order_by=order_str,
|
|
106
|
-
page=page,
|
|
107
|
-
count=count,
|
|
108
152
|
**kwargs,
|
|
109
153
|
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
direction = -1 if order_str.startswith("-") else 1
|
|
116
|
-
field = order_str.lstrip("-")
|
|
117
|
-
order_list = [(field, direction)]
|
|
118
|
-
|
|
119
|
-
query = self.repository.build_filter_query(
|
|
120
|
-
search=search,
|
|
121
|
-
search_fields=self.search_fields,
|
|
122
|
-
filters=applied_filters,
|
|
123
|
-
order_by=order_list,
|
|
124
|
-
**kwargs,
|
|
154
|
+
return await self.repository.paginate_pipeline(
|
|
155
|
+
pipeline,
|
|
156
|
+
page=page,
|
|
157
|
+
count=count,
|
|
158
|
+
validate=self.aggregation_validate,
|
|
125
159
|
)
|
|
126
|
-
|
|
160
|
+
|
|
161
|
+
# FindMany path
|
|
162
|
+
order_list = None
|
|
163
|
+
if order_str:
|
|
164
|
+
direction = -1 if order_str.startswith("-") else 1
|
|
165
|
+
field = order_str.lstrip("-")
|
|
166
|
+
order_list = [(field, direction)]
|
|
167
|
+
|
|
168
|
+
query = self.build_list_queryset(
|
|
169
|
+
search=search,
|
|
170
|
+
search_fields=self.search_fields,
|
|
171
|
+
filters=applied_filters,
|
|
172
|
+
order_by=order_list,
|
|
173
|
+
**kwargs,
|
|
174
|
+
)
|
|
175
|
+
return await self.repository.paginate(
|
|
176
|
+
query, page, count, order_by=order_list
|
|
177
|
+
)
|
|
127
178
|
|
|
128
179
|
async def create(
|
|
129
180
|
self, payload: BaseModel, check_fields: Optional[List[str]] = None
|
{fastapi_basekit-0.3.0 → fastapi_basekit-0.3.2}/fastapi_basekit/aio/sqlalchemy/service/base.py
RENAMED
|
@@ -10,7 +10,14 @@ from ....exceptions.api_exceptions import (
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class BaseService:
|
|
13
|
-
"""Servicio base para SQLAlchemy AsyncSession.
|
|
13
|
+
"""Servicio base para SQLAlchemy AsyncSession.
|
|
14
|
+
|
|
15
|
+
Regla del proyecto: los servicios NO deben llamar `session.flush()`,
|
|
16
|
+
`session.commit()` ni `session.refresh()`. El flush vive en
|
|
17
|
+
`BaseRepository.create / update`; el commit/rollback único por
|
|
18
|
+
request lo gestiona el lifecycle creado con
|
|
19
|
+
`fastapi_basekit.aio.sqlalchemy.make_session_lifecycle`.
|
|
20
|
+
"""
|
|
14
21
|
|
|
15
22
|
repository: BaseRepository
|
|
16
23
|
search_fields: List[str] = []
|