fastapi-basekit 0.4.1__tar.gz → 0.5.0__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.4.1 → fastapi_basekit-0.5.0}/PKG-INFO +3 -3
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/controller/base.py +1 -1
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/repository/base.py +126 -14
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/service/base.py +68 -26
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/controller/base.py +175 -69
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/controller/base.py +1 -1
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/repository/base.py +89 -15
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/service/base.py +106 -11
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/repository/base.py +105 -22
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/service/base.py +36 -11
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/exceptions/handler.py +15 -0
- fastapi_basekit-0.5.0/fastapi_basekit/py.typed +1 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/servicios/thrid/jwt.py +42 -7
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/PKG-INFO +3 -3
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/SOURCES.txt +23 -1
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/requires.txt +2 -2
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/pyproject.toml +4 -3
- fastapi_basekit-0.5.0/tests/test_beanie_keyset_pagination.py +75 -0
- fastapi_basekit-0.5.0/tests/test_beanie_repository_regression.py +261 -0
- fastapi_basekit-0.5.0/tests/test_beanie_service_mutable_defaults.py +29 -0
- fastapi_basekit-0.5.0/tests/test_beanie_service_regression.py +244 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_exception_handlers.py +30 -0
- fastapi_basekit-0.5.0/tests/test_generics_optional_and_cache.py +99 -0
- fastapi_basekit-0.5.0/tests/test_generics_wiring.py +100 -0
- fastapi_basekit-0.5.0/tests/test_jwt_config_robustness.py +61 -0
- fastapi_basekit-0.5.0/tests/test_params_classification.py +193 -0
- fastapi_basekit-0.5.0/tests/test_params_coercion_matrix.py +110 -0
- fastapi_basekit-0.5.0/tests/test_params_coercion_unit.py +165 -0
- fastapi_basekit-0.5.0/tests/test_params_http_matrix_beanie.py +109 -0
- fastapi_basekit-0.5.0/tests/test_params_http_matrix_sqlalchemy.py +114 -0
- fastapi_basekit-0.5.0/tests/test_params_rich_type_coercion.py +79 -0
- fastapi_basekit-0.5.0/tests/test_path_filter_mixin_compat.py +179 -0
- fastapi_basekit-0.5.0/tests/test_post_process_list_hook.py +154 -0
- fastapi_basekit-0.5.0/tests/test_pulbot_cbv_pattern_beanie.py +146 -0
- fastapi_basekit-0.5.0/tests/test_repo_signature_aliases.py +74 -0
- fastapi_basekit-0.5.0/tests/test_sqlalchemy_repository_regression.py +173 -0
- fastapi_basekit-0.5.0/tests/test_sqlalchemy_service_regression.py +154 -0
- fastapi_basekit-0.5.0/tests/test_sqlmodel_filter_operators.py +92 -0
- fastapi_basekit-0.5.0/tests/test_typed_filter_coercion.py +114 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/LICENSE +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/README.md +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/controller/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/repository/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/service/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/controller/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/permissions/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/permissions/base.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/controller/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/repository/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/service/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlalchemy/session.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/controller/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/controller/base.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/repository/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/sqlmodel/service/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/cli/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/cli/main.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/exceptions/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/exceptions/api_exceptions.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/exceptions/domain.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/openapi.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/schema/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/schema/base.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/schema/jwt.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/schema/schema.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/servicios/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/servicios/thrid/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/cookiecutter.json +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/hooks/post_gen_project.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/hooks/pre_gen_project.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.env.example +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.gitignore +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Dockerfile +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/LICENSE +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Makefile +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/README.md +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/env.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/script.py.mako +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic.ini +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/auth.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/user.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/routers.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/database.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/settings.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/main.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/auth.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/permissions.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/auth.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/base.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/enums.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/types.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/user.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/repository.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/auth.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/base.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/user.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init_admin.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/auth_service.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/dependency.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/user_service.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/__init__.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/exception_handlers.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/security.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/docker-compose.yml +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/pytest.ini +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/requirements.txt +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/dependency_links.txt +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/entry_points.txt +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit.egg-info/top_level.txt +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/setup.cfg +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_action_param_leak.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_api_exceptions.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_base_response.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_base_service.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_beanie_aggregation_hooks.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_beanie_aggregation_integration.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_controller_auto_permissions.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_crud_beanie_controller.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_crud_controller.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_crud_sqlmodel_repository_service.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_filter_operators.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_jwt_service.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_openapi_simplify.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/tests/test_sql_queryset_override.py +0 -0
- {fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/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
|
+
Version: 0.5.0
|
|
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
|
|
@@ -23,7 +23,7 @@ Provides-Extra: beanie
|
|
|
23
23
|
Requires-Dist: beanie<3,>=2.0; extra == "beanie"
|
|
24
24
|
Provides-Extra: sqlalchemy
|
|
25
25
|
Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "sqlalchemy"
|
|
26
|
-
Requires-Dist: psycopg2>=2.9.0; extra == "sqlalchemy"
|
|
26
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "sqlalchemy"
|
|
27
27
|
Provides-Extra: sqlmodel
|
|
28
28
|
Requires-Dist: sqlmodel>=0.0.37; extra == "sqlmodel"
|
|
29
29
|
Provides-Extra: init
|
|
@@ -37,7 +37,7 @@ Requires-Dist: mike>=2.1.0; extra == "docs"
|
|
|
37
37
|
Provides-Extra: all
|
|
38
38
|
Requires-Dist: beanie<3,>=2.0; extra == "all"
|
|
39
39
|
Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "all"
|
|
40
|
-
Requires-Dist: psycopg2>=2.9.0; extra == "all"
|
|
40
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
41
41
|
Requires-Dist: sqlmodel>=0.0.37; extra == "all"
|
|
42
42
|
Requires-Dist: cookiecutter>=2.5.0; extra == "all"
|
|
43
43
|
Requires-Dist: mkdocs>=1.6.0; extra == "all"
|
{fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/controller/base.py
RENAMED
|
@@ -19,7 +19,7 @@ class BeanieBaseController(BaseController):
|
|
|
19
19
|
async def list(self):
|
|
20
20
|
"""Lista documentos con paginación usando Beanie."""
|
|
21
21
|
await self.prepare_action("list")
|
|
22
|
-
params = self._params(
|
|
22
|
+
params = self._params()
|
|
23
23
|
items, total = await self.service.list(**params)
|
|
24
24
|
count = params.get("count") or 0
|
|
25
25
|
page = params.get("page") or 1
|
{fastapi_basekit-0.4.1 → fastapi_basekit-0.5.0}/fastapi_basekit/aio/beanie/repository/base.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union
|
|
2
3
|
from typing import get_args, get_origin
|
|
3
4
|
import re
|
|
4
5
|
|
|
@@ -8,10 +9,33 @@ from beanie import Document, Link
|
|
|
8
9
|
from beanie.odm.queries.find import FindMany
|
|
9
10
|
from beanie.operators import Or, RegEx
|
|
10
11
|
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
#: Tipo del Document Beanie que gestiona el repositorio. Parametrízalo al
|
|
15
|
+
#: declarar la subclase — ``class UserRepository(BaseRepository[User])`` — para
|
|
16
|
+
#: que ``get_by_id``/``create``/``update``/``list_all`` devuelvan el Document
|
|
17
|
+
#: tipado (autocompletado + mypy para consumidores e IAs).
|
|
18
|
+
ModelT = TypeVar("ModelT", bound=Document)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseRepository(Generic[ModelT]):
|
|
22
|
+
"""Repositorio base para Beanie ODM (MongoDB), parametrizado por el modelo.
|
|
23
|
+
|
|
24
|
+
OJO — las firmas difieren del repo SQLAlchemy (misma clase, distinto ORM):
|
|
25
|
+
- get por id: `get_by_id(id)` (SQL usa `get(id)`)
|
|
26
|
+
- update: `update(obj, data)` — el 1er arg es el **Document**, no el
|
|
27
|
+
id (SQL usa `update(id, dict)`). Fetch primero con
|
|
28
|
+
`get_by_id`, luego `update(obj, data)`.
|
|
29
|
+
- delete: `delete(obj)` — recibe el Document (SQL usa `delete(id)`).
|
|
30
|
+
- create: `create(obj | dict)`.
|
|
31
|
+
Listado: `build_list_queryset(...)`→FindMany + `paginate(...)`, o
|
|
32
|
+
`build_list_pipeline(...)` + `paginate_pipeline(...)` para joins (`$lookup`).
|
|
33
|
+
Preferí llamar el SERVICE (API unificada) en vez del repo directo; llamar
|
|
34
|
+
`get_by_id` directo bypassea el scoping de `get_filters()`.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
model: Type[ModelT]
|
|
11
38
|
|
|
12
|
-
class BaseRepository:
|
|
13
|
-
model: Type[Document]
|
|
14
|
-
|
|
15
39
|
def _parse_order_field(self, order_by: str) -> tuple[str, int, bool]:
|
|
16
40
|
"""Parse order_by string into components.
|
|
17
41
|
|
|
@@ -204,6 +228,17 @@ class BaseRepository:
|
|
|
204
228
|
async def paginate(
|
|
205
229
|
self, query: FindMany[Document], page: int, count: int, order_by: Optional[List[tuple]] = None
|
|
206
230
|
) -> tuple[List[Document], int]:
|
|
231
|
+
"""MOTOR DE PAGINACIÓN (FindMany) — NO LO REIMPLEMENTES.
|
|
232
|
+
|
|
233
|
+
Único loop de paginación offset (count + skip/limit). Para personalizar
|
|
234
|
+
el listado, override el HOOK correcto, no copies skip/limit/count:
|
|
235
|
+
- query/filtros/scoping/orden → `build_list_queryset` (o `build_filter_query`)
|
|
236
|
+
- join `$lookup` / shape plano → `use_aggregation=True` + `build_list_pipeline`
|
|
237
|
+
- filtros por usuario/acción → `Service.get_filters`
|
|
238
|
+
- orden por defecto → `Service.get_order`
|
|
239
|
+
- enriquecer items de la página→ `Service.post_process_list`
|
|
240
|
+
- scroll infinito / cursor → `paginate_keyset` (método+endpoint aparte)
|
|
241
|
+
"""
|
|
207
242
|
# Apply ordering if provided and not already applied
|
|
208
243
|
if order_by:
|
|
209
244
|
query = query.sort(order_by)
|
|
@@ -212,6 +247,44 @@ class BaseRepository:
|
|
|
212
247
|
items = await query.skip(count * (page - 1)).limit(count).to_list()
|
|
213
248
|
return items, total
|
|
214
249
|
|
|
250
|
+
async def paginate_keyset(
|
|
251
|
+
self,
|
|
252
|
+
query: FindMany[Document],
|
|
253
|
+
limit: int,
|
|
254
|
+
cursor_field: str,
|
|
255
|
+
cursor_value: Optional[Any] = None,
|
|
256
|
+
ascending: bool = False,
|
|
257
|
+
) -> tuple[List[Document], bool]:
|
|
258
|
+
"""Keyset (cursor) pagination — O(1) por página, sin `skip` ni `count`.
|
|
259
|
+
|
|
260
|
+
Alternativa a `paginate` para listados que escalan: en vez de contar
|
|
261
|
+
toda la colección y saltar `count*(page-1)` documentos (coste que crece
|
|
262
|
+
con la profundidad), filtra por un cursor sobre `cursor_field` y trae
|
|
263
|
+
`limit + 1` para saber si quedan más — sin un `count()`.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
query: FindMany ya filtrado (los filtros base del listado).
|
|
267
|
+
limit: tamaño de página.
|
|
268
|
+
cursor_field: campo Mongo indexado del cursor (ej. "_id",
|
|
269
|
+
"created_at", "timestamp"). Debe existir un índice que lo cubra.
|
|
270
|
+
cursor_value: valor del último item de la página previa. None =
|
|
271
|
+
primera página.
|
|
272
|
+
ascending: True = orden ascendente (`> cursor`); False = descendente
|
|
273
|
+
(`< cursor`), útil para "más recientes primero".
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
(items, has_more). El caller deriva el próximo cursor del último
|
|
277
|
+
item devuelto (`getattr(items[-1], cursor_field)`).
|
|
278
|
+
"""
|
|
279
|
+
limit = max(1, int(limit))
|
|
280
|
+
op = "$gt" if ascending else "$lt"
|
|
281
|
+
if cursor_value is not None:
|
|
282
|
+
query = query.find({cursor_field: {op: cursor_value}})
|
|
283
|
+
query = query.sort((cursor_field, 1 if ascending else -1))
|
|
284
|
+
docs = await query.limit(limit + 1).to_list()
|
|
285
|
+
has_more = len(docs) > limit
|
|
286
|
+
return docs[:limit], has_more
|
|
287
|
+
|
|
215
288
|
def build_list_queryset(
|
|
216
289
|
self,
|
|
217
290
|
search: Optional[str] = None,
|
|
@@ -338,7 +411,13 @@ class BaseRepository:
|
|
|
338
411
|
count: int,
|
|
339
412
|
validate: bool = True,
|
|
340
413
|
) -> tuple[List[Any], int]:
|
|
341
|
-
"""
|
|
414
|
+
"""MOTOR DE PAGINACIÓN (aggregation `$facet`) — NO LO REIMPLEMENTES.
|
|
415
|
+
|
|
416
|
+
Ejecuta el pipeline con paginación `$facet`. Para un listado agregado
|
|
417
|
+
NO copies este `$facet`/skip/limit: override `Service.build_list_pipeline`
|
|
418
|
+
(agrega `$lookup`/`$project`/`$group`) y activa `use_aggregation=True`;
|
|
419
|
+
este método le pone la paginación. Si la proyección no es el modelo,
|
|
420
|
+
`aggregation_validate=False`.
|
|
342
421
|
|
|
343
422
|
Args:
|
|
344
423
|
pipeline: Pipeline stages (without the final `$facet`).
|
|
@@ -371,11 +450,31 @@ class BaseRepository:
|
|
|
371
450
|
return items_raw, total
|
|
372
451
|
|
|
373
452
|
items: List[Any] = []
|
|
453
|
+
dropped = 0
|
|
374
454
|
for raw_item in items_raw:
|
|
375
455
|
try:
|
|
376
456
|
items.append(self.model.model_validate(raw_item))
|
|
377
|
-
except Exception:
|
|
378
|
-
|
|
457
|
+
except Exception as exc:
|
|
458
|
+
# No tragar en silencio: una fila que no valida se pierde de la
|
|
459
|
+
# respuesta sin aviso (menos items que el limit, total inflado).
|
|
460
|
+
# Se loguea para que sea diagnosticable; la fila igual se omite
|
|
461
|
+
# para no romper el listado completo por un doc corrupto.
|
|
462
|
+
dropped += 1
|
|
463
|
+
logger.warning(
|
|
464
|
+
"paginate_pipeline: fila descartada por fallo de "
|
|
465
|
+
"validación contra %s: %s",
|
|
466
|
+
getattr(self.model, "__name__", self.model),
|
|
467
|
+
exc,
|
|
468
|
+
)
|
|
469
|
+
if dropped:
|
|
470
|
+
logger.warning(
|
|
471
|
+
"paginate_pipeline: %d/%d filas descartadas en %s "
|
|
472
|
+
"(total reportado=%d)",
|
|
473
|
+
dropped,
|
|
474
|
+
len(items_raw),
|
|
475
|
+
getattr(self.model, "__name__", self.model),
|
|
476
|
+
total,
|
|
477
|
+
)
|
|
379
478
|
return items, total
|
|
380
479
|
|
|
381
480
|
async def list_with_aggregation(
|
|
@@ -410,7 +509,7 @@ class BaseRepository:
|
|
|
410
509
|
self,
|
|
411
510
|
obj_id: Union[str, ObjectId],
|
|
412
511
|
**kwargs,
|
|
413
|
-
) -> Optional[
|
|
512
|
+
) -> Optional[ModelT]:
|
|
414
513
|
if not isinstance(obj_id, ObjectId):
|
|
415
514
|
obj_id = ObjectId(obj_id)
|
|
416
515
|
return await self.model.find_one(
|
|
@@ -418,12 +517,19 @@ class BaseRepository:
|
|
|
418
517
|
**self._get_query_kwargs(**kwargs),
|
|
419
518
|
)
|
|
420
519
|
|
|
520
|
+
async def get(
|
|
521
|
+
self, obj_id: Union[str, ObjectId], **kwargs
|
|
522
|
+
) -> Optional[ModelT]:
|
|
523
|
+
"""Alias de `get_by_id(id)` — nombre unificado con los repos SQL
|
|
524
|
+
(`get`) para que las lecturas por id sean portables entre ORMs."""
|
|
525
|
+
return await self.get_by_id(obj_id, **kwargs)
|
|
526
|
+
|
|
421
527
|
async def get_by_field(
|
|
422
528
|
self,
|
|
423
529
|
field_name: str,
|
|
424
530
|
value: Any,
|
|
425
531
|
**kwargs,
|
|
426
|
-
) -> Optional[
|
|
532
|
+
) -> Optional[ModelT]:
|
|
427
533
|
if not hasattr(self.model, field_name):
|
|
428
534
|
raise AttributeError(
|
|
429
535
|
f"{self.model.__name__} no tiene el campo '{field_name}'"
|
|
@@ -437,7 +543,7 @@ class BaseRepository:
|
|
|
437
543
|
self,
|
|
438
544
|
filters: Dict[str, Any],
|
|
439
545
|
**kwargs,
|
|
440
|
-
) -> Optional[
|
|
546
|
+
) -> Optional[ModelT]:
|
|
441
547
|
exprs = [
|
|
442
548
|
getattr(self.model, f) == v
|
|
443
549
|
for f, v in filters.items()
|
|
@@ -452,21 +558,27 @@ class BaseRepository:
|
|
|
452
558
|
async def list_all(
|
|
453
559
|
self,
|
|
454
560
|
**kwargs,
|
|
455
|
-
) -> List[
|
|
561
|
+
) -> List[ModelT]:
|
|
456
562
|
query = self.model.find_all(**self._get_query_kwargs(**kwargs))
|
|
457
563
|
return await query.to_list()
|
|
458
564
|
|
|
459
|
-
async def create(self, obj: Union[
|
|
565
|
+
async def create(self, obj: Union[ModelT, Dict[str, Any]]) -> ModelT:
|
|
460
566
|
if isinstance(obj, dict):
|
|
461
567
|
obj = self.model(**obj)
|
|
462
568
|
await obj.insert()
|
|
463
569
|
return obj
|
|
464
570
|
|
|
465
|
-
async def update(self, obj:
|
|
571
|
+
async def update(self, obj: ModelT, data: Dict[str, Any]) -> ModelT:
|
|
572
|
+
"""Actualiza un Document Beanie ya cargado.
|
|
573
|
+
|
|
574
|
+
Divergencia con SQL: acá el 1er arg es el **Document** (no el id) y SÍ
|
|
575
|
+
setea ``None``. El repo SQLAlchemy usa ``update(id, dict)`` y omite
|
|
576
|
+
``None``. Ver la tabla en el CLAUDE.md de la lib.
|
|
577
|
+
"""
|
|
466
578
|
for key, value in data.items():
|
|
467
579
|
setattr(obj, key, value)
|
|
468
580
|
await obj.save()
|
|
469
581
|
return obj
|
|
470
582
|
|
|
471
|
-
async def delete(self, obj:
|
|
583
|
+
async def delete(self, obj: ModelT) -> None:
|
|
472
584
|
await obj.delete()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Union
|
|
1
|
+
from typing import Any, Dict, Generic, List, Optional, Tuple, Union
|
|
2
2
|
|
|
3
3
|
from beanie import Document
|
|
4
4
|
from beanie.odm.queries.find import FindMany
|
|
@@ -6,17 +6,25 @@ from fastapi import Request
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
from ...beanie.repository.base import BaseRepository
|
|
9
|
+
from ...beanie.repository.base import BaseRepository, ModelT
|
|
10
10
|
from ....exceptions.api_exceptions import (
|
|
11
11
|
NotFoundException,
|
|
12
12
|
DatabaseIntegrityException,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class BaseService:
|
|
17
|
-
"""Servicio base específico para Beanie ODM (async).
|
|
16
|
+
class BaseService(Generic[ModelT]):
|
|
17
|
+
"""Servicio base específico para Beanie ODM (async), parametrizado.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Declara el modelo vía el genérico para tipar el CRUD::
|
|
20
|
+
|
|
21
|
+
class UserService(BaseService[User]):
|
|
22
|
+
repository: UserRepository
|
|
23
|
+
|
|
24
|
+
Así ``retrieve``/``create``/``update`` devuelven ``User``.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
repository: BaseRepository[ModelT]
|
|
20
28
|
search_fields: List[str] = []
|
|
21
29
|
duplicate_check_fields: List[str] = []
|
|
22
30
|
kwargs_query: Dict[str, Union[str, int]] = {}
|
|
@@ -30,6 +38,14 @@ class BaseService:
|
|
|
30
38
|
):
|
|
31
39
|
self.repository = repository
|
|
32
40
|
self.request = request
|
|
41
|
+
# Copia por instancia de los defaults mutables (heredados como
|
|
42
|
+
# atributos de CLASE, compartidos por todo el proceso). Sin esto, una
|
|
43
|
+
# mutación en runtime (`self.search_fields.append(...)`) se filtraría a
|
|
44
|
+
# la clase y contaminaría otras requests. Respeta el override del
|
|
45
|
+
# subclass: `list(self.search_fields)` lee su atributo de clase.
|
|
46
|
+
self.search_fields = list(self.search_fields)
|
|
47
|
+
self.duplicate_check_fields = list(self.duplicate_check_fields)
|
|
48
|
+
self.kwargs_query = dict(self.kwargs_query)
|
|
33
49
|
endpoint_func = (
|
|
34
50
|
self.request.scope.get("endpoint") if self.request else None
|
|
35
51
|
)
|
|
@@ -66,7 +82,7 @@ class BaseService:
|
|
|
66
82
|
filters = filters or {}
|
|
67
83
|
return filters
|
|
68
84
|
|
|
69
|
-
async def retrieve(self, id: str):
|
|
85
|
+
async def retrieve(self, id: str) -> ModelT:
|
|
70
86
|
kwargs = self.get_kwargs_query()
|
|
71
87
|
obj = await self.repository.get_by_id(id, **kwargs)
|
|
72
88
|
if not obj:
|
|
@@ -125,7 +141,7 @@ class BaseService:
|
|
|
125
141
|
count: int = 25,
|
|
126
142
|
filters: Optional[Dict[str, Any]] = None,
|
|
127
143
|
order_by: Optional[str] = None, # Dynamic ordering (e.g., "-created_at" or "tool__name")
|
|
128
|
-
):
|
|
144
|
+
) -> Tuple[List[ModelT], int]:
|
|
129
145
|
kwargs = self.get_kwargs_query()
|
|
130
146
|
applied_filters = self.get_filters(filters)
|
|
131
147
|
|
|
@@ -151,34 +167,60 @@ class BaseService:
|
|
|
151
167
|
order_by=order_str,
|
|
152
168
|
**kwargs,
|
|
153
169
|
)
|
|
154
|
-
|
|
170
|
+
items, total = await self.repository.paginate_pipeline(
|
|
155
171
|
pipeline,
|
|
156
172
|
page=page,
|
|
157
173
|
count=count,
|
|
158
174
|
validate=self.aggregation_validate,
|
|
159
175
|
)
|
|
176
|
+
else:
|
|
177
|
+
# FindMany path
|
|
178
|
+
order_list = None
|
|
179
|
+
if order_str:
|
|
180
|
+
direction = -1 if order_str.startswith("-") else 1
|
|
181
|
+
field = order_str.lstrip("-")
|
|
182
|
+
order_list = [(field, direction)]
|
|
160
183
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
query = self.build_list_queryset(
|
|
185
|
+
search=search,
|
|
186
|
+
search_fields=self.search_fields,
|
|
187
|
+
filters=applied_filters,
|
|
188
|
+
order_by=order_list,
|
|
189
|
+
**kwargs,
|
|
190
|
+
)
|
|
191
|
+
items, total = await self.repository.paginate(
|
|
192
|
+
query, page, count, order_by=order_list
|
|
193
|
+
)
|
|
167
194
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
195
|
+
items = await self.post_process_list(items)
|
|
196
|
+
return items, total
|
|
197
|
+
|
|
198
|
+
async def post_process_list(self, items: List[ModelT]) -> List[ModelT]:
|
|
199
|
+
"""Hook: transforma/enriquece los items DE UNA PÁGINA ya paginada.
|
|
200
|
+
|
|
201
|
+
Este es el método para "hacer algo custom con los resultados" SIN
|
|
202
|
+
reescribir la paginación ni overridear `list()`. Corre después de
|
|
203
|
+
`paginate`/`paginate_pipeline`, sobre los items de la página actual
|
|
204
|
+
(no toda la colección), así que es seguro para un enrich con un `await`
|
|
205
|
+
extra por página (agrega un contador, resuelve un campo derivado, etc.).
|
|
206
|
+
|
|
207
|
+
Default: devuelve los items sin cambios. Ejemplo::
|
|
208
|
+
|
|
209
|
+
async def post_process_list(self, items):
|
|
210
|
+
ids = [c.id for c in items]
|
|
211
|
+
counts = await self.conv_repo.count_by_customer_ids(ids)
|
|
212
|
+
for c in items:
|
|
213
|
+
c.conversation_count = counts.get(c.id, 0)
|
|
214
|
+
return items
|
|
215
|
+
|
|
216
|
+
NO cambies el `total` acá (es el de la query completa). NO filtres items
|
|
217
|
+
(eso va en `get_filters`/`build_list_*`, o el total quedaría mal).
|
|
218
|
+
"""
|
|
219
|
+
return items
|
|
178
220
|
|
|
179
221
|
async def create(
|
|
180
222
|
self, payload: BaseModel, check_fields: Optional[List[str]] = None
|
|
181
|
-
) ->
|
|
223
|
+
) -> ModelT:
|
|
182
224
|
data = (
|
|
183
225
|
payload.model_dump() if not isinstance(payload, dict) else payload
|
|
184
226
|
)
|
|
@@ -197,7 +239,7 @@ class BaseService:
|
|
|
197
239
|
else created
|
|
198
240
|
)
|
|
199
241
|
|
|
200
|
-
async def update(self, id: str, data: BaseModel) ->
|
|
242
|
+
async def update(self, id: str, data: BaseModel) -> ModelT:
|
|
201
243
|
kwargs = self.get_kwargs_query()
|
|
202
244
|
obj = await self.repository.get_by_id(id, **kwargs)
|
|
203
245
|
if not obj:
|