hexcore 1.6.2__tar.gz → 1.6.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 (104) hide show
  1. {hexcore-1.6.2 → hexcore-1.6.3}/PKG-INFO +1 -1
  2. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +2 -7
  3. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/__init__.py +8 -4
  4. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/PKG-INFO +1 -1
  5. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/SOURCES.txt +2 -1
  6. {hexcore-1.6.2 → hexcore-1.6.3}/pyproject.toml +1 -1
  7. hexcore-1.6.3/tests/test_uow_session_regression.py +110 -0
  8. {hexcore-1.6.2 → hexcore-1.6.3}/LICENSE +0 -0
  9. {hexcore-1.6.2 → hexcore-1.6.3}/README.md +0 -0
  10. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/__init__.py +0 -0
  11. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/__init__.pyi +0 -0
  12. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/__main__.py +0 -0
  13. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/__init__.py +0 -0
  14. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/__init__.pyi +0 -0
  15. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/dtos/__init__.py +0 -0
  16. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/dtos/__init__.pyi +0 -0
  17. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/dtos/base.py +0 -0
  18. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/dtos/base.pyi +0 -0
  19. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/use_cases/__init__.py +0 -0
  20. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/use_cases/__init__.pyi +0 -0
  21. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/use_cases/base.py +0 -0
  22. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/application/use_cases/base.pyi +0 -0
  23. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/config.py +0 -0
  24. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/config.pyi +0 -0
  25. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/__init__.py +0 -0
  26. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/__init__.pyi +0 -0
  27. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/__init__.py +0 -0
  28. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/__init__.pyi +0 -0
  29. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/permissions.py +0 -0
  30. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/permissions.pyi +0 -0
  31. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/value_objects.py +0 -0
  32. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/auth/value_objects.pyi +0 -0
  33. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/base.py +0 -0
  34. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/base.pyi +0 -0
  35. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/events.py +0 -0
  36. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/events.pyi +0 -0
  37. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/exceptions.py +0 -0
  38. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/exceptions.pyi +0 -0
  39. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/repositories.py +0 -0
  40. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/repositories.pyi +0 -0
  41. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/services.py +0 -0
  42. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/services.pyi +0 -0
  43. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/uow.py +0 -0
  44. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/domain/uow.pyi +0 -0
  45. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/__init__.py +0 -0
  46. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/__init__.pyi +0 -0
  47. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/api/__init__.py +0 -0
  48. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/api/__init__.pyi +0 -0
  49. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/api/utils.py +0 -0
  50. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/api/utils.pyi +0 -0
  51. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/__init__.py +0 -0
  52. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/__init__.pyi +0 -0
  53. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
  54. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/__init__.pyi +0 -0
  55. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/memory.py +0 -0
  56. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/memory.pyi +0 -0
  57. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/redis.py +0 -0
  58. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cache/cache_backends/redis.pyi +0 -0
  59. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cli.py +0 -0
  60. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/cli.pyi +0 -0
  61. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/__init__.py +0 -0
  62. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/__init__.pyi +0 -0
  63. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/events_backends/__init__.py +0 -0
  64. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/events_backends/__init__.pyi +0 -0
  65. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/events_backends/memory.py +0 -0
  66. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/events/events_backends/memory.pyi +0 -0
  67. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/__init__.py +0 -0
  68. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/__init__.pyi +0 -0
  69. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/base.py +0 -0
  70. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/base.pyi +0 -0
  71. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/decorators.py +0 -0
  72. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/decorators.pyi +0 -0
  73. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/implementations.py +0 -0
  74. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/implementations.pyi +0 -0
  75. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
  76. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/__init__.pyi +0 -0
  77. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
  78. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/beanie/__init__.pyi +0 -0
  79. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/beanie/utils.py +0 -0
  80. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/beanie/utils.pyi +0 -0
  81. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +0 -0
  82. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.pyi +0 -0
  83. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.pyi +0 -0
  84. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.py +0 -0
  85. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.pyi +0 -0
  86. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/utils.py +0 -0
  87. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/repositories/utils.pyi +0 -0
  88. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/__init__.pyi +0 -0
  89. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/decorators.py +0 -0
  90. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/decorators.pyi +0 -0
  91. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/helpers.py +0 -0
  92. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/infrastructure/uow/helpers.pyi +0 -0
  93. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/types.py +0 -0
  94. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore/types.pyi +0 -0
  95. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/dependency_links.txt +0 -0
  96. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/entry_points.txt +0 -0
  97. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/requires.txt +0 -0
  98. {hexcore-1.6.2 → hexcore-1.6.3}/hexcore.egg-info/top_level.txt +0 -0
  99. {hexcore-1.6.2 → hexcore-1.6.3}/scripts/__init__.py +0 -0
  100. {hexcore-1.6.2 → hexcore-1.6.3}/scripts/fix_pyi_files.py +0 -0
  101. {hexcore-1.6.2 → hexcore-1.6.3}/scripts/fix_types_pyi_aliases.py +0 -0
  102. {hexcore-1.6.2 → hexcore-1.6.3}/scripts/main.py +0 -0
  103. {hexcore-1.6.2 → hexcore-1.6.3}/setup.cfg +0 -0
  104. {hexcore-1.6.2 → hexcore-1.6.3}/tests/test_basic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 1.6.2
3
+ Version: 1.6.3
4
4
  Summary: Núcleo reutilizable para proyectos Python con arquitectura hexagonal y event handling. Provee abstracciones, utilidades y contratos para DDD, eventos y desacoplamiento de infraestructura.
5
5
  Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
6
  License-Expression: MIT
@@ -23,11 +23,6 @@ AsyncSessionLocal: async_sessionmaker[AsyncSession] = async_sessionmaker(
23
23
 
24
24
 
25
25
  async def get_async_db_session():
26
- """Generador de sesiones asíncronas con gestión de transacciones integrada."""
26
+ """Generador de sesiones asíncronas delegando el ciclo de vida al context manager."""
27
27
  async with AsyncSessionLocal() as db:
28
- try:
29
- yield db
30
- except Exception:
31
- if db.in_transaction():
32
- await db.rollback()
33
- raise
28
+ yield db
@@ -39,16 +39,20 @@ class SqlAlchemyUnitOfWork(IUnitOfWork):
39
39
  exc_tb: t.Optional[t.Any],
40
40
  ) -> None:
41
41
  # El ciclo de vida de la sesion se maneja en la dependencia/factory
42
- # que la crea. Aqui solo manejamos rollback en errores.
43
- await super().__aexit__(exc_type, exc_val, exc_tb)
42
+ # que la crea. Evitamos rollback duplicado para no interferir con
43
+ # el unwind del contexto externo de AsyncSession.
44
+ if exc_type:
45
+ self.clear_tracked_entities()
44
46
 
45
47
  async def commit(self):
46
48
  """
47
49
  Confirma la transacción y despacha los eventos.
48
50
  """
49
51
  await self.session.commit()
50
- await self.dispatch_events()
51
- self.clear_tracked_entities()
52
+ try:
53
+ await self.dispatch_events()
54
+ finally:
55
+ self.clear_tracked_entities()
52
56
 
53
57
  async def rollback(self):
54
58
  if self.session.in_transaction():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 1.6.2
3
+ Version: 1.6.3
4
4
  Summary: Núcleo reutilizable para proyectos Python con arquitectura hexagonal y event handling. Provee abstracciones, utilidades y contratos para DDD, eventos y desacoplamiento de infraestructura.
5
5
  Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
6
  License-Expression: MIT
@@ -98,4 +98,5 @@ scripts/__init__.py
98
98
  scripts/fix_pyi_files.py
99
99
  scripts/fix_types_pyi_aliases.py
100
100
  scripts/main.py
101
- tests/test_basic.py
101
+ tests/test_basic.py
102
+ tests/test_uow_session_regression.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hexcore"
3
- version = "1.6.2"
3
+ version = "1.6.3"
4
4
  authors = [
5
5
  { name="David Latosefki (Indroic)", email="indroic@outlook.com" },
6
6
  ]
@@ -0,0 +1,110 @@
1
+ import asyncio
2
+ from collections.abc import AsyncGenerator
3
+ from types import TracebackType
4
+ from typing import Any, cast
5
+ from unittest.mock import AsyncMock, MagicMock, patch
6
+
7
+ from hexcore.infrastructure.repositories.orms.sqlalchemy.session import (
8
+ get_async_db_session,
9
+ )
10
+ from hexcore.infrastructure.uow import SqlAlchemyUnitOfWork
11
+
12
+
13
+ class _DummySessionContext:
14
+ def __init__(self, session: Any) -> None:
15
+ self._session = session
16
+
17
+ async def __aenter__(self) -> Any:
18
+ return self._session
19
+
20
+ async def __aexit__(
21
+ self,
22
+ exc_type: type[BaseException] | None,
23
+ exc_val: BaseException | None,
24
+ exc_tb: TracebackType | None,
25
+ ) -> bool:
26
+ return False
27
+
28
+
29
+ def _build_sql_uow(session: Any) -> SqlAlchemyUnitOfWork:
30
+ with patch("hexcore.infrastructure.uow.discover_sql_repositories", return_value={}):
31
+ return SqlAlchemyUnitOfWork(session=session)
32
+
33
+
34
+ def test_get_async_db_session_no_manual_rollback_on_error():
35
+ async def _run():
36
+ session = MagicMock()
37
+ session.rollback = AsyncMock()
38
+
39
+ with patch(
40
+ "hexcore.infrastructure.repositories.orms.sqlalchemy.session.AsyncSessionLocal",
41
+ return_value=_DummySessionContext(session),
42
+ ):
43
+ session_generator = cast(AsyncGenerator[Any, None], get_async_db_session())
44
+ yielded = await anext(session_generator)
45
+ assert yielded is session
46
+
47
+ try:
48
+ await session_generator.athrow(ValueError("boom"))
49
+ except ValueError:
50
+ pass
51
+
52
+ session.rollback.assert_not_awaited()
53
+
54
+ asyncio.run(_run())
55
+
56
+
57
+ def test_sql_uow_aexit_does_not_rollback_session():
58
+ async def _run():
59
+ session = MagicMock()
60
+ session.rollback = AsyncMock()
61
+
62
+ uow = _build_sql_uow(session)
63
+ uow.clear_tracked_entities = MagicMock()
64
+
65
+ await uow.__aexit__(ValueError, ValueError("boom"), None)
66
+
67
+ session.rollback.assert_not_awaited()
68
+ uow.clear_tracked_entities.assert_called_once()
69
+
70
+ asyncio.run(_run())
71
+
72
+
73
+ def test_sql_uow_rollback_keeps_explicit_rollback_behavior():
74
+ async def _run():
75
+ session = MagicMock()
76
+ session.in_transaction.return_value = True
77
+ session.rollback = AsyncMock()
78
+
79
+ uow = _build_sql_uow(session)
80
+ uow.clear_tracked_entities = MagicMock()
81
+
82
+ await uow.rollback()
83
+
84
+ session.rollback.assert_awaited_once()
85
+ uow.clear_tracked_entities.assert_called_once()
86
+
87
+ asyncio.run(_run())
88
+
89
+
90
+ def test_sql_uow_commit_clears_entities_even_if_dispatch_fails():
91
+ async def _run():
92
+ session = MagicMock()
93
+ session.commit = AsyncMock()
94
+
95
+ uow = _build_sql_uow(session)
96
+ uow.dispatch_events = AsyncMock(side_effect=RuntimeError("dispatch failed"))
97
+ uow.clear_tracked_entities = MagicMock()
98
+
99
+ raised = None
100
+ try:
101
+ await uow.commit()
102
+ except RuntimeError as exc:
103
+ raised = exc
104
+
105
+ assert raised is not None
106
+ session.commit.assert_awaited_once()
107
+ uow.dispatch_events.assert_awaited_once()
108
+ uow.clear_tracked_entities.assert_called_once()
109
+
110
+ asyncio.run(_run())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes