hexcore 9.0.0__tar.gz → 9.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. {hexcore-9.0.0/hexcore.egg-info → hexcore-9.2.1}/PKG-INFO +4 -4
  2. {hexcore-9.0.0 → hexcore-9.2.1}/README.md +3 -3
  3. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/plugins.py +12 -0
  4. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/plugins.py +16 -0
  5. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/dependencies.py +25 -0
  6. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/routers.py +44 -15
  7. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/router.py +41 -3
  8. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/router.py +34 -8
  9. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/router.py +23 -2
  10. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/router.py +18 -6
  11. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/router.py +31 -3
  12. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/__init__.py +12 -0
  13. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/router.py +3 -1
  14. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/app.py +36 -13
  15. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/exception_handlers.py +40 -3
  16. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/_sqlalchemy_impl.py +2 -0
  17. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.py +8 -0
  18. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/__init__.py +54 -17
  19. {hexcore-9.0.0 → hexcore-9.2.1/hexcore.egg-info}/PKG-INFO +4 -4
  20. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/SOURCES.txt +2 -0
  21. {hexcore-9.0.0 → hexcore-9.2.1}/pyproject.toml +28 -4
  22. hexcore-9.2.1/tests/test_anclas_del_repo.py +119 -0
  23. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_exception_handlers.py +89 -0
  24. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_backend_neutrality.py +2 -3
  25. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_cli.py +3 -8
  26. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_models.py +2 -4
  27. hexcore-9.2.1/tests/test_darwin_openapi.py +125 -0
  28. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_plugin_decoupling.py +4 -2
  29. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_two_factor.py +75 -6
  30. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_deprecations.py +2 -7
  31. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_documentation_examples.py +1 -2
  32. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_uow_integration.py +101 -0
  33. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_packaging.py +9 -9
  34. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_typing_gate.py +2 -1
  35. {hexcore-9.0.0 → hexcore-9.2.1}/LICENSE +0 -0
  36. {hexcore-9.0.0 → hexcore-9.2.1}/MANIFEST.in +0 -0
  37. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/__init__.py +0 -0
  38. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/__main__.py +0 -0
  39. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/_deprecation.py +0 -0
  40. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/__init__.py +0 -0
  41. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/__init__.py +0 -0
  42. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/adapters.py +0 -0
  43. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/config.py +0 -0
  44. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/factory.py +0 -0
  45. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/in_memory_buses.py +0 -0
  46. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/pipeline.py +0 -0
  47. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/registry.py +0 -0
  48. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/cqrs/scheduler.py +0 -0
  49. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/__init__.py +0 -0
  50. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/base.py +0 -0
  51. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/cursor.py +0 -0
  52. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/errors.py +0 -0
  53. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/dtos/query.py +0 -0
  54. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/__init__.py +0 -0
  55. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/config.py +0 -0
  56. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/factory.py +0 -0
  57. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/projector.py +0 -0
  58. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/relay.py +0 -0
  59. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/eventsourcing/repository.py +0 -0
  60. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/__init__.py +0 -0
  61. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/base.py +0 -0
  62. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/application/use_cases/query.py +0 -0
  63. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/capabilities.py +0 -0
  64. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/config.py +0 -0
  65. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/cqrs.py +0 -0
  66. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/cqrs.pyi +0 -0
  67. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/__init__.py +0 -0
  68. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/__init__.pyi +0 -0
  69. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/__init__.py +0 -0
  70. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/commands.py +0 -0
  71. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/config.py +0 -0
  72. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/container.py +0 -0
  73. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/hooks.py +0 -0
  74. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/application/services.py +0 -0
  75. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/__init__.py +0 -0
  76. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/context.py +0 -0
  77. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/entities.py +0 -0
  78. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/events.py +0 -0
  79. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/exceptions.py +0 -0
  80. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/permissions.py +0 -0
  81. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/ports.py +0 -0
  82. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/domain/value_objects.py +0 -0
  83. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/__init__.py +0 -0
  84. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/__init__.py +0 -0
  85. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/api/middlewares.py +0 -0
  86. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/cli.py +0 -0
  87. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/clock.py +0 -0
  88. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/envelope.py +0 -0
  89. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/hashing.py +0 -0
  90. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/keys.py +0 -0
  91. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/lifespan.py +0 -0
  92. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/__init__.py +0 -0
  93. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/__init__.py +0 -0
  94. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/documents.py +0 -0
  95. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/repositories.py +0 -0
  96. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/beanie/schema.py +0 -0
  97. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/selection.py +0 -0
  98. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/__init__.py +0 -0
  99. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/models.py +0 -0
  100. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/models_mixins.py +0 -0
  101. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/repositories.py +0 -0
  102. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/orms/sqlalchemy/schema.py +0 -0
  103. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/revocation.py +0 -0
  104. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/secretbox.py +0 -0
  105. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/tokens.py +0 -0
  106. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/infrastructure/transports.py +0 -0
  107. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/__init__.py +0 -0
  108. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/__init__.py +0 -0
  109. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/commands.py +0 -0
  110. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/domain.py +0 -0
  111. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/impersonate/service.py +0 -0
  112. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/__init__.py +0 -0
  113. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/magic_link/commands.py +0 -0
  114. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/__init__.py +0 -0
  115. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/domain.py +0 -0
  116. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/http.py +0 -0
  117. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/__init__.py +0 -0
  118. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/beanie/__init__.py +0 -0
  119. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/beanie/repository.py +0 -0
  120. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/__init__.py +0 -0
  121. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models.py +0 -0
  122. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/models_mixins.py +0 -0
  123. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/orms/sqlalchemy/repository.py +0 -0
  124. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/providers.py +0 -0
  125. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/oauth/service.py +0 -0
  126. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/__init__.py +0 -0
  127. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/domain.py +0 -0
  128. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/__init__.py +0 -0
  129. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/beanie/__init__.py +0 -0
  130. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/beanie/repository.py +0 -0
  131. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/__init__.py +0 -0
  132. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models.py +0 -0
  133. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/models_mixins.py +0 -0
  134. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/orms/sqlalchemy/repository.py +0 -0
  135. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/organization/service.py +0 -0
  136. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/__init__.py +0 -0
  137. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/domain.py +0 -0
  138. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/__init__.py +0 -0
  139. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/beanie/__init__.py +0 -0
  140. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/beanie/repository.py +0 -0
  141. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/__init__.py +0 -0
  142. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models.py +0 -0
  143. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/models_mixins.py +0 -0
  144. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/orms/sqlalchemy/repository.py +0 -0
  145. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/service.py +0 -0
  146. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/passkey/webauthn_adapter.py +0 -0
  147. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/storage.py +0 -0
  148. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/commands.py +0 -0
  149. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/crypto.py +0 -0
  150. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/domain.py +0 -0
  151. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/__init__.py +0 -0
  152. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/beanie/__init__.py +0 -0
  153. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/beanie/repository.py +0 -0
  154. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/__init__.py +0 -0
  155. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models.py +0 -0
  156. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/models_mixins.py +0 -0
  157. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/orms/sqlalchemy/repository.py +0 -0
  158. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/service.py +0 -0
  159. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/plugins/two_factor/totp.py +0 -0
  160. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/__init__.py +0 -0
  161. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/fakes.py +0 -0
  162. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/fixtures.py +0 -0
  163. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/darwin/testing/helpers.py +0 -0
  164. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/__init__.py +0 -0
  165. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/__init__.py +0 -0
  166. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/permissions.py +0 -0
  167. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/auth/value_objects.py +0 -0
  168. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/base.py +0 -0
  169. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/__init__.py +0 -0
  170. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/buses.py +0 -0
  171. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/commands.py +0 -0
  172. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/context.py +0 -0
  173. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/cron.py +0 -0
  174. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/decorators.py +0 -0
  175. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/dispatch.py +0 -0
  176. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/envelope.py +0 -0
  177. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/exceptions.py +0 -0
  178. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/handlers.py +0 -0
  179. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/middleware.py +0 -0
  180. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/queries.py +0 -0
  181. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/resolution.py +0 -0
  182. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/serializer.py +0 -0
  183. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/cqrs/task_queues.py +0 -0
  184. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/events.py +0 -0
  185. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/__init__.py +0 -0
  186. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/aggregate.py +0 -0
  187. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/exceptions.py +0 -0
  188. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/projections.py +0 -0
  189. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/snapshots.py +0 -0
  190. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/store.py +0 -0
  191. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/eventsourcing/stored.py +0 -0
  192. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/exceptions.py +0 -0
  193. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/repositories.py +0 -0
  194. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/services.py +0 -0
  195. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/domain/uow.py +0 -0
  196. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/eventsourcing.py +0 -0
  197. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/eventsourcing.pyi +0 -0
  198. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/fastapi.py +0 -0
  199. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/fastapi.pyi +0 -0
  200. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/__init__.py +0 -0
  201. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/__init__.py +0 -0
  202. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/cqrs.py +0 -0
  203. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/eventsourcing.py +0 -0
  204. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/health.py +0 -0
  205. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/lifespan.py +0 -0
  206. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/middlewares.py +0 -0
  207. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/rate_limit.py +0 -0
  208. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/routing.py +0 -0
  209. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/streaming.py +0 -0
  210. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/api/utils.py +0 -0
  211. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/__init__.py +0 -0
  212. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
  213. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/memory.py +0 -0
  214. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cache/cache_backends/redis.py +0 -0
  215. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cli.py +0 -0
  216. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/__init__.py +0 -0
  217. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/cron_sql.py +0 -0
  218. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/middlewares.py +0 -0
  219. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/postgres_bus.py +0 -0
  220. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/postgres_lock.py +0 -0
  221. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/procrastinate.py +0 -0
  222. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/pydantic_serializer.py +0 -0
  223. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/rabbitmq.py +0 -0
  224. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/redis_bus.py +0 -0
  225. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/cqrs/redis_lock.py +0 -0
  226. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/__init__.py +0 -0
  227. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/events_backends/__init__.py +0 -0
  228. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/events/events_backends/memory.py +0 -0
  229. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/__init__.py +0 -0
  230. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/beanie_documents.py +0 -0
  231. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/beanie_store.py +0 -0
  232. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/memory.py +0 -0
  233. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/redis_store.py +0 -0
  234. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_models.py +0 -0
  235. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_models_mixins.py +0 -0
  236. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/eventsourcing/sqlalchemy_store.py +0 -0
  237. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/__init__.py +0 -0
  238. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/_beanie_impl.py +0 -0
  239. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/base.py +0 -0
  240. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/decorators.py +0 -0
  241. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/implementations.py +0 -0
  242. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
  243. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
  244. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/beanie/utils.py +0 -0
  245. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +0 -0
  246. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +0 -0
  247. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/repositories/utils.py +0 -0
  248. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/__init__.py +0 -0
  249. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/celery_adapter.py +0 -0
  250. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/task_queues/procrastinate_adapter.py +0 -0
  251. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/decorators.py +0 -0
  252. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/helpers.py +0 -0
  253. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/uow/scopes.py +0 -0
  254. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/__init__.py +0 -0
  255. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/consumer.py +0 -0
  256. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/rabbitmq_worker.py +0 -0
  257. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/infrastructure/workers/runner.py +0 -0
  258. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/py.typed +0 -0
  259. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/sql.py +0 -0
  260. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/sql.pyi +0 -0
  261. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/__init__.py +0 -0
  262. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/fakes.py +0 -0
  263. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/fixtures.py +0 -0
  264. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/helpers.py +0 -0
  265. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/testing/repositories.py +0 -0
  266. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore/types.py +0 -0
  267. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/dependency_links.txt +0 -0
  268. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/entry_points.txt +0 -0
  269. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/requires.txt +0 -0
  270. {hexcore-9.0.0 → hexcore-9.2.1}/hexcore.egg-info/top_level.txt +0 -0
  271. {hexcore-9.0.0 → hexcore-9.2.1}/setup.cfg +0 -0
  272. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_alembic_metadata_completeness.py +0 -0
  273. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_app_and_lifespan.py +0 -0
  274. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_cqrs_providers.py +0 -0
  275. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_health.py +0 -0
  276. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_middlewares_and_routing.py +0 -0
  277. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_rate_limit.py +0 -0
  278. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_api_streaming.py +0 -0
  279. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_basic.py +0 -0
  280. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_beanie_query_utils.py +0 -0
  281. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_celery_event_loop.py +0 -0
  282. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_config_cors_security.py +0 -0
  283. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_config_loading.py +0 -0
  284. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_consumer_optional_event_bus.py +0 -0
  285. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs.py +0 -0
  286. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_envelope.py +0 -0
  287. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_factory.py +0 -0
  288. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cqrs_middlewares.py +0 -0
  289. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_cron_sql.py +0 -0
  290. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_beanie.py +0 -0
  291. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_container.py +0 -0
  292. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_context.py +0 -0
  293. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_custom_plugin.py +0 -0
  294. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_envelope.py +0 -0
  295. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_facade_and_events.py +0 -0
  296. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_flows.py +0 -0
  297. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_hashing.py +0 -0
  298. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_http.py +0 -0
  299. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_impersonate.py +0 -0
  300. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_magic_link.py +0 -0
  301. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_oauth.py +0 -0
  302. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_organization.py +0 -0
  303. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_passkey.py +0 -0
  304. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_permissions.py +0 -0
  305. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_plugins.py +0 -0
  306. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_repositories.py +0 -0
  307. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_revocation.py +0 -0
  308. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_schema_registration.py +0 -0
  309. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_storage_selection.py +0 -0
  310. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_tokens.py +0 -0
  311. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_darwin_value_objects.py +0 -0
  312. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_domain_base_contract.py +0 -0
  313. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_domain_service_query.py +0 -0
  314. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_dotted_resolution.py +0 -0
  315. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_event_dispatch_mro.py +0 -0
  316. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_aggregate.py +0 -0
  317. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_beanie.py +0 -0
  318. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_config.py +0 -0
  319. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_ports.py +0 -0
  320. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_projections.py +0 -0
  321. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_redis.py +0 -0
  322. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_relay.py +0 -0
  323. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_repository.py +0 -0
  324. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_snapshot_checkpoint.py +0 -0
  325. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_sql_schema.py +0 -0
  326. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_eventsourcing_store_contract.py +0 -0
  327. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_facades.py +0 -0
  328. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_handler_registry.py +0 -0
  329. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_infrastructure_query_path.py +0 -0
  330. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_lock_error_policy.py +0 -0
  331. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_optional_dependencies.py +0 -0
  332. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_postgres_bus.py +0 -0
  333. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_postgres_lock.py +0 -0
  334. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_query_cursor_and_endpoint.py +0 -0
  335. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_query_field_validation.py +0 -0
  336. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_rabbitmq_bus.py +0 -0
  337. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_redis_bus.py +0 -0
  338. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_redis_lock.py +0 -0
  339. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_repositories_utils.py +0 -0
  340. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_scheduler.py +0 -0
  341. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_scheduler_catchup.py +0 -0
  342. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_smart_routing.py +0 -0
  343. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_sql_naming_convention.py +0 -0
  344. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_sql_session_layer.py +0 -0
  345. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_task_queues_adapters.py +0 -0
  346. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_testing_kit.py +0 -0
  347. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_testing_utilities.py +0 -0
  348. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_uow_session_regression.py +0 -0
  349. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_use_cases_query.py +0 -0
  350. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_worker_bus_integration.py +0 -0
  351. {hexcore-9.0.0 → hexcore-9.2.1}/tests/test_worker_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 9.0.0
3
+ Version: 9.2.1
4
4
  Summary: Reusable core for Python projects with hexagonal architecture, DDD, CQRS and background tasks. Ships the abstractions and the infrastructure: SQL session layer, FastAPI factories, worker runner, dynamic cron and identity.
5
5
  Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
6
  License-Expression: MIT
@@ -335,6 +335,6 @@ There is a set of skills for extending HexCore in VS Code and compatible environ
335
335
 
336
336
  - [docs/](./docs/) — the complete documentation, in English and Spanish.
337
337
  - [docs/ARCHITECTURE_TYPING.md](./docs/ARCHITECTURE_TYPING.md) — type system and stubs.
338
- - [CHANGELOG.md](./CHANGELOG.md) — change history.
339
- - [CONTRIBUTING.md](./CONTRIBUTING.md) — collaboration guidelines.
340
- - [SECURITY.md](./SECURITY.md) — security policy.
338
+ - [CHANGELOG.md](../../CHANGELOG.md) — change history.
339
+ - [CONTRIBUTING.md](../../CONTRIBUTING.md) — collaboration guidelines.
340
+ - [SECURITY.md](../../SECURITY.md) — security policy.
@@ -260,6 +260,6 @@ There is a set of skills for extending HexCore in VS Code and compatible environ
260
260
 
261
261
  - [docs/](./docs/) — the complete documentation, in English and Spanish.
262
262
  - [docs/ARCHITECTURE_TYPING.md](./docs/ARCHITECTURE_TYPING.md) — type system and stubs.
263
- - [CHANGELOG.md](./CHANGELOG.md) — change history.
264
- - [CONTRIBUTING.md](./CONTRIBUTING.md) — collaboration guidelines.
265
- - [SECURITY.md](./SECURITY.md) — security policy.
263
+ - [CHANGELOG.md](../../CHANGELOG.md) — change history.
264
+ - [CONTRIBUTING.md](../../CONTRIBUTING.md) — collaboration guidelines.
265
+ - [SECURITY.md](../../SECURITY.md) — security policy.
@@ -333,6 +333,18 @@ class PluginRegistry:
333
333
  acumulado.update(plugin.exception_status_map())
334
334
  return acumulado
335
335
 
336
+ def exception_payload(self, exc: Exception) -> dict[str, t.Any]:
337
+ """
338
+ El payload extra combinado que los plugins agregan al body de `exc`.
339
+
340
+ Espejo de `exception_status_map()`: el del último plugin gana, y `create_app`
341
+ mergea esto debajo del identity-level y del consumidor.
342
+ """
343
+ acumulado: dict[str, t.Any] = {}
344
+ for plugin in self.plugins:
345
+ acumulado.update(plugin.exception_payload(exc))
346
+ return acumulado
347
+
336
348
  def register_handlers(self, registry: t.Any) -> t.Any:
337
349
  for plugin in self.plugins:
338
350
  plugin.register_handlers(registry)
@@ -237,6 +237,22 @@ class DarwinPlugin(abc.ABC):
237
237
  """
238
238
  return {}
239
239
 
240
+ def exception_payload(self, exc: Exception) -> t.Mapping[str, t.Any]:
241
+ """
242
+ Claves extra para el **cuerpo** de la excepción, si el plugin las tiene.
243
+
244
+ Espejo de `exception_status_map()` pero para el body en vez del status: hay
245
+ excepciones que llevan datos que el cliente necesita para completar el flujo — el
246
+ `challenge` de `TwoFactorRequiredError`, que el segundo paso del login exige de
247
+ vuelta en `POST /auth/2fa/challenge` — y que no caben en `detail` sin obligar al
248
+ cliente a parsear texto.
249
+
250
+ Método **concreto** que devuelve vacío, igual que el resto de los puntos de
251
+ extensión de `DarwinPlugin`: agregar éste no rompe a ningún plugin existente, que
252
+ no tiene por qué aportar nada al body de sus errores.
253
+ """
254
+ return {}
255
+
240
256
  def register_handlers(self, registry: t.Any) -> None:
241
257
  """
242
258
  Registra los comandos y queries del plugin en el `HandlerRegistry`.
@@ -33,6 +33,7 @@ __all__ = [
33
33
  "require_roles",
34
34
  "require_not_impersonated",
35
35
  "identity_exception_headers",
36
+ "identity_exception_payload",
36
37
  ]
37
38
 
38
39
  #: El valor de `WWW-Authenticate` de un 401. RFC 6750 §3.
@@ -174,3 +175,27 @@ def identity_exception_headers(exc: Exception) -> t.Mapping[str, str]:
174
175
  if isinstance(exc, AuthenticationError):
175
176
  return {"WWW-Authenticate": WWW_AUTHENTICATE}
176
177
  return {}
178
+
179
+
180
+ def identity_exception_payload(exc: Exception) -> t.Mapping[str, t.Any]:
181
+ """
182
+ El payload extra que las excepciones de identidad agregan al cuerpo del error.
183
+
184
+ Se pasa a `create_app(exception_payload=...)`, y `create_app` lo hace sólo cuando
185
+ `AppFeatures.auth_context` está prendido — mismo cableado que `identity_exception_headers`.
186
+
187
+ A diferencia de los headers —genéricos para toda `AuthenticationError`—, qué payload
188
+ corresponde es específico de cada plugin (el `challenge` de `TwoFactorRequiredError`, por
189
+ ejemplo), así que esta función delega en `PluginRegistry.exception_payload()` del
190
+ despliegue en curso en vez de chequear un tipo fijo.
191
+
192
+ Sin un despliegue configurado (`create_app` corrido antes de `configure_identity`, o sin
193
+ Darwin) devuelve vacío en vez de lanzar — mismo trato que `_mapa_de_plugins()` en `app.py`.
194
+ """
195
+ from hexcore.darwin.application.container import get_identity_container
196
+
197
+ try:
198
+ contenedor = get_identity_container()
199
+ except Exception:
200
+ return {}
201
+ return contenedor.plugins.exception_payload(exc)
@@ -43,6 +43,9 @@ __all__ = [
43
43
  "SignInRequest",
44
44
  "SessionResponse",
45
45
  "MeResponse",
46
+ "SignedOutResponse",
47
+ "RevokedResponse",
48
+ "SessionInfo",
46
49
  "build_identity_router",
47
50
  "emit_tokens",
48
51
  "session_response_body",
@@ -100,6 +103,31 @@ class MeResponse(BaseModel):
100
103
  scopes: list[str] = Field(default_factory=list)
101
104
 
102
105
 
106
+ class SignedOutResponse(BaseModel):
107
+ """La respuesta de `POST /auth/sign-out`."""
108
+
109
+ signed_out: bool
110
+
111
+
112
+ class RevokedResponse(BaseModel):
113
+ """La respuesta de `POST /auth/sign-out-everywhere`: cuántas sesiones se revocaron."""
114
+
115
+ revoked: int
116
+
117
+
118
+ class SessionInfo(BaseModel):
119
+ """Una fila de `GET /auth/sessions`, para una pantalla de seguridad."""
120
+
121
+ session_id: str
122
+ transport: str
123
+ created_at: str
124
+ expires_at: str
125
+ ip_address: str | None
126
+ user_agent: str | None
127
+ impersonated: bool
128
+ current: bool
129
+
130
+
103
131
  # ── Helpers de transporte ─────────────────────────────────────────────────────
104
132
  def resolve_transport(request: Request) -> "AbstractTransport":
105
133
  """Dependencia: el transporte de este request, resuelto una sola vez."""
@@ -241,7 +269,7 @@ def build_identity_router(
241
269
  usuario = await servicio.verify_email(email=payload.email, code=payload.code)
242
270
  return {"email_verified": usuario.email_verified}
243
271
 
244
- @router.post("/sign-in", dependencies=limite)
272
+ @router.post("/sign-in", dependencies=limite, response_model=SessionResponse)
245
273
  async def sign_in(
246
274
  payload: SignInRequest,
247
275
  request: Request,
@@ -270,7 +298,7 @@ def build_identity_router(
270
298
  emit_tokens(respuesta, tokens, transport, contenedor.config)
271
299
  return respuesta
272
300
 
273
- @router.post("/refresh")
301
+ @router.post("/refresh", response_model=SessionResponse)
274
302
  async def refresh(
275
303
  request: Request,
276
304
  transport: "AbstractTransport" = Depends(resolve_transport),
@@ -304,7 +332,7 @@ def build_identity_router(
304
332
  emit_tokens(respuesta, tokens, transport, contenedor.config)
305
333
  return respuesta
306
334
 
307
- @router.post("/sign-out")
335
+ @router.post("/sign-out", response_model=SignedOutResponse)
308
336
  async def sign_out(
309
337
  auth: "AuthContext[t.Any]" = Depends(provide_auth),
310
338
  transport: "AbstractTransport" = Depends(resolve_transport),
@@ -335,6 +363,7 @@ def build_identity_router(
335
363
  @router.post(
336
364
  "/sign-out-everywhere",
337
365
  dependencies=[Depends(require_not_impersonated("sign_out_everywhere"))],
366
+ response_model=RevokedResponse,
338
367
  )
339
368
  async def sign_out_everywhere(
340
369
  auth: "AuthContext[t.Any]" = Depends(provide_auth),
@@ -386,10 +415,10 @@ def build_identity_router(
386
415
  scopes=sorted(auth.actor.scopes),
387
416
  )
388
417
 
389
- @router.get("/sessions")
418
+ @router.get("/sessions", response_model=list[SessionInfo])
390
419
  async def sessions(
391
420
  auth: "AuthContext[t.Any]" = Depends(provide_auth),
392
- ) -> list[dict[str, t.Any]]:
421
+ ) -> list[SessionInfo]:
393
422
  """
394
423
  Las sesiones vivas del sujeto, para una pantalla de seguridad.
395
424
 
@@ -402,16 +431,16 @@ def build_identity_router(
402
431
  repo = get_identity_container().sessions_repository()
403
432
  vivas = await repo.list_active_for_user(t.cast(t.Any, auth.subject_id))
404
433
  return [
405
- {
406
- "session_id": str(s.id),
407
- "transport": s.transport,
408
- "created_at": s.created_at.isoformat(),
409
- "expires_at": s.expires_at.isoformat(),
410
- "ip_address": s.ip_address,
411
- "user_agent": s.user_agent,
412
- "impersonated": s.is_impersonated,
413
- "current": s.id == getattr(auth.actor, "session_id", None),
414
- }
434
+ SessionInfo(
435
+ session_id=str(s.id),
436
+ transport=s.transport,
437
+ created_at=s.created_at.isoformat(),
438
+ expires_at=s.expires_at.isoformat(),
439
+ ip_address=s.ip_address,
440
+ user_agent=s.user_agent,
441
+ impersonated=s.is_impersonated,
442
+ current=s.id == getattr(auth.actor, "session_id", None),
443
+ )
415
444
  for s in vivas
416
445
  ]
417
446
 
@@ -22,7 +22,15 @@ from fastapi import APIRouter, Depends, Request, Response
22
22
  from fastapi.responses import JSONResponse
23
23
  from pydantic import BaseModel, Field
24
24
 
25
- __all__ = ["StartImpersonationBody", "ImpersonationStatus", "build_impersonate_router"]
25
+ from hexcore.darwin.infrastructure.api.routers import SessionResponse
26
+
27
+ __all__ = [
28
+ "StartImpersonationBody",
29
+ "ImpersonationStatus",
30
+ "ImpersonationStoppedResponse",
31
+ "ImpersonationStartedResponse",
32
+ "build_impersonate_router",
33
+ ]
26
34
 
27
35
 
28
36
  class StartImpersonationBody(BaseModel):
@@ -44,6 +52,30 @@ class ImpersonationStatus(BaseModel):
44
52
  expires_at: str | None = None
45
53
 
46
54
 
55
+ class ImpersonationStoppedResponse(BaseModel):
56
+ """La respuesta de `POST /auth/impersonate/stop`."""
57
+
58
+ stopped: bool
59
+
60
+
61
+ class ImpersonationStartedResponse(SessionResponse):
62
+ """
63
+ La respuesta de `POST /auth/impersonate/{user_id}`.
64
+
65
+ `impersonating` queda **deprecado**: en esta ruta lleva el id del sujeto impersonado como
66
+ string, mientras que en `MeResponse.impersonating` es un `bool` — el mismo nombre significa
67
+ dos cosas distintas según la ruta, y un cliente TypeScript no puede tipar las dos con el
68
+ mismo campo sin mentir. Renombrarlo sería breaking y no lo amerita un major; en su lugar,
69
+ `subject_id` es el mismo valor con un nombre que no colisiona.
70
+ """
71
+
72
+ impersonating: str
73
+ subject_id: str
74
+ #: Cuándo vence la ventana de impersonación (no la del access token, que es
75
+ #: `expires_in`, heredado de `SessionResponse`).
76
+ expires_at: str | None = None
77
+
78
+
47
79
  def build_impersonate_router(
48
80
  *,
49
81
  prefix: str = "/auth/impersonate",
@@ -95,7 +127,7 @@ def build_impersonate_router(
95
127
  # registro, así que con `/{user_id}` primero, un `POST /auth/impersonate/stop` matchea la
96
128
  # ruta paramétrica e intenta parsear `"stop"` como UUID — 422 en vez de terminar la
97
129
  # impersonación. Lo encontró un test, no la revisión.
98
- @router.post("/stop")
130
+ @router.post("/stop", response_model=ImpersonationStoppedResponse)
99
131
  async def stop(
100
132
  auth: t.Any = Depends(provide_auth),
101
133
  transport: t.Any = Depends(_resolve_transport),
@@ -114,7 +146,9 @@ def build_impersonate_router(
114
146
  transport.clear(respuesta)
115
147
  return respuesta
116
148
 
117
- @router.post("/{user_id}", dependencies=limite)
149
+ @router.post(
150
+ "/{user_id}", dependencies=limite, response_model=ImpersonationStartedResponse
151
+ )
118
152
  async def start(
119
153
  user_id: UUID,
120
154
  payload: StartImpersonationBody,
@@ -149,6 +183,10 @@ def build_impersonate_router(
149
183
  exclude_none=True
150
184
  )
151
185
  cuerpo["impersonating"] = str(resultado.subject.id)
186
+ # `subject_id` es el mismo valor que `impersonating`, con un nombre que no colisiona
187
+ # con el `bool` de `MeResponse.impersonating`. Ver el docstring de
188
+ # `ImpersonationStartedResponse`.
189
+ cuerpo["subject_id"] = str(resultado.subject.id)
152
190
  cuerpo["expires_at"] = (
153
191
  resultado.session.impersonation_expires_at.isoformat()
154
192
  if resultado.session.impersonation_expires_at
@@ -18,7 +18,14 @@ from fastapi import APIRouter, Depends, Request, Response
18
18
  from fastapi.responses import JSONResponse
19
19
  from pydantic import BaseModel
20
20
 
21
- __all__ = ["RequestMagicLinkBody", "ConsumeMagicLinkBody", "build_magic_link_router"]
21
+ from hexcore.darwin.infrastructure.api.routers import SessionResponse
22
+
23
+ __all__ = [
24
+ "RequestMagicLinkBody",
25
+ "ConsumeMagicLinkBody",
26
+ "MagicLinkRequested",
27
+ "build_magic_link_router",
28
+ ]
22
29
 
23
30
 
24
31
  class RequestMagicLinkBody(BaseModel):
@@ -30,6 +37,18 @@ class ConsumeMagicLinkBody(BaseModel):
30
37
  token: str
31
38
 
32
39
 
40
+ class MagicLinkRequested(BaseModel):
41
+ """
42
+ La respuesta de `POST /auth/magic-link/request`.
43
+
44
+ `token` sólo viaja en un despliegue que no manda mails de verdad — ver la advertencia del
45
+ docstring de la ruta: en producción no se devuelve, y el campo queda `None`.
46
+ """
47
+
48
+ sent: bool
49
+ token: str | None = None
50
+
51
+
33
52
  def build_magic_link_router(
34
53
  *,
35
54
  prefix: str = "/auth/magic-link",
@@ -58,8 +77,18 @@ def build_magic_link_router(
58
77
  vida = ttl or DEFAULT_TTL
59
78
  limite = _rate_limit(rate_limit)
60
79
 
61
- @router.post("/request", dependencies=limite)
62
- async def request(payload: RequestMagicLinkBody) -> dict[str, t.Any]:
80
+ @router.post(
81
+ "/request",
82
+ dependencies=limite,
83
+ response_model=MagicLinkRequested,
84
+ # `exclude_none`: antes de este `response_model` la ruta devolvía un `dict` que
85
+ # directamente omitía la clave `token` cuando era `None`. Sin esto, el modelo la
86
+ # incluiría como `null` en los dos casos — no filtra nada (la ruta sigue
87
+ # respondiendo igual exista o no la cuenta), pero cambiaría el shape que un
88
+ # cliente ya integrado contra el dict viejo espera.
89
+ response_model_exclude_none=True,
90
+ )
91
+ async def request(payload: RequestMagicLinkBody) -> MagicLinkRequested:
63
92
  """
64
93
  Pide un magic link. **Responde igual exista o no la cuenta.**
65
94
 
@@ -74,12 +103,9 @@ def build_magic_link_router(
74
103
 
75
104
  # La forma de la respuesta es idéntica en los dos casos. Que el token venga o no es lo
76
105
  # único que cambia, y en producción no se devuelve.
77
- cuerpo: dict[str, t.Any] = {"sent": True}
78
- if emitido.token is not None:
79
- cuerpo["token"] = emitido.token
80
- return cuerpo
106
+ return MagicLinkRequested(sent=True, token=emitido.token)
81
107
 
82
- @router.post("/consume")
108
+ @router.post("/consume", response_model=SessionResponse)
83
109
  async def consume(
84
110
  payload: ConsumeMagicLinkBody,
85
111
  request: Request,
@@ -25,9 +25,12 @@ from fastapi import APIRouter, Depends, Query, Request, Response
25
25
  from fastapi.responses import JSONResponse
26
26
  from pydantic import BaseModel
27
27
 
28
+ from hexcore.darwin.infrastructure.api.routers import SessionResponse
29
+
28
30
  __all__ = [
29
31
  "AuthorizationResponse",
30
32
  "LinkedProviders",
33
+ "OAuthCallbackResponse",
31
34
  "build_oauth_router",
32
35
  ]
33
36
 
@@ -50,6 +53,17 @@ class LinkedProviders(BaseModel):
50
53
  providers: list[str]
51
54
 
52
55
 
56
+ class OAuthCallbackResponse(SessionResponse):
57
+ """
58
+ La respuesta de `GET /auth/oauth/{provider}/callback`.
59
+
60
+ `created` distingue un signup implícito de un login: es lo que el cliente necesita para
61
+ mandar a un usuario nuevo a onboarding en vez de al home.
62
+ """
63
+
64
+ created: bool
65
+
66
+
53
67
  def build_oauth_router(
54
68
  *,
55
69
  prefix: str = "/auth/oauth",
@@ -84,7 +98,12 @@ def build_oauth_router(
84
98
 
85
99
  return LinkedProviders(providers=list(get_oauth_service().provider_ids))
86
100
 
87
- @router.get("/{provider}/start", dependencies=limite)
101
+ @router.get(
102
+ "/{provider}/start",
103
+ dependencies=limite,
104
+ response_model=AuthorizationResponse,
105
+ responses={302: {"description": "Con `?redirect=1`: redirect directo al proveedor."}},
106
+ )
88
107
  async def start(
89
108
  provider: str,
90
109
  redirect_uri: str = Query(..., description="A dónde vuelve el usuario."),
@@ -110,7 +129,9 @@ def build_oauth_router(
110
129
  ).model_dump()
111
130
  )
112
131
 
113
- @router.get("/{provider}/callback", dependencies=limite)
132
+ @router.get(
133
+ "/{provider}/callback", dependencies=limite, response_model=OAuthCallbackResponse
134
+ )
114
135
  async def callback(
115
136
  provider: str,
116
137
  request: Request,
@@ -30,6 +30,7 @@ __all__ = [
30
30
  "OrganizationOut",
31
31
  "MemberOut",
32
32
  "InvitationOut",
33
+ "InvitationIssued",
33
34
  "build_organization_router",
34
35
  ]
35
36
 
@@ -87,11 +88,19 @@ class InvitationOut(BaseModel):
87
88
  """
88
89
 
89
90
  id: str
91
+
90
92
  email: str
91
93
  role: OrgRole
92
94
  expires_at: str
93
95
 
94
96
 
97
+ class InvitationIssued(BaseModel):
98
+ """La respuesta de `POST /organizations/{organization_id}/invitations`."""
99
+
100
+ invitation: InvitationOut
101
+ token: str
102
+
103
+
95
104
  def build_organization_router(
96
105
  *,
97
106
  prefix: str = "/organizations",
@@ -240,12 +249,16 @@ def build_organization_router(
240
249
  return {"removed": True}
241
250
 
242
251
  # ── Las invitaciones ──────────────────────────────────────────────────────
243
- @router.post("/{organization_id}/invitations", status_code=201)
252
+ @router.post(
253
+ "/{organization_id}/invitations",
254
+ status_code=201,
255
+ response_model=InvitationIssued,
256
+ )
244
257
  async def invitar(
245
258
  organization_id: UUID,
246
259
  payload: InviteBody,
247
260
  auth: t.Any = Depends(provide_auth),
248
- ) -> dict[str, t.Any]:
261
+ ) -> InvitationIssued:
249
262
  """
250
263
  Invita a alguien. Requiere ser `admin` o más, y no se puede invitar por encima del rol
251
264
  propio.
@@ -262,10 +275,9 @@ def build_organization_router(
262
275
  email=payload.email,
263
276
  role=payload.role,
264
277
  )
265
- return {
266
- "invitation": _invitation_out(emitida.invitation).model_dump(),
267
- "token": emitida.token,
268
- }
278
+ return InvitationIssued(
279
+ invitation=_invitation_out(emitida.invitation), token=emitida.token
280
+ )
269
281
 
270
282
  @router.get("/{organization_id}/invitations")
271
283
  async def pendientes(
@@ -23,6 +23,8 @@ from fastapi import APIRouter, Depends, Request, Response
23
23
  from fastapi.responses import JSONResponse
24
24
  from pydantic import BaseModel, Field
25
25
 
26
+ from hexcore.darwin.infrastructure.api.routers import SessionResponse
27
+
26
28
  __all__ = [
27
29
  "FinishRegistrationBody",
28
30
  "AuthenticationOptionsBody",
@@ -99,7 +101,20 @@ def build_passkey_router(
99
101
  limite = _rate_limit(rate_limit)
100
102
 
101
103
  # ── Registro (con sesión) ─────────────────────────────────────────────────
102
- @router.post("/register/options")
104
+ # Sin `response_model`: la forma es la de `PublicKeyCredentialCreationOptions` del spec
105
+ # WebAuthn (https://www.w3.org/TR/webauthn-3/#dictionary-makecredentialoptions).
106
+ # Remodelarla en pydantic sería mantener una copia de un estándar ajeno para ganar un
107
+ # schema que un cliente ya escribe a mano contra el spec — `openapi_extra` documenta la
108
+ # referencia sin pagar esa copia.
109
+ @router.post(
110
+ "/register/options",
111
+ openapi_extra={
112
+ "externalDocs": {
113
+ "description": "PublicKeyCredentialCreationOptions (WebAuthn L3)",
114
+ "url": "https://www.w3.org/TR/webauthn-3/#dictionary-makecredentialoptions",
115
+ }
116
+ },
117
+ )
103
118
  async def register_options(
104
119
  auth: t.Any = Depends(provide_auth),
105
120
  ) -> dict[str, t.Any]:
@@ -140,7 +155,18 @@ def build_passkey_router(
140
155
  return _resumen(guardada)
141
156
 
142
157
  # ── Login (público) ───────────────────────────────────────────────────────
143
- @router.post("/authenticate/options", dependencies=limite)
158
+ # Mismo motivo que `/register/options`: la forma es la de
159
+ # `PublicKeyCredentialRequestOptions` del spec WebAuthn.
160
+ @router.post(
161
+ "/authenticate/options",
162
+ dependencies=limite,
163
+ openapi_extra={
164
+ "externalDocs": {
165
+ "description": "PublicKeyCredentialRequestOptions (WebAuthn L3)",
166
+ "url": "https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options",
167
+ }
168
+ },
169
+ )
144
170
  async def authenticate_options(
145
171
  payload: AuthenticationOptionsBody,
146
172
  ) -> dict[str, t.Any]:
@@ -166,7 +192,9 @@ def build_passkey_router(
166
192
  opciones = await get_passkey_service().start_authentication(user_id=user_id)
167
193
  return opciones.options
168
194
 
169
- @router.post("/authenticate", dependencies=limite)
195
+ @router.post(
196
+ "/authenticate", dependencies=limite, response_model=SessionResponse
197
+ )
170
198
  async def authenticate(
171
199
  payload: FinishAuthenticationBody,
172
200
  request: Request,
@@ -179,6 +179,18 @@ class TwoFactorPlugin(DarwinPlugin):
179
179
  def exception_status_map(self) -> t.Mapping[type[Exception], int]:
180
180
  return TWO_FACTOR_EXCEPTION_STATUS_MAP
181
181
 
182
+ def exception_payload(self, exc: Exception) -> t.Mapping[str, t.Any]:
183
+ """
184
+ El `challenge` de `TwoFactorRequiredError`, en el cuerpo del 401.
185
+
186
+ Sin esto el segundo paso del login no es completable desde HTTP: `POST
187
+ /auth/2fa/challenge` exige el `challenge` de vuelta, y hasta la Fase 0 el cliente
188
+ nunca lo recibía — sólo vivía en el atributo Python de la excepción.
189
+ """
190
+ if isinstance(exc, TwoFactorRequiredError) and exc.challenge is not None:
191
+ return {"challenge": exc.challenge}
192
+ return {}
193
+
182
194
  def hooks(self) -> t.Sequence[HookBinding]:
183
195
  from hexcore.darwin.application.services import SIGN_IN_AUTHENTICATED
184
196
 
@@ -22,6 +22,8 @@ from fastapi import APIRouter, Depends, Request, Response
22
22
  from fastapi.responses import JSONResponse
23
23
  from pydantic import BaseModel
24
24
 
25
+ from hexcore.darwin.infrastructure.api.routers import SessionResponse
26
+
25
27
  __all__ = [
26
28
  "ConfirmBody",
27
29
  "DisableBody",
@@ -156,7 +158,7 @@ def build_two_factor_router(
156
158
  )
157
159
  return {"disabled": True}
158
160
 
159
- @router.post("/challenge", dependencies=limite)
161
+ @router.post("/challenge", dependencies=limite, response_model=SessionResponse)
160
162
  async def challenge(
161
163
  payload: ChallengeBody,
162
164
  request: Request,