fastapi-basekit 0.3.2__tar.gz → 0.3.3__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 (119) hide show
  1. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/PKG-INFO +1 -1
  2. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/controller/base.py +2 -0
  3. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/controller/base.py +111 -41
  4. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/controller/base.py +3 -0
  5. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/controller/base.py +3 -0
  6. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/PKG-INFO +1 -1
  7. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/SOURCES.txt +4 -0
  8. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/pyproject.toml +1 -1
  9. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_base_service.py +18 -1
  10. fastapi_basekit-0.3.3/tests/test_beanie_aggregation_hooks.py +484 -0
  11. fastapi_basekit-0.3.3/tests/test_beanie_aggregation_integration.py +199 -0
  12. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_controller_auto_permissions.py +56 -71
  13. fastapi_basekit-0.3.3/tests/test_crud_sqlmodel_repository_service.py +242 -0
  14. fastapi_basekit-0.3.3/tests/test_sql_queryset_override.py +332 -0
  15. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/LICENSE +0 -0
  16. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/README.md +0 -0
  17. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/__init__.py +0 -0
  18. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/__init__.py +0 -0
  19. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/__init__.py +0 -0
  20. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/controller/__init__.py +0 -0
  21. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/repository/__init__.py +0 -0
  22. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/repository/base.py +0 -0
  23. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/service/__init__.py +0 -0
  24. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/beanie/service/base.py +0 -0
  25. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/controller/__init__.py +0 -0
  26. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/permissions/__init__.py +0 -0
  27. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/permissions/base.py +0 -0
  28. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/__init__.py +0 -0
  29. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/controller/__init__.py +0 -0
  30. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/repository/__init__.py +0 -0
  31. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/repository/base.py +0 -0
  32. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/service/__init__.py +0 -0
  33. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/service/base.py +0 -0
  34. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlalchemy/session.py +0 -0
  35. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/__init__.py +0 -0
  36. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/controller/__init__.py +0 -0
  37. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/repository/__init__.py +0 -0
  38. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/repository/base.py +0 -0
  39. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/service/__init__.py +0 -0
  40. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/aio/sqlmodel/service/base.py +0 -0
  41. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/cli/__init__.py +0 -0
  42. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/cli/main.py +0 -0
  43. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/exceptions/__init__.py +0 -0
  44. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/exceptions/api_exceptions.py +0 -0
  45. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/exceptions/domain.py +0 -0
  46. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/exceptions/handler.py +0 -0
  47. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/schema/__init__.py +0 -0
  48. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/schema/base.py +0 -0
  49. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/schema/jwt.py +0 -0
  50. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/schema/schema.py +0 -0
  51. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/servicios/__init__.py +0 -0
  52. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/servicios/thrid/__init__.py +0 -0
  53. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/servicios/thrid/jwt.py +0 -0
  54. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/cookiecutter.json +0 -0
  55. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/hooks/post_gen_project.py +0 -0
  56. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/hooks/pre_gen_project.py +0 -0
  57. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.env.example +0 -0
  58. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.gitignore +0 -0
  59. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Dockerfile +0 -0
  60. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/LICENSE +0 -0
  61. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Makefile +0 -0
  62. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/README.md +0 -0
  63. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/env.py +0 -0
  64. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/script.py.mako +0 -0
  65. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic.ini +0 -0
  66. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/__init__.py +0 -0
  67. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/__init__.py +0 -0
  68. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/__init__.py +0 -0
  69. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/__init__.py +0 -0
  70. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/__init__.py +0 -0
  71. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/auth.py +0 -0
  72. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/__init__.py +0 -0
  73. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/user.py +0 -0
  74. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/routers.py +0 -0
  75. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/__init__.py +0 -0
  76. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/database.py +0 -0
  77. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/settings.py +0 -0
  78. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/main.py +0 -0
  79. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/__init__.py +0 -0
  80. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/auth.py +0 -0
  81. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/permissions.py +0 -0
  82. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/__init__.py +0 -0
  83. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/auth.py +0 -0
  84. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/base.py +0 -0
  85. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/enums.py +0 -0
  86. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/types.py +0 -0
  87. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/__init__.py +0 -0
  88. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/user.py +0 -0
  89. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/__init__.py +0 -0
  90. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/__init__.py +0 -0
  91. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/repository.py +0 -0
  92. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/__init__.py +0 -0
  93. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/auth.py +0 -0
  94. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/base.py +0 -0
  95. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/user.py +0 -0
  96. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/__init__.py +0 -0
  97. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init.py +0 -0
  98. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init_admin.py +0 -0
  99. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/__init__.py +0 -0
  100. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/auth_service.py +0 -0
  101. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/dependency.py +0 -0
  102. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/user_service.py +0 -0
  103. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/__init__.py +0 -0
  104. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/exception_handlers.py +0 -0
  105. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/security.py +0 -0
  106. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/docker-compose.yml +0 -0
  107. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/pytest.ini +0 -0
  108. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/requirements.txt +0 -0
  109. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/dependency_links.txt +0 -0
  110. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/entry_points.txt +0 -0
  111. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/requires.txt +0 -0
  112. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/fastapi_basekit.egg-info/top_level.txt +0 -0
  113. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/setup.cfg +0 -0
  114. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_api_exceptions.py +0 -0
  115. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_base_response.py +0 -0
  116. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_crud_beanie_controller.py +0 -0
  117. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_crud_controller.py +0 -0
  118. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/tests/test_jwt_service.py +0 -0
  119. {fastapi_basekit-0.3.2 → fastapi_basekit-0.3.3}/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.2
3
+ Version: 0.3.3
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
@@ -18,6 +18,7 @@ class BeanieBaseController(BaseController):
18
18
 
19
19
  async def list(self):
20
20
  """Lista documentos con paginación usando Beanie."""
21
+ await self.prepare_action("list")
21
22
  params = self._params(skip_frames=2)
22
23
  items, total = await self.service.list(**params)
23
24
  count = params.get("count") or 0
@@ -39,6 +40,7 @@ class BeanieBaseController(BaseController):
39
40
  check_fields: Optional[List[str]] = None,
40
41
  ):
41
42
  """Crea un nuevo documento con validación de campos únicos."""
43
+ await self.prepare_action("create")
42
44
  result = await self.service.create(validated_data, check_fields)
43
45
  return self.format_response(result, message="Creado exitosamente")
44
46
 
@@ -14,7 +14,11 @@ class BaseController:
14
14
 
15
15
  service = Depends()
16
16
  schema_class: ClassVar[Type[BaseModel]]
17
- action: ClassVar[Optional[str]] = None
17
+
18
+ # DRF Style: Permisos globales por defecto
19
+ permission_classes: ClassVar[List[Type[BasePermission]]] = []
20
+
21
+ action: Optional[str] = None
18
22
  request: Request
19
23
  _params_excluded_fields: ClassVar[Set[str]] = {
20
24
  "self",
@@ -32,34 +36,73 @@ class BaseController:
32
36
  }
33
37
 
34
38
  def __init__(self) -> None:
35
- endpoint_func = (
36
- self.request.scope.get("endpoint")
37
- if hasattr(self, "request") and self.request
38
- else None
39
- )
40
- self.action = endpoint_func.__name__ if endpoint_func else None
39
+ """Inicializa el controller."""
40
+ pass
41
+
42
+ def get_permissions(self) -> List[Type[BasePermission]]:
43
+ """
44
+ Instancia y retorna la lista de permisos que esta vista requiere.
45
+
46
+ Sobrescribir esto permite lógica tipo DRF:
47
+
48
+ if self.action == 'list':
49
+ return [AllowAny]
50
+ return [IsAuthenticated]
51
+ """
52
+ return self.permission_classes
53
+
54
+ async def prepare_action(self, action_name: str) -> None:
55
+ """Set the current action and run permission checks.
56
+
57
+ Auto-called by ``ControllerMeta`` for every public async method on
58
+ the controller. Idempotent within one invocation: if the same
59
+ ``action_name`` has already been prepared on this instance,
60
+ subsequent calls are no-ops. This lets custom methods opt into
61
+ calling ``await self.prepare_action(...)`` explicitly without
62
+ double-firing permission checks (when the metaclass already ran
63
+ before entering the method body).
64
+ """
65
+ if getattr(self, "_basekit_prepared_action", None) == action_name:
66
+ return
67
+ self.action = action_name
68
+ self._basekit_prepared_action = action_name
69
+ await self.check_permissions()
70
+
71
+ async def check_permissions(self):
72
+ """Run each declared permission. Raises ``PermissionException``
73
+ on the first denial.
74
+ """
75
+ for permission_class in self.get_permissions():
76
+ permission = permission_class()
77
+ has_perm = await permission.has_permission(self.request)
78
+ if not has_perm:
79
+ message = getattr(
80
+ permission,
81
+ "message_exception",
82
+ "No tienes permiso para realizar esta acción.",
83
+ )
84
+ raise PermissionException(message)
85
+
86
+ async def check_permissions_class(self):
87
+ """Backward-compat alias for ``check_permissions``.
88
+
89
+ Pre-0.3.2 controllers called this manually inside endpoint methods.
90
+ Keep working for users who haven't migrated to ``permission_classes``
91
+ + auto-wrapping yet. New code should declare ``permission_classes``
92
+ on the controller and let the metaclass run permissions.
93
+ """
94
+ await self.check_permissions()
41
95
 
42
96
  def get_schema_class(self) -> Type[BaseModel]:
43
97
  assert self.schema_class is not None, (
44
98
  "'%s' should either include a `schema_class` attribute, "
45
- "or override the `get_serializer_class()` method."
99
+ "or override the `get_schema_class()` method."
46
100
  % self.__class__.__name__
47
101
  )
48
102
  return self.schema_class
49
103
 
50
- async def check_permissions_class(self):
51
- permissions = self.check_permissions()
52
- if permissions:
53
- for permission in permissions:
54
- obj = permission()
55
- check = await obj.has_permission(self.request)
56
- if not check:
57
- raise PermissionException(obj.message_exception)
58
-
59
- def check_permissions(self) -> List[Type[BasePermission]]:
60
- pass
61
-
62
104
  async def list(self):
105
+ await self.prepare_action("list")
63
106
  params = self._params()
64
107
  items, total = await self.service.list(**params)
65
108
  count = params.get("count") or 0
@@ -75,18 +118,22 @@ class BaseController:
75
118
  return self.format_response(data=items, pagination=pagination)
76
119
 
77
120
  async def retrieve(self, id: str):
121
+ await self.prepare_action("retrieve")
78
122
  item = await self.service.retrieve(id)
79
123
  return self.format_response(data=item)
80
124
 
81
125
  async def create(self, validated_data: Any):
126
+ await self.prepare_action("create")
82
127
  result = await self.service.create(validated_data)
83
128
  return self.format_response(result, message="Creado exitosamente")
84
129
 
85
130
  async def update(self, id: str, validated_data: Any):
131
+ await self.prepare_action("update")
86
132
  result = await self.service.update(id, validated_data)
87
133
  return self.format_response(result, message="Actualizado exitosamente")
88
134
 
89
135
  async def delete(self, id: str):
136
+ await self.prepare_action("delete")
90
137
  await self.service.delete(id)
91
138
  return self.format_response(None, message="Eliminado exitosamente")
92
139
 
@@ -99,33 +146,48 @@ class BaseController:
99
146
  ) -> BaseModel:
100
147
  schema = self.get_schema_class()
101
148
 
149
+ # Robust Pydantic v2 validation. Each branch falls back to the
150
+ # raw value when the schema doesn't fit (custom-action endpoints
151
+ # often return ad-hoc dicts that don't match the controller's
152
+ # default schema_class — those should pass through untouched).
102
153
  if isinstance(data, list):
103
154
  data_dicts = [self.to_dict(item) for item in data]
104
- adapter = TypeAdapter(List[schema])
105
- data_parsed = adapter.validate_python(data_dicts)
106
- elif self.service.repository and isinstance(
107
- data, self.service.repository.model
108
- ):
109
- data_parsed = self.to_dict(data)
110
- data_parsed = schema.model_validate(data_parsed)
155
+ try:
156
+ adapter = TypeAdapter(List[schema])
157
+ data_parsed = adapter.validate_python(data_dicts)
158
+ except Exception:
159
+ data_parsed = data_dicts
160
+
111
161
  elif isinstance(data, dict):
112
- data_parsed = schema.model_validate(data)
162
+ try:
163
+ data_parsed = schema.model_validate(data)
164
+ except Exception:
165
+ data_parsed = data
166
+
167
+ elif hasattr(data, "__dict__"):
168
+ data_dict = self.to_dict(data)
169
+ try:
170
+ data_parsed = schema.model_validate(data_dict)
171
+ except Exception:
172
+ data_parsed = data_dict
173
+
174
+ elif data is None:
175
+ data_parsed = None
113
176
  else:
114
177
  data_parsed = data
115
178
 
179
+ response_cls = BasePaginationResponse if pagination else BaseResponse
180
+
181
+ # Construcción dinámica de argumentos
182
+ kwargs = {
183
+ "data": data_parsed,
184
+ "message": message or "Operación exitosa",
185
+ "status": response_status,
186
+ }
116
187
  if pagination:
117
- return BasePaginationResponse(
118
- data=data_parsed,
119
- pagination=pagination,
120
- message=message or "Operación exitosa",
121
- status=response_status,
122
- )
123
- else:
124
- return BaseResponse(
125
- data=data_parsed,
126
- message=message or "Operación exitosa",
127
- status=response_status,
128
- )
188
+ kwargs["pagination"] = pagination
189
+
190
+ return response_cls(**kwargs)
129
191
 
130
192
  def _params(self, skip_frames: int = 1) -> Dict[str, Any]:
131
193
  """
@@ -208,6 +270,14 @@ class BaseController:
208
270
  }
209
271
 
210
272
  def to_dict(self, obj: Any):
211
- if hasattr(obj, "model_dump"):
273
+ """Helper para convertir modelos ORM/Pydantic a dict."""
274
+ if hasattr(obj, "model_dump"): # Pydantic v2
212
275
  return obj.model_dump()
276
+ if hasattr(obj, "dict"): # Pydantic v1
277
+ return obj.dict()
278
+ if hasattr(obj, "__dict__"): # SQLAlchemy models (basic)
279
+ # Filtramos atributos privados de SQLAlchemy
280
+ return {
281
+ k: v for k, v in obj.__dict__.items() if not k.startswith("_")
282
+ }
213
283
  return obj
@@ -48,6 +48,7 @@ class SQLAlchemyBaseController(BaseController):
48
48
  joins: Lista de relaciones a hacer JOIN eager loading
49
49
  order_by: Expresión de ordenamiento (ej: User.created_at.desc())
50
50
  """
51
+ await self.prepare_action("list")
51
52
  params = self._params(skip_frames=2)
52
53
  service_params = {
53
54
  **params,
@@ -73,6 +74,7 @@ class SQLAlchemyBaseController(BaseController):
73
74
  id: ID del registro
74
75
  joins: Lista de relaciones a hacer JOIN eager loading
75
76
  """
77
+ await self.prepare_action("retrieve")
76
78
  item = await self.service.retrieve(id, joins=joins)
77
79
  return self.format_response(data=item)
78
80
 
@@ -89,6 +91,7 @@ class SQLAlchemyBaseController(BaseController):
89
91
  validated_data: Datos validados para crear
90
92
  check_fields: Campos a verificar por duplicados antes de crear
91
93
  """
94
+ await self.prepare_action("create")
92
95
  result = await self.service.create(validated_data, check_fields)
93
96
  return self.format_response(result, message="Creado exitosamente")
94
97
 
@@ -49,6 +49,7 @@ class SQLModelBaseController(BaseController):
49
49
  joins: Lista de relaciones para eager loading.
50
50
  order_by: Expresión de ordenamiento (ej: ``"-created_at"``).
51
51
  """
52
+ await self.prepare_action("list")
52
53
  params = self._params(skip_frames=2)
53
54
  service_params = {
54
55
  **params,
@@ -73,6 +74,7 @@ class SQLModelBaseController(BaseController):
73
74
  id: ID del registro.
74
75
  joins: Lista de relaciones para eager loading.
75
76
  """
77
+ await self.prepare_action("retrieve")
76
78
  item = await self.service.retrieve(id, joins=joins)
77
79
  return self.format_response(data=item)
78
80
 
@@ -88,6 +90,7 @@ class SQLModelBaseController(BaseController):
88
90
  validated_data: Datos validados para crear.
89
91
  check_fields: Campos a verificar por duplicados antes de crear.
90
92
  """
93
+ await self.prepare_action("create")
91
94
  result = await self.service.create(validated_data, check_fields)
92
95
  return self.format_response(result, message="Creado exitosamente")
93
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-basekit
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -106,8 +106,12 @@ fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/securi
106
106
  tests/test_api_exceptions.py
107
107
  tests/test_base_response.py
108
108
  tests/test_base_service.py
109
+ tests/test_beanie_aggregation_hooks.py
110
+ tests/test_beanie_aggregation_integration.py
109
111
  tests/test_controller_auto_permissions.py
110
112
  tests/test_crud_beanie_controller.py
111
113
  tests/test_crud_controller.py
114
+ tests/test_crud_sqlmodel_repository_service.py
112
115
  tests/test_jwt_service.py
116
+ tests/test_sql_queryset_override.py
113
117
  tests/test_sqlalchemy_base_service_order.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-basekit"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Utilities and base classes for FastAPI async projects (Beanie, SQLAlchemy or SQLModel)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -47,7 +47,24 @@ class FakeRepository:
47
47
  if all(obj.get(k) == v for k, v in (filters or {}).items())
48
48
  ]
49
49
 
50
- async def paginate(self, query, page, count):
50
+ def build_list_queryset(
51
+ self,
52
+ search=None,
53
+ search_fields=None,
54
+ filters=None,
55
+ order_by=None,
56
+ **kwargs,
57
+ ):
58
+ # Synchronous wrapper compatible with the new BaseService.list contract.
59
+ return self.build_filter_query(
60
+ search=search,
61
+ search_fields=search_fields or [],
62
+ filters=filters or {},
63
+ order_by=order_by,
64
+ **kwargs,
65
+ )
66
+
67
+ async def paginate(self, query, page, count, order_by=None):
51
68
  # Si query es una coroutine, esperarla primero
52
69
  if hasattr(query, "__await__"):
53
70
  query = await query