fastapi-basekit 0.4.3__tar.gz → 0.5.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.
Files changed (147) hide show
  1. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/PKG-INFO +3 -3
  2. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/controller/base.py +1 -1
  3. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/repository/base.py +76 -12
  4. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/service/base.py +60 -26
  5. fastapi_basekit-0.5.2/fastapi_basekit/aio/controller/base.py +438 -0
  6. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/controller/base.py +1 -1
  7. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/repository/base.py +93 -16
  8. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/service/base.py +48 -10
  9. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/repository/base.py +120 -22
  10. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/service/base.py +36 -11
  11. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/exceptions/handler.py +15 -0
  12. fastapi_basekit-0.5.2/fastapi_basekit/py.typed +1 -0
  13. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/servicios/thrid/jwt.py +42 -7
  14. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/PKG-INFO +3 -3
  15. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/SOURCES.txt +21 -1
  16. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/requires.txt +2 -2
  17. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/pyproject.toml +4 -3
  18. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_action_param_leak.py +41 -2
  19. fastapi_basekit-0.5.2/tests/test_beanie_repository_regression.py +295 -0
  20. fastapi_basekit-0.5.2/tests/test_beanie_service_regression.py +244 -0
  21. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_exception_handlers.py +30 -0
  22. fastapi_basekit-0.5.2/tests/test_generics_optional_and_cache.py +99 -0
  23. fastapi_basekit-0.5.2/tests/test_generics_wiring.py +100 -0
  24. fastapi_basekit-0.5.2/tests/test_jwt_config_robustness.py +61 -0
  25. fastapi_basekit-0.5.2/tests/test_params_classification.py +193 -0
  26. fastapi_basekit-0.5.2/tests/test_params_coercion_matrix.py +110 -0
  27. fastapi_basekit-0.5.2/tests/test_params_coercion_unit.py +165 -0
  28. fastapi_basekit-0.5.2/tests/test_params_http_matrix_beanie.py +109 -0
  29. fastapi_basekit-0.5.2/tests/test_params_http_matrix_sqlalchemy.py +114 -0
  30. fastapi_basekit-0.5.2/tests/test_params_rich_type_coercion.py +79 -0
  31. fastapi_basekit-0.5.2/tests/test_path_filter_mixin_compat.py +179 -0
  32. fastapi_basekit-0.5.2/tests/test_post_process_list_hook.py +154 -0
  33. fastapi_basekit-0.5.2/tests/test_pulbot_cbv_pattern_beanie.py +146 -0
  34. fastapi_basekit-0.5.2/tests/test_repo_signature_aliases.py +74 -0
  35. fastapi_basekit-0.5.2/tests/test_sqlalchemy_repository_regression.py +173 -0
  36. fastapi_basekit-0.5.2/tests/test_sqlalchemy_service_regression.py +154 -0
  37. fastapi_basekit-0.5.2/tests/test_sqlmodel_filter_operators.py +92 -0
  38. fastapi_basekit-0.5.2/tests/test_typed_filter_coercion.py +114 -0
  39. fastapi_basekit-0.4.3/fastapi_basekit/aio/controller/base.py +0 -301
  40. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/LICENSE +0 -0
  41. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/README.md +0 -0
  42. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/__init__.py +0 -0
  43. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/__init__.py +0 -0
  44. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/__init__.py +0 -0
  45. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/controller/__init__.py +0 -0
  46. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/repository/__init__.py +0 -0
  47. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/beanie/service/__init__.py +0 -0
  48. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/controller/__init__.py +0 -0
  49. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/permissions/__init__.py +0 -0
  50. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/permissions/base.py +0 -0
  51. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/__init__.py +0 -0
  52. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/controller/__init__.py +0 -0
  53. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/repository/__init__.py +0 -0
  54. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/service/__init__.py +0 -0
  55. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlalchemy/session.py +0 -0
  56. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/__init__.py +0 -0
  57. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/controller/__init__.py +0 -0
  58. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/controller/base.py +0 -0
  59. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/repository/__init__.py +0 -0
  60. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/aio/sqlmodel/service/__init__.py +0 -0
  61. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/cli/__init__.py +0 -0
  62. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/cli/main.py +0 -0
  63. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/exceptions/__init__.py +0 -0
  64. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/exceptions/api_exceptions.py +0 -0
  65. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/exceptions/domain.py +0 -0
  66. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/openapi.py +0 -0
  67. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/schema/__init__.py +0 -0
  68. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/schema/base.py +0 -0
  69. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/schema/jwt.py +0 -0
  70. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/schema/schema.py +0 -0
  71. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/servicios/__init__.py +0 -0
  72. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/servicios/thrid/__init__.py +0 -0
  73. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/cookiecutter.json +0 -0
  74. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/hooks/post_gen_project.py +0 -0
  75. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/hooks/pre_gen_project.py +0 -0
  76. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.env.example +0 -0
  77. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.gitignore +0 -0
  78. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Dockerfile +0 -0
  79. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/LICENSE +0 -0
  80. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Makefile +0 -0
  81. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/README.md +0 -0
  82. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/env.py +0 -0
  83. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/script.py.mako +0 -0
  84. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic.ini +0 -0
  85. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/__init__.py +0 -0
  86. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/__init__.py +0 -0
  87. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/__init__.py +0 -0
  88. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/__init__.py +0 -0
  89. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/__init__.py +0 -0
  90. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/auth.py +0 -0
  91. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/__init__.py +0 -0
  92. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/user.py +0 -0
  93. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/routers.py +0 -0
  94. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/__init__.py +0 -0
  95. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/database.py +0 -0
  96. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/settings.py +0 -0
  97. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/main.py +0 -0
  98. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/__init__.py +0 -0
  99. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/auth.py +0 -0
  100. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/permissions.py +0 -0
  101. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/__init__.py +0 -0
  102. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/auth.py +0 -0
  103. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/base.py +0 -0
  104. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/enums.py +0 -0
  105. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/types.py +0 -0
  106. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/__init__.py +0 -0
  107. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/user.py +0 -0
  108. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/__init__.py +0 -0
  109. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/__init__.py +0 -0
  110. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/repository.py +0 -0
  111. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/__init__.py +0 -0
  112. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/auth.py +0 -0
  113. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/base.py +0 -0
  114. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/user.py +0 -0
  115. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/__init__.py +0 -0
  116. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init.py +0 -0
  117. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init_admin.py +0 -0
  118. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/__init__.py +0 -0
  119. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/auth_service.py +0 -0
  120. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/dependency.py +0 -0
  121. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/user_service.py +0 -0
  122. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/__init__.py +0 -0
  123. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/exception_handlers.py +0 -0
  124. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/security.py +0 -0
  125. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/docker-compose.yml +0 -0
  126. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/pytest.ini +0 -0
  127. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/requirements.txt +0 -0
  128. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/dependency_links.txt +0 -0
  129. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/entry_points.txt +0 -0
  130. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/fastapi_basekit.egg-info/top_level.txt +0 -0
  131. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/setup.cfg +0 -0
  132. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_api_exceptions.py +0 -0
  133. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_base_response.py +0 -0
  134. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_base_service.py +0 -0
  135. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_beanie_aggregation_hooks.py +0 -0
  136. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_beanie_aggregation_integration.py +0 -0
  137. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_beanie_keyset_pagination.py +0 -0
  138. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_beanie_service_mutable_defaults.py +0 -0
  139. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_controller_auto_permissions.py +0 -0
  140. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_crud_beanie_controller.py +0 -0
  141. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_crud_controller.py +0 -0
  142. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_crud_sqlmodel_repository_service.py +0 -0
  143. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_filter_operators.py +0 -0
  144. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_jwt_service.py +0 -0
  145. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_openapi_simplify.py +0 -0
  146. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.2}/tests/test_sql_queryset_override.py +0 -0
  147. {fastapi_basekit-0.4.3 → fastapi_basekit-0.5.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.4.3
3
+ Version: 0.5.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
@@ -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"
@@ -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(skip_frames=2)
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
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Any, Dict, List, Optional, Type, Union
2
+ from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union
3
3
  from typing import get_args, get_origin
4
4
  import re
5
5
 
@@ -11,10 +11,31 @@ from beanie.operators import Or, RegEx
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
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]
14
38
 
15
- class BaseRepository:
16
- model: Type[Document]
17
-
18
39
  def _parse_order_field(self, order_by: str) -> tuple[str, int, bool]:
19
40
  """Parse order_by string into components.
20
41
 
@@ -194,6 +215,19 @@ class BaseRepository:
194
215
  exprs.append(field_attr.id == _coerce_objectid(v))
195
216
  continue
196
217
 
218
+ # Clave no resoluble (typo, campo inexistente, nesting mal escrito).
219
+ # Se descarta — pero se AVISA: si la clave venía de `get_filters`
220
+ # (scoping por tenant/owner), su desaparición silenciosa deja el
221
+ # listado sin filtrar → fuga cross-tenant (IDOR). El warning hace
222
+ # visible la pérdida en logs/Sentry en vez de fallar en silencio.
223
+ logger.warning(
224
+ "build_filter_query: filtro '%s' descartado (no resuelve a "
225
+ "ningún campo de %s). Si era un filtro de scoping, el listado "
226
+ "queda SIN ese filtro.",
227
+ k,
228
+ getattr(self.model, "__name__", self.model),
229
+ )
230
+
197
231
  # Raw MongoDB filters go first so Beanie processes them as a dict condition
198
232
  query_args: list = ([raw_filters] if raw_filters else []) + exprs
199
233
  query = self.model.find(*query_args, **self._get_query_kwargs(**kwargs))
@@ -207,6 +241,17 @@ class BaseRepository:
207
241
  async def paginate(
208
242
  self, query: FindMany[Document], page: int, count: int, order_by: Optional[List[tuple]] = None
209
243
  ) -> tuple[List[Document], int]:
244
+ """MOTOR DE PAGINACIÓN (FindMany) — NO LO REIMPLEMENTES.
245
+
246
+ Único loop de paginación offset (count + skip/limit). Para personalizar
247
+ el listado, override el HOOK correcto, no copies skip/limit/count:
248
+ - query/filtros/scoping/orden → `build_list_queryset` (o `build_filter_query`)
249
+ - join `$lookup` / shape plano → `use_aggregation=True` + `build_list_pipeline`
250
+ - filtros por usuario/acción → `Service.get_filters`
251
+ - orden por defecto → `Service.get_order`
252
+ - enriquecer items de la página→ `Service.post_process_list`
253
+ - scroll infinito / cursor → `paginate_keyset` (método+endpoint aparte)
254
+ """
210
255
  # Apply ordering if provided and not already applied
211
256
  if order_by:
212
257
  query = query.sort(order_by)
@@ -379,7 +424,13 @@ class BaseRepository:
379
424
  count: int,
380
425
  validate: bool = True,
381
426
  ) -> tuple[List[Any], int]:
382
- """Run an aggregation pipeline with `$facet` pagination.
427
+ """MOTOR DE PAGINACIÓN (aggregation `$facet`) — NO LO REIMPLEMENTES.
428
+
429
+ Ejecuta el pipeline con paginación `$facet`. Para un listado agregado
430
+ NO copies este `$facet`/skip/limit: override `Service.build_list_pipeline`
431
+ (agrega `$lookup`/`$project`/`$group`) y activa `use_aggregation=True`;
432
+ este método le pone la paginación. Si la proyección no es el modelo,
433
+ `aggregation_validate=False`.
383
434
 
384
435
  Args:
385
436
  pipeline: Pipeline stages (without the final `$facet`).
@@ -471,7 +522,7 @@ class BaseRepository:
471
522
  self,
472
523
  obj_id: Union[str, ObjectId],
473
524
  **kwargs,
474
- ) -> Optional[Document]:
525
+ ) -> Optional[ModelT]:
475
526
  if not isinstance(obj_id, ObjectId):
476
527
  obj_id = ObjectId(obj_id)
477
528
  return await self.model.find_one(
@@ -479,12 +530,19 @@ class BaseRepository:
479
530
  **self._get_query_kwargs(**kwargs),
480
531
  )
481
532
 
533
+ async def get(
534
+ self, obj_id: Union[str, ObjectId], **kwargs
535
+ ) -> Optional[ModelT]:
536
+ """Alias de `get_by_id(id)` — nombre unificado con los repos SQL
537
+ (`get`) para que las lecturas por id sean portables entre ORMs."""
538
+ return await self.get_by_id(obj_id, **kwargs)
539
+
482
540
  async def get_by_field(
483
541
  self,
484
542
  field_name: str,
485
543
  value: Any,
486
544
  **kwargs,
487
- ) -> Optional[Document]:
545
+ ) -> Optional[ModelT]:
488
546
  if not hasattr(self.model, field_name):
489
547
  raise AttributeError(
490
548
  f"{self.model.__name__} no tiene el campo '{field_name}'"
@@ -498,7 +556,7 @@ class BaseRepository:
498
556
  self,
499
557
  filters: Dict[str, Any],
500
558
  **kwargs,
501
- ) -> Optional[Document]:
559
+ ) -> Optional[ModelT]:
502
560
  exprs = [
503
561
  getattr(self.model, f) == v
504
562
  for f, v in filters.items()
@@ -513,21 +571,27 @@ class BaseRepository:
513
571
  async def list_all(
514
572
  self,
515
573
  **kwargs,
516
- ) -> List[Document]:
574
+ ) -> List[ModelT]:
517
575
  query = self.model.find_all(**self._get_query_kwargs(**kwargs))
518
576
  return await query.to_list()
519
577
 
520
- async def create(self, obj: Union[Document, Dict[str, Any]]) -> Document:
578
+ async def create(self, obj: Union[ModelT, Dict[str, Any]]) -> ModelT:
521
579
  if isinstance(obj, dict):
522
580
  obj = self.model(**obj)
523
581
  await obj.insert()
524
582
  return obj
525
583
 
526
- async def update(self, obj: Document, data: Dict[str, Any]) -> Document:
584
+ async def update(self, obj: ModelT, data: Dict[str, Any]) -> ModelT:
585
+ """Actualiza un Document Beanie ya cargado.
586
+
587
+ Divergencia con SQL: acá el 1er arg es el **Document** (no el id) y SÍ
588
+ setea ``None``. El repo SQLAlchemy usa ``update(id, dict)`` y omite
589
+ ``None``. Ver la tabla en el CLAUDE.md de la lib.
590
+ """
527
591
  for key, value in data.items():
528
592
  setattr(obj, key, value)
529
593
  await obj.save()
530
594
  return obj
531
595
 
532
- async def delete(self, obj: Document) -> None:
596
+ async def delete(self, obj: ModelT) -> None:
533
597
  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
- repository: BaseRepository
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]] = {}
@@ -74,7 +82,7 @@ class BaseService:
74
82
  filters = filters or {}
75
83
  return filters
76
84
 
77
- async def retrieve(self, id: str):
85
+ async def retrieve(self, id: str) -> ModelT:
78
86
  kwargs = self.get_kwargs_query()
79
87
  obj = await self.repository.get_by_id(id, **kwargs)
80
88
  if not obj:
@@ -133,7 +141,7 @@ class BaseService:
133
141
  count: int = 25,
134
142
  filters: Optional[Dict[str, Any]] = None,
135
143
  order_by: Optional[str] = None, # Dynamic ordering (e.g., "-created_at" or "tool__name")
136
- ):
144
+ ) -> Tuple[List[ModelT], int]:
137
145
  kwargs = self.get_kwargs_query()
138
146
  applied_filters = self.get_filters(filters)
139
147
 
@@ -159,34 +167,60 @@ class BaseService:
159
167
  order_by=order_str,
160
168
  **kwargs,
161
169
  )
162
- return await self.repository.paginate_pipeline(
170
+ items, total = await self.repository.paginate_pipeline(
163
171
  pipeline,
164
172
  page=page,
165
173
  count=count,
166
174
  validate=self.aggregation_validate,
167
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)]
168
183
 
169
- # FindMany path
170
- order_list = None
171
- if order_str:
172
- direction = -1 if order_str.startswith("-") else 1
173
- field = order_str.lstrip("-")
174
- order_list = [(field, direction)]
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
+ )
175
194
 
176
- query = self.build_list_queryset(
177
- search=search,
178
- search_fields=self.search_fields,
179
- filters=applied_filters,
180
- order_by=order_list,
181
- **kwargs,
182
- )
183
- return await self.repository.paginate(
184
- query, page, count, order_by=order_list
185
- )
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
186
220
 
187
221
  async def create(
188
222
  self, payload: BaseModel, check_fields: Optional[List[str]] = None
189
- ) -> Any:
223
+ ) -> ModelT:
190
224
  data = (
191
225
  payload.model_dump() if not isinstance(payload, dict) else payload
192
226
  )
@@ -205,7 +239,7 @@ class BaseService:
205
239
  else created
206
240
  )
207
241
 
208
- async def update(self, id: str, data: BaseModel) -> Any:
242
+ async def update(self, id: str, data: BaseModel) -> ModelT:
209
243
  kwargs = self.get_kwargs_query()
210
244
  obj = await self.repository.get_by_id(id, **kwargs)
211
245
  if not obj: