hexcore 1.6.7__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. {hexcore-1.6.7 → hexcore-1.7.0}/PKG-INFO +1 -1
  2. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/utils.py +112 -4
  3. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/PKG-INFO +1 -1
  4. {hexcore-1.6.7 → hexcore-1.7.0}/pyproject.toml +1 -1
  5. {hexcore-1.6.7 → hexcore-1.7.0}/tests/test_repositories_utils.py +54 -0
  6. {hexcore-1.6.7 → hexcore-1.7.0}/LICENSE +0 -0
  7. {hexcore-1.6.7 → hexcore-1.7.0}/README.md +0 -0
  8. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/__init__.py +0 -0
  9. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/__init__.pyi +0 -0
  10. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/__main__.py +0 -0
  11. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/__init__.py +0 -0
  12. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/__init__.pyi +0 -0
  13. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/dtos/__init__.py +0 -0
  14. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/dtos/__init__.pyi +0 -0
  15. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/dtos/base.py +0 -0
  16. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/dtos/base.pyi +0 -0
  17. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/use_cases/__init__.py +0 -0
  18. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/use_cases/__init__.pyi +0 -0
  19. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/use_cases/base.py +0 -0
  20. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/application/use_cases/base.pyi +0 -0
  21. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/config.py +0 -0
  22. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/config.pyi +0 -0
  23. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/__init__.py +0 -0
  24. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/__init__.pyi +0 -0
  25. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/__init__.py +0 -0
  26. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/__init__.pyi +0 -0
  27. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/permissions.py +0 -0
  28. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/permissions.pyi +0 -0
  29. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/value_objects.py +0 -0
  30. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/auth/value_objects.pyi +0 -0
  31. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/base.py +0 -0
  32. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/base.pyi +0 -0
  33. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/events.py +0 -0
  34. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/events.pyi +0 -0
  35. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/exceptions.py +0 -0
  36. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/exceptions.pyi +0 -0
  37. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/repositories.py +0 -0
  38. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/repositories.pyi +0 -0
  39. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/services.py +0 -0
  40. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/services.pyi +0 -0
  41. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/uow.py +0 -0
  42. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/domain/uow.pyi +0 -0
  43. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/__init__.py +0 -0
  44. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/__init__.pyi +0 -0
  45. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/api/__init__.py +0 -0
  46. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/api/__init__.pyi +0 -0
  47. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/api/utils.py +0 -0
  48. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/api/utils.pyi +0 -0
  49. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/__init__.py +0 -0
  50. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/__init__.pyi +0 -0
  51. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
  52. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/__init__.pyi +0 -0
  53. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/memory.py +0 -0
  54. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/memory.pyi +0 -0
  55. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/redis.py +0 -0
  56. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cache/cache_backends/redis.pyi +0 -0
  57. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cli.py +0 -0
  58. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/cli.pyi +0 -0
  59. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/__init__.py +0 -0
  60. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/__init__.pyi +0 -0
  61. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/events_backends/__init__.py +0 -0
  62. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/events_backends/__init__.pyi +0 -0
  63. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/events_backends/memory.py +0 -0
  64. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/events/events_backends/memory.pyi +0 -0
  65. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/__init__.py +0 -0
  66. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/__init__.pyi +0 -0
  67. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/base.py +0 -0
  68. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/base.pyi +0 -0
  69. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/decorators.py +0 -0
  70. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/decorators.pyi +0 -0
  71. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/implementations.py +0 -0
  72. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/implementations.pyi +0 -0
  73. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
  74. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/__init__.pyi +0 -0
  75. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
  76. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/beanie/__init__.pyi +0 -0
  77. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/beanie/utils.py +0 -0
  78. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/beanie/utils.pyi +0 -0
  79. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +0 -0
  80. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.pyi +0 -0
  81. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +0 -0
  82. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/session.pyi +0 -0
  83. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.py +0 -0
  84. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/orms/sqlalchemy/utils.pyi +0 -0
  85. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/repositories/utils.pyi +0 -0
  86. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/__init__.py +0 -0
  87. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/__init__.pyi +0 -0
  88. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/decorators.py +0 -0
  89. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/decorators.pyi +0 -0
  90. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/helpers.py +0 -0
  91. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/infrastructure/uow/helpers.pyi +0 -0
  92. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/types.py +0 -0
  93. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore/types.pyi +0 -0
  94. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/SOURCES.txt +0 -0
  95. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/dependency_links.txt +0 -0
  96. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/entry_points.txt +0 -0
  97. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/requires.txt +0 -0
  98. {hexcore-1.6.7 → hexcore-1.7.0}/hexcore.egg-info/top_level.txt +0 -0
  99. {hexcore-1.6.7 → hexcore-1.7.0}/scripts/__init__.py +0 -0
  100. {hexcore-1.6.7 → hexcore-1.7.0}/scripts/fix_pyi_files.py +0 -0
  101. {hexcore-1.6.7 → hexcore-1.7.0}/scripts/fix_types_pyi_aliases.py +0 -0
  102. {hexcore-1.6.7 → hexcore-1.7.0}/scripts/main.py +0 -0
  103. {hexcore-1.6.7 → hexcore-1.7.0}/setup.cfg +0 -0
  104. {hexcore-1.6.7 → hexcore-1.7.0}/tests/test_basic.py +0 -0
  105. {hexcore-1.6.7 → hexcore-1.7.0}/tests/test_uow_session_regression.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 1.6.7
3
+ Version: 1.7.0
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
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import inspect
4
5
  import pkgutil
5
6
  import sys
6
7
  import typing as t
@@ -242,6 +243,65 @@ def _autoload_repository_modules() -> None:
242
243
  )
243
244
 
244
245
 
246
+ def _normalize_repository_module(module_name: str) -> str:
247
+ if module_name.startswith("src."):
248
+ return module_name[4:]
249
+ return module_name
250
+
251
+
252
+ def _get_preferred_repository_prefixes() -> list[str]:
253
+ try:
254
+ from hexcore.config import LazyConfig
255
+
256
+ config = LazyConfig.get_config()
257
+ except Exception:
258
+ return [
259
+ "src.infrastructure.repositories",
260
+ "infrastructure.repositories",
261
+ "hexcore.infrastructure.repositories",
262
+ ]
263
+
264
+ configured = getattr(config, "repository_discovery_preferred_prefixes", ())
265
+ if isinstance(configured, (list, tuple, set)):
266
+ normalized: list[str] = []
267
+ configured_iter = t.cast(t.Iterable[object], configured)
268
+ for item in configured_iter:
269
+ value = str(item).strip()
270
+ if value:
271
+ normalized.append(value)
272
+ if normalized:
273
+ return normalized
274
+
275
+ return [
276
+ "src.infrastructure.repositories",
277
+ "infrastructure.repositories",
278
+ "hexcore.infrastructure.repositories",
279
+ ]
280
+
281
+
282
+ def _module_priority(module_name: str, prefixes: list[str]) -> int:
283
+ normalized_module = module_name.strip()
284
+ for index, prefix in enumerate(prefixes):
285
+ normalized_prefix = prefix.strip()
286
+ if normalized_module == normalized_prefix or normalized_module.startswith(
287
+ f"{normalized_prefix}."
288
+ ):
289
+ return index
290
+ return len(prefixes)
291
+
292
+
293
+ def _get_repository_class_source_path(repo_cls: type) -> str | None:
294
+ try:
295
+ source_file = inspect.getsourcefile(repo_cls)
296
+ except (TypeError, OSError):
297
+ source_file = None
298
+
299
+ if source_file is None:
300
+ return None
301
+
302
+ return source_file.replace("\\", "/").lower()
303
+
304
+
245
305
  def _repository_key_from_class_name(class_name: str) -> str:
246
306
  normalized_name = class_name.strip()
247
307
  lowered_name = normalized_name.lower()
@@ -265,6 +325,7 @@ def _discover_repositories(
265
325
  ) -> t.Dict[str, type]:
266
326
  _autoload_repository_modules()
267
327
 
328
+ preferred_prefixes = _get_preferred_repository_prefixes()
268
329
  repositories: t.Dict[str, type] = {}
269
330
  all_subclasses = _get_all_subclasses(base_repository_class)
270
331
  sorted_classes = sorted(
@@ -285,11 +346,58 @@ def _discover_repositories(
285
346
  existing_repo_cls = repositories.get(repo_key)
286
347
 
287
348
  if existing_repo_cls is not None and existing_repo_cls is not repo_cls:
288
- raise ValueError(
289
- "Se detecto una colision de nombres de repositorio para "
290
- f"'{repo_key}': {existing_repo_cls.__module__}.{existing_repo_cls.__name__} "
291
- f"y {repo_cls.__module__}.{repo_cls.__name__}."
349
+ existing_module = _normalize_repository_module(existing_repo_cls.__module__)
350
+ current_module = _normalize_repository_module(repo_cls.__module__)
351
+ existing_source = _get_repository_class_source_path(existing_repo_cls)
352
+ current_source = _get_repository_class_source_path(repo_cls)
353
+
354
+ if (
355
+ existing_module == current_module
356
+ and existing_repo_cls.__name__ == repo_cls.__name__
357
+ and existing_repo_cls.__qualname__ == repo_cls.__qualname__
358
+ and existing_source is not None
359
+ and current_source is not None
360
+ and existing_source == current_source
361
+ ):
362
+ warnings.warn(
363
+ "Repositorio duplicado detectado por alias de import y omitido: "
364
+ f"'{repo_key}' -> {repo_cls.__module__}.{repo_cls.__name__}. "
365
+ f"Se mantiene {existing_repo_cls.__module__}.{existing_repo_cls.__name__}.",
366
+ UserWarning,
367
+ stacklevel=2,
368
+ )
369
+ continue
370
+
371
+ existing_priority = _module_priority(
372
+ existing_repo_cls.__module__, preferred_prefixes
292
373
  )
374
+ current_priority = _module_priority(repo_cls.__module__, preferred_prefixes)
375
+
376
+ if current_priority < existing_priority:
377
+ warnings.warn(
378
+ "Colision de repositorio resuelta por prioridad de modulo para "
379
+ f"'{repo_key}': se reemplaza "
380
+ f"{existing_repo_cls.__module__}.{existing_repo_cls.__name__} por "
381
+ f"{repo_cls.__module__}.{repo_cls.__name__}.",
382
+ UserWarning,
383
+ stacklevel=2,
384
+ )
385
+ elif current_priority > existing_priority:
386
+ warnings.warn(
387
+ "Colision de repositorio resuelta por prioridad de modulo para "
388
+ f"'{repo_key}': se omite "
389
+ f"{repo_cls.__module__}.{repo_cls.__name__} y se mantiene "
390
+ f"{existing_repo_cls.__module__}.{existing_repo_cls.__name__}.",
391
+ UserWarning,
392
+ stacklevel=2,
393
+ )
394
+ continue
395
+ else:
396
+ raise ValueError(
397
+ "Se detecto una colision de nombres de repositorio para "
398
+ f"'{repo_key}': {existing_repo_cls.__module__}.{existing_repo_cls.__name__} "
399
+ f"y {repo_cls.__module__}.{repo_cls.__name__}."
400
+ )
293
401
 
294
402
  repositories[repo_key] = repo_cls
295
403
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexcore
3
- Version: 1.6.7
3
+ Version: 1.7.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hexcore"
3
- version = "1.6.7"
3
+ version = "1.7.0"
4
4
  authors = [
5
5
  { name="David Latosefki (Indroic)", email="indroic@outlook.com" },
6
6
  ]
@@ -242,3 +242,57 @@ def test_discover_sql_repositories_warns_for_abstract_repositories() -> None:
242
242
  warning_message = str(caught[0].message)
243
243
  assert "_AbstractWarningRepo" in warning_message
244
244
  assert "custom_required" in warning_message
245
+
246
+
247
+ def test_discover_sql_repositories_ignores_alias_duplicates() -> None:
248
+ RepoBase = type("AccountingSnapshotRepository", (_SqlRepoA,), {})
249
+ RepoAlias = type("AccountingSnapshotRepository", (_SqlRepoA,), {})
250
+
251
+ RepoBase.__module__ = "infrastructure.repositories.accounting_snapshot_repository"
252
+ RepoAlias.__module__ = (
253
+ "src.infrastructure.repositories.accounting_snapshot_repository"
254
+ )
255
+ RepoBase.__qualname__ = "AccountingSnapshotRepository"
256
+ RepoAlias.__qualname__ = "AccountingSnapshotRepository"
257
+
258
+ with (
259
+ patch("hexcore.infrastructure.repositories.utils._autoload_repository_modules"),
260
+ patch(
261
+ "hexcore.infrastructure.repositories.utils._get_all_subclasses",
262
+ return_value={RepoBase, RepoAlias},
263
+ ),
264
+ patch(
265
+ "hexcore.infrastructure.repositories.utils._get_repository_class_source_path",
266
+ return_value="c:/repo/src/infrastructure/repositories/accounting_snapshot_repository.py",
267
+ ),
268
+ warnings.catch_warnings(record=True) as caught,
269
+ ):
270
+ warnings.simplefilter("always")
271
+ discovered = discover_sql_repositories()
272
+
273
+ assert "accountingsnapshot" in discovered
274
+ assert discovered["accountingsnapshot"] in {RepoBase, RepoAlias}
275
+ assert any("alias de import" in str(item.message) for item in caught)
276
+
277
+
278
+ def test_discover_sql_repositories_prefers_high_priority_prefix() -> None:
279
+ InfraRepo = type("PaymentsRepository", (_SqlRepoA,), {})
280
+ SrcRepo = type("PaymentsRepository", (_SqlRepoB,), {})
281
+
282
+ InfraRepo.__module__ = "infrastructure.repositories.payments_repository"
283
+ SrcRepo.__module__ = "src.infrastructure.repositories.payments_repository"
284
+
285
+ with (
286
+ patch("hexcore.infrastructure.repositories.utils._autoload_repository_modules"),
287
+ patch(
288
+ "hexcore.infrastructure.repositories.utils._get_all_subclasses",
289
+ return_value={InfraRepo, SrcRepo},
290
+ ),
291
+ warnings.catch_warnings(record=True) as caught,
292
+ ):
293
+ warnings.simplefilter("always")
294
+ discovered = discover_sql_repositories()
295
+
296
+ assert "payments" in discovered
297
+ assert discovered["payments"] is SrcRepo
298
+ assert any("prioridad de modulo" in str(item.message) for item in caught)
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