hexcore 2.1.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.
- hexcore-2.1.1/LICENSE +25 -0
- hexcore-2.1.1/PKG-INFO +38 -0
- hexcore-2.1.1/README.md +14 -0
- hexcore-2.1.1/hexcore/__init__.py +42 -0
- hexcore-2.1.1/hexcore/__init__.pyi +10 -0
- hexcore-2.1.1/hexcore/application/__init__.py +8 -0
- hexcore-2.1.1/hexcore/application/__init__.pyi +4 -0
- hexcore-2.1.1/hexcore/application/dtos/__init__.py +7 -0
- hexcore-2.1.1/hexcore/application/dtos/__init__.pyi +3 -0
- hexcore-2.1.1/hexcore/application/dtos/base.py +13 -0
- hexcore-2.1.1/hexcore/application/dtos/base.pyi +4 -0
- hexcore-2.1.1/hexcore/application/use_cases/__init__.py +7 -0
- hexcore-2.1.1/hexcore/application/use_cases/__init__.pyi +3 -0
- hexcore-2.1.1/hexcore/application/use_cases/base.py +15 -0
- hexcore-2.1.1/hexcore/application/use_cases/base.pyi +11 -0
- hexcore-2.1.1/hexcore/config.py +99 -0
- hexcore-2.1.1/hexcore/config.pyi +35 -0
- hexcore-2.1.1/hexcore/domain/__init__.py +0 -0
- hexcore-2.1.1/hexcore/domain/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/domain/auth/__init__.py +17 -0
- hexcore-2.1.1/hexcore/domain/auth/__init__.pyi +4 -0
- hexcore-2.1.1/hexcore/domain/auth/permissions.py +73 -0
- hexcore-2.1.1/hexcore/domain/auth/permissions.pyi +25 -0
- hexcore-2.1.1/hexcore/domain/auth/value_objects.py +18 -0
- hexcore-2.1.1/hexcore/domain/auth/value_objects.pyi +12 -0
- hexcore-2.1.1/hexcore/domain/base.py +80 -0
- hexcore-2.1.1/hexcore/domain/base.pyi +19 -0
- hexcore-2.1.1/hexcore/domain/events.py +70 -0
- hexcore-2.1.1/hexcore/domain/events.pyi +35 -0
- hexcore-2.1.1/hexcore/domain/exceptions.py +5 -0
- hexcore-2.1.1/hexcore/domain/exceptions.pyi +2 -0
- hexcore-2.1.1/hexcore/domain/repositories.py +51 -0
- hexcore-2.1.1/hexcore/domain/repositories.pyi +25 -0
- hexcore-2.1.1/hexcore/domain/services.py +11 -0
- hexcore-2.1.1/hexcore/domain/services.pyi +8 -0
- hexcore-2.1.1/hexcore/domain/uow.py +55 -0
- hexcore-2.1.1/hexcore/domain/uow.pyi +18 -0
- hexcore-2.1.1/hexcore/infrastructure/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/api/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/api/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/api/utils.py +20 -0
- hexcore-2.1.1/hexcore/infrastructure/api/utils.pyi +9 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/__init__.py +16 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/__init__.pyi +10 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/memory.py +24 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/memory.pyi +10 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/redis.py +31 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/redis.pyi +12 -0
- hexcore-2.1.1/hexcore/infrastructure/cli.py +397 -0
- hexcore-2.1.1/hexcore/infrastructure/cli.pyi +21 -0
- hexcore-2.1.1/hexcore/infrastructure/events/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.py +20 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.pyi +8 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/base.py +24 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/base.pyi +13 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/decorators.py +50 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/decorators.pyi +5 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/implementations.py +192 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/implementations.pyi +39 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.py +20 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.pyi +14 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.py +134 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.pyi +18 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.py +44 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.pyi +19 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.py +31 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.pyi +9 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/utils.py +171 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/utils.pyi +22 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/utils.py +119 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/utils.pyi +13 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/__init__.py +121 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/__init__.pyi +22 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/decorators.py +16 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/decorators.pyi +4 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/helpers.py +9 -0
- hexcore-2.1.1/hexcore/infrastructure/uow/helpers.pyi +7 -0
- hexcore-2.1.1/hexcore/types.py +33 -0
- hexcore-2.1.1/hexcore/types.pyi +21 -0
- hexcore-2.1.1/hexcore.egg-info/PKG-INFO +38 -0
- hexcore-2.1.1/hexcore.egg-info/SOURCES.txt +100 -0
- hexcore-2.1.1/hexcore.egg-info/dependency_links.txt +1 -0
- hexcore-2.1.1/hexcore.egg-info/entry_points.txt +2 -0
- hexcore-2.1.1/hexcore.egg-info/requires.txt +13 -0
- hexcore-2.1.1/hexcore.egg-info/top_level.txt +4 -0
- hexcore-2.1.1/pyproject.toml +64 -0
- hexcore-2.1.1/setup.cfg +4 -0
- hexcore-2.1.1/tests/test_basic.py +2 -0
hexcore-2.1.1/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
2
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
3
|
+
furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2025 David Latosefki (Indroic)
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
hexcore-2.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hexcore
|
|
3
|
+
Version: 2.1.1
|
|
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
|
+
Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: aiosqlite>=0.21.0
|
|
11
|
+
Requires-Dist: alembic>=1.16.5
|
|
12
|
+
Requires-Dist: async-typer>=0.1.10
|
|
13
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
14
|
+
Requires-Dist: beanie>=2.0.0
|
|
15
|
+
Requires-Dist: build>=1.3.0
|
|
16
|
+
Requires-Dist: fastapi>=0.116.1
|
|
17
|
+
Requires-Dist: pika>=1.3.2
|
|
18
|
+
Requires-Dist: pydantic>=2.11.7
|
|
19
|
+
Requires-Dist: redis>=6.4.0
|
|
20
|
+
Requires-Dist: sqlalchemy>=2.0.43
|
|
21
|
+
Requires-Dist: twine>=6.2.0
|
|
22
|
+
Requires-Dist: typer>=0.17.3
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# HexCore
|
|
26
|
+
|
|
27
|
+
HexCore es un módulo base reutilizable para proyectos Python que implementan arquitectura hexagonal y event handling.
|
|
28
|
+
|
|
29
|
+
## ¿Qué provee HexCore?
|
|
30
|
+
|
|
31
|
+
- **Clases base y abstracciones** para entidades, repositorios, servicios y unidad de trabajo (UoW), siguiendo los principios de DDD y arquitectura hexagonal.
|
|
32
|
+
- **Interfaces y contratos** para caché, eventos y manejo de dependencias, desacoplando la lógica de negocio de la infraestructura.
|
|
33
|
+
- **Utilidades para event sourcing y event dispatching** listas para usar en cualquier proyecto.
|
|
34
|
+
- **Estructura flexible** para que puedas construir microservicios o aplicaciones monolíticas desacopladas y testeables.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
**Versión:** 2.1.1
|
hexcore-2.1.1/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# HexCore
|
|
2
|
+
|
|
3
|
+
HexCore es un módulo base reutilizable para proyectos Python que implementan arquitectura hexagonal y event handling.
|
|
4
|
+
|
|
5
|
+
## ¿Qué provee HexCore?
|
|
6
|
+
|
|
7
|
+
- **Clases base y abstracciones** para entidades, repositorios, servicios y unidad de trabajo (UoW), siguiendo los principios de DDD y arquitectura hexagonal.
|
|
8
|
+
- **Interfaces y contratos** para caché, eventos y manejo de dependencias, desacoplando la lógica de negocio de la infraestructura.
|
|
9
|
+
- **Utilidades para event sourcing y event dispatching** listas para usar en cualquier proyecto.
|
|
10
|
+
- **Estructura flexible** para que puedas construir microservicios o aplicaciones monolíticas desacopladas y testeables.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
**Versión:** 2.1.1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Euphoria Kernel Core
|
|
3
|
+
Submódulo principal con entidades, eventos y repositorios.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .domain.base import BaseEntity
|
|
7
|
+
from .domain.auth.permissions import PermissionsEnum
|
|
8
|
+
from .domain.auth.value_objects import TokenClaims
|
|
9
|
+
from .application.dtos.base import DTO
|
|
10
|
+
from .domain.events import (
|
|
11
|
+
DomainEvent,
|
|
12
|
+
EntityCreatedEvent,
|
|
13
|
+
EntityDeletedEvent,
|
|
14
|
+
EntityUpdatedEvent,
|
|
15
|
+
)
|
|
16
|
+
from .infrastructure.repositories.base import (
|
|
17
|
+
IBaseRepository,
|
|
18
|
+
IBaseTenantAwareRepository,
|
|
19
|
+
BaseSQLRepository,
|
|
20
|
+
BaseSQLTenantAwareRepository,
|
|
21
|
+
)
|
|
22
|
+
from .infrastructure import cli
|
|
23
|
+
from .infrastructure import cache
|
|
24
|
+
from . import config
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BaseEntity",
|
|
28
|
+
"PermissionsEnum",
|
|
29
|
+
"TokenClaims",
|
|
30
|
+
"DTO",
|
|
31
|
+
"DomainEvent",
|
|
32
|
+
"EntityCreatedEvent",
|
|
33
|
+
"EntityDeletedEvent",
|
|
34
|
+
"EntityUpdatedEvent",
|
|
35
|
+
"IBaseRepository",
|
|
36
|
+
"IBaseTenantAwareRepository",
|
|
37
|
+
"BaseSQLRepository",
|
|
38
|
+
"BaseSQLTenantAwareRepository",
|
|
39
|
+
"cli",
|
|
40
|
+
"cache",
|
|
41
|
+
"config",
|
|
42
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from . import config as config
|
|
2
|
+
from .application.dtos.base import DTO as DTO
|
|
3
|
+
from .domain.auth.permissions import PermissionsEnum as PermissionsEnum
|
|
4
|
+
from .domain.auth.value_objects import TokenClaims as TokenClaims
|
|
5
|
+
from .domain.base import BaseEntity as BaseEntity
|
|
6
|
+
from .domain.events import DomainEvent as DomainEvent, EntityCreatedEvent as EntityCreatedEvent, EntityDeletedEvent as EntityDeletedEvent, EntityUpdatedEvent as EntityUpdatedEvent
|
|
7
|
+
from .infrastructure import cache as cache, cli as cli
|
|
8
|
+
from .infrastructure.repositories.base import BaseSQLRepository as BaseSQLRepository, BaseSQLTenantAwareRepository as BaseSQLTenantAwareRepository, IBaseRepository as IBaseRepository, IBaseTenantAwareRepository as IBaseTenantAwareRepository
|
|
9
|
+
|
|
10
|
+
__all__ = ['BaseEntity', 'PermissionsEnum', 'TokenClaims', 'DTO', 'DomainEvent', 'EntityCreatedEvent', 'EntityDeletedEvent', 'EntityUpdatedEvent', 'IBaseRepository', 'IBaseTenantAwareRepository', 'BaseSQLRepository', 'BaseSQLTenantAwareRepository', 'cli', 'cache', 'config']
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DTO(BaseModel, ABC):
|
|
6
|
+
"""
|
|
7
|
+
Clase base para todos los DTOs de la capa de aplicación.
|
|
8
|
+
|
|
9
|
+
Estos representan los datos que se desea exponer o recibir a través de la API,
|
|
10
|
+
evitando la exposición de detalles internos del dominio.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
pass
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from hexcore.application.dtos.base import DTO
|
|
4
|
+
|
|
5
|
+
# Tipo del Input
|
|
6
|
+
T = t.TypeVar("T", bound=DTO)
|
|
7
|
+
|
|
8
|
+
# Tipo del Output(o resultado)
|
|
9
|
+
R = t.TypeVar("R", bound=DTO)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UseCase(ABC, t.Generic[T, R]):
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def execute(self, command: T) -> R:
|
|
15
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as t
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from hexcore.application.dtos.base import DTO as DTO
|
|
5
|
+
|
|
6
|
+
T = t.TypeVar('T', bound=DTO)
|
|
7
|
+
R = t.TypeVar('R', bound=DTO)
|
|
8
|
+
|
|
9
|
+
class UseCase(ABC, t.Generic[T, R], metaclass=abc.ABCMeta):
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def execute(self, command: T) -> R: ...
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import importlib
|
|
3
|
+
import typing as t
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from hexcore.infrastructure.cache import ICache
|
|
7
|
+
from hexcore.domain.events import IEventDispatcher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from hexcore.infrastructure.cache.cache_backends.memory import MemoryCache
|
|
11
|
+
from hexcore.infrastructure.events.events_backends.memory import InMemoryEventDispatcher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ServerConfig(BaseModel):
|
|
15
|
+
# Project Config
|
|
16
|
+
base_dir: Path = Path(".")
|
|
17
|
+
|
|
18
|
+
# SERVER CONFIG
|
|
19
|
+
host: str = "localhost"
|
|
20
|
+
port: int = 8000
|
|
21
|
+
debug: bool = True
|
|
22
|
+
|
|
23
|
+
# DB CONFIG
|
|
24
|
+
sql_database_url: str = "sqlite:///./db.sqlite3"
|
|
25
|
+
async_sql_database_url: str = "sqlite+aiosqlite:///./db.sqlite3"
|
|
26
|
+
|
|
27
|
+
mongo_database_url: str = "mongodb://localhost:27017"
|
|
28
|
+
async_mongo_database_url: str = "mongodb+async://localhost:27017"
|
|
29
|
+
mongo_db_name: str = "euphoria_db"
|
|
30
|
+
mongo_uri: str = "mongodb://localhost:27017/euphoria_db"
|
|
31
|
+
|
|
32
|
+
redis_uri: str = "redis://localhost:6379/0"
|
|
33
|
+
redis_host: str = "localhost"
|
|
34
|
+
redis_port: int = 6379
|
|
35
|
+
redis_db: int = 0
|
|
36
|
+
redis_cache_duration: int = 300 # seconds
|
|
37
|
+
|
|
38
|
+
# Security
|
|
39
|
+
allow_origins: list[str] = [
|
|
40
|
+
"*" if debug else "http://localhost:{port}".format(port=port)
|
|
41
|
+
]
|
|
42
|
+
allow_credentials: bool = True
|
|
43
|
+
allow_methods: list[str] = ["*"]
|
|
44
|
+
allow_headers: list[str] = ["*"]
|
|
45
|
+
|
|
46
|
+
# caching
|
|
47
|
+
cache_backend: ICache = (
|
|
48
|
+
MemoryCache()
|
|
49
|
+
) # Debe ser una instancia de ICache(o subclase)
|
|
50
|
+
|
|
51
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
52
|
+
|
|
53
|
+
# Event Dispatcher
|
|
54
|
+
event_dispatcher: IEventDispatcher = InMemoryEventDispatcher()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LazyConfig:
|
|
58
|
+
"""
|
|
59
|
+
Loader de configuración flexible.
|
|
60
|
+
Busca una variable 'config' (instancia de ServerConfig) o una clase 'ServerConfig' en los módulos personalizados.
|
|
61
|
+
Si no la encuentra, usa la configuración base del kernel.
|
|
62
|
+
|
|
63
|
+
IMPORTANTE: La configuración personalizada debe estar en un módulo llamado 'config' en src.domain
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
_imported_config: t.Optional[ServerConfig] = None
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def get_config(cls) -> ServerConfig:
|
|
71
|
+
if cls._imported_config is not None:
|
|
72
|
+
return cls._imported_config
|
|
73
|
+
# Intenta importar la config personalizada
|
|
74
|
+
for modpath in ("config", "src.domain.config"):
|
|
75
|
+
try:
|
|
76
|
+
mod = importlib.import_module(modpath)
|
|
77
|
+
config_instance = getattr(mod, "config", None)
|
|
78
|
+
if config_instance is not None:
|
|
79
|
+
# Si es clase, instanciar
|
|
80
|
+
if isinstance(config_instance, type) and issubclass(
|
|
81
|
+
config_instance, ServerConfig
|
|
82
|
+
):
|
|
83
|
+
config_instance = config_instance()
|
|
84
|
+
if isinstance(config_instance, ServerConfig):
|
|
85
|
+
cls._imported_config = config_instance
|
|
86
|
+
return cls._imported_config
|
|
87
|
+
# Alternativamente, busca la clase ServerConfig
|
|
88
|
+
config_class = getattr(mod, "ServerConfig", None)
|
|
89
|
+
if isinstance(config_class, type) and issubclass(
|
|
90
|
+
config_class, ServerConfig
|
|
91
|
+
):
|
|
92
|
+
config_instance = config_class()
|
|
93
|
+
cls._imported_config = config_instance
|
|
94
|
+
return cls._imported_config
|
|
95
|
+
except (ModuleNotFoundError, AttributeError):
|
|
96
|
+
continue
|
|
97
|
+
# Fallback: config base del kernel
|
|
98
|
+
cls._imported_config = ServerConfig()
|
|
99
|
+
return cls._imported_config
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from hexcore.domain.events import IEventDispatcher as IEventDispatcher
|
|
3
|
+
from hexcore.infrastructure.cache import ICache as ICache
|
|
4
|
+
from hexcore.infrastructure.cache.cache_backends.memory import MemoryCache as MemoryCache
|
|
5
|
+
from hexcore.infrastructure.events.events_backends.memory import InMemoryEventDispatcher as InMemoryEventDispatcher
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
class ServerConfig(BaseModel):
|
|
10
|
+
base_dir: Path
|
|
11
|
+
host: str
|
|
12
|
+
port: int
|
|
13
|
+
debug: bool
|
|
14
|
+
sql_database_url: str
|
|
15
|
+
async_sql_database_url: str
|
|
16
|
+
mongo_database_url: str
|
|
17
|
+
async_mongo_database_url: str
|
|
18
|
+
mongo_db_name: str
|
|
19
|
+
mongo_uri: str
|
|
20
|
+
redis_uri: str
|
|
21
|
+
redis_host: str
|
|
22
|
+
redis_port: int
|
|
23
|
+
redis_db: int
|
|
24
|
+
redis_cache_duration: int
|
|
25
|
+
allow_origins: list[str]
|
|
26
|
+
allow_credentials: bool
|
|
27
|
+
allow_methods: list[str]
|
|
28
|
+
allow_headers: list[str]
|
|
29
|
+
cache_backend: ICache
|
|
30
|
+
model_config: Incomplete
|
|
31
|
+
event_dispatcher: IEventDispatcher
|
|
32
|
+
|
|
33
|
+
class LazyConfig:
|
|
34
|
+
@classmethod
|
|
35
|
+
def get_config(cls) -> ServerConfig: ...
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Submódulo de autenticación y permisos del kernel.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .permissions import (
|
|
6
|
+
PermissionsEnum,
|
|
7
|
+
get_all_permission_values,
|
|
8
|
+
get_owner_permissions,
|
|
9
|
+
)
|
|
10
|
+
from .value_objects import TokenClaims
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"PermissionsEnum",
|
|
14
|
+
"get_all_permission_values",
|
|
15
|
+
"get_owner_permissions",
|
|
16
|
+
"TokenClaims",
|
|
17
|
+
]
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from .permissions import PermissionsEnum as PermissionsEnum, get_all_permission_values as get_all_permission_values, get_owner_permissions as get_owner_permissions
|
|
2
|
+
from .value_objects import TokenClaims as TokenClaims
|
|
3
|
+
|
|
4
|
+
__all__ = ['PermissionsEnum', 'get_all_permission_values', 'get_owner_permissions', 'TokenClaims']
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
# --- Prefijos de Permisos ---
|
|
5
|
+
_ROLES = "roles"
|
|
6
|
+
_USERS = "users"
|
|
7
|
+
_TENANTS = "tenants"
|
|
8
|
+
|
|
9
|
+
_LOGISTICS_INVENTORY = "logistics.inventory"
|
|
10
|
+
_LOGISTICS_PRODUCTS = "logistics.products"
|
|
11
|
+
|
|
12
|
+
# --- Sufijos Comunes ---
|
|
13
|
+
|
|
14
|
+
_VIEW = "view"
|
|
15
|
+
_CREATE = "create"
|
|
16
|
+
_EDIT = "edit"
|
|
17
|
+
_DELETE = "delete"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PermissionsEnum(str, Enum):
|
|
21
|
+
"""
|
|
22
|
+
Catálogo central de todos los permisos del sistema.
|
|
23
|
+
Esta es la ÚNICA fuente de la verdad para los permisos.
|
|
24
|
+
El valor (string) es lo que se almacena en la base de datos.
|
|
25
|
+
El formato es: <dominio>.<entidad>.<accion>
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# -- SUPERUSER PERMISSION --
|
|
29
|
+
|
|
30
|
+
SUPERUSER = "__all__" # Permiso especial que otorga todos los permisos del sistema
|
|
31
|
+
|
|
32
|
+
# --- Dominio: IAM (Identidad y Acceso) ---
|
|
33
|
+
ROLES_VIEW = f"{_ROLES}.{_VIEW}"
|
|
34
|
+
ROLES_CREATE = f"{_ROLES}.{_CREATE}"
|
|
35
|
+
ROLES_EDIT = f"{_ROLES}.{_EDIT}"
|
|
36
|
+
ROLES_DELETE = f"{_ROLES}.{_DELETE}"
|
|
37
|
+
|
|
38
|
+
USERS_CREATE = f"{_USERS}.{_CREATE}"
|
|
39
|
+
USERS_VIEW = f"{_USERS}.{_VIEW}"
|
|
40
|
+
USERS_INVITE = f"{_USERS}.invite"
|
|
41
|
+
USERS_EDIT = f"{_USERS}.{_EDIT}"
|
|
42
|
+
USERS_DELETE = f"{_USERS}.{_DELETE}"
|
|
43
|
+
USERS_ADD_ROLE = f"{_USERS}.add.rol"
|
|
44
|
+
|
|
45
|
+
TENANTS_VIEW = f"{_TENANTS}.{_VIEW}"
|
|
46
|
+
TENANTS_EDIT = f"{_TENANTS}.{_EDIT}"
|
|
47
|
+
|
|
48
|
+
# --- Dominio: Logistics (Logística) ---
|
|
49
|
+
LOGISTICS_INVENTORY_VIEW = f"{_LOGISTICS_INVENTORY}.{_VIEW}"
|
|
50
|
+
LOGISTICS_INVENTORY_ADJUST = f"{_LOGISTICS_INVENTORY}.adjust"
|
|
51
|
+
LOGISTICS_PRODUCTS_MANAGE = f"{_LOGISTICS_PRODUCTS}.manage"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Permisos que No puede tener un propietario
|
|
55
|
+
OWNER_EXCLUDED_PERMISSIONS = {
|
|
56
|
+
PermissionsEnum.TENANTS_EDIT,
|
|
57
|
+
PermissionsEnum.TENANTS_VIEW,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_all_permission_values() -> set[str]:
|
|
62
|
+
"""
|
|
63
|
+
Devuelve un conjunto con todos los valores de los permisos definidos en PermissionsEnum.
|
|
64
|
+
Ideal para usar en el comando de sincronización.
|
|
65
|
+
"""
|
|
66
|
+
return {p.value for p in PermissionsEnum}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_owner_permissions() -> set[str]:
|
|
70
|
+
"""
|
|
71
|
+
Devuelve los permisos de un propietario.
|
|
72
|
+
"""
|
|
73
|
+
return {p.value for p in PermissionsEnum} - OWNER_EXCLUDED_PERMISSIONS
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
class PermissionsEnum(str, Enum):
|
|
5
|
+
SUPERUSER = '__all__'
|
|
6
|
+
ROLES_VIEW = ...
|
|
7
|
+
ROLES_CREATE = ...
|
|
8
|
+
ROLES_EDIT = ...
|
|
9
|
+
ROLES_DELETE = ...
|
|
10
|
+
USERS_CREATE = ...
|
|
11
|
+
USERS_VIEW = ...
|
|
12
|
+
USERS_INVITE = ...
|
|
13
|
+
USERS_EDIT = ...
|
|
14
|
+
USERS_DELETE = ...
|
|
15
|
+
USERS_ADD_ROLE = ...
|
|
16
|
+
TENANTS_VIEW = ...
|
|
17
|
+
TENANTS_EDIT = ...
|
|
18
|
+
LOGISTICS_INVENTORY_VIEW = ...
|
|
19
|
+
LOGISTICS_INVENTORY_ADJUST = ...
|
|
20
|
+
LOGISTICS_PRODUCTS_MANAGE = ...
|
|
21
|
+
|
|
22
|
+
OWNER_EXCLUDED_PERMISSIONS: Incomplete
|
|
23
|
+
|
|
24
|
+
def get_all_permission_values() -> set[str]: ...
|
|
25
|
+
def get_owner_permissions() -> set[str]: ...
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
from pydantic import Field, BaseModel
|
|
5
|
+
from .permissions import PermissionsEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenClaims(BaseModel):
|
|
9
|
+
"""Detalles sobre los claims de un token."""
|
|
10
|
+
|
|
11
|
+
iss: str # Identificador del token de acceso
|
|
12
|
+
sub: str # ID del usuario
|
|
13
|
+
exp: int # Tiempo de expiración
|
|
14
|
+
iat: int # Tiempo de emisión
|
|
15
|
+
jti: str = Field(default_factory=lambda: str(uuid4())) # ID del token
|
|
16
|
+
client_id: str # ID del cliente OAuth
|
|
17
|
+
scopes: t.List[PermissionsEnum] = [] # Permisos del Token
|
|
18
|
+
tenant_id: t.Optional[str] = None # ID del tenant (si aplica)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .permissions import PermissionsEnum as PermissionsEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
class TokenClaims(BaseModel):
|
|
5
|
+
iss: str
|
|
6
|
+
sub: str
|
|
7
|
+
exp: int
|
|
8
|
+
iat: int
|
|
9
|
+
jti: str
|
|
10
|
+
client_id: str
|
|
11
|
+
scopes: list[PermissionsEnum]
|
|
12
|
+
tenant_id: str | None
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import typing as t
|
|
3
|
+
import abc
|
|
4
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
5
|
+
from uuid import UUID, uuid4
|
|
6
|
+
from datetime import datetime, UTC
|
|
7
|
+
|
|
8
|
+
if t.TYPE_CHECKING:
|
|
9
|
+
from hexcore.domain.events import DomainEvent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseEntity(BaseModel):
|
|
13
|
+
"""
|
|
14
|
+
Clase base para todas las entidades del dominio.
|
|
15
|
+
|
|
16
|
+
Proporciona campos comunes y configuración de Pydantic para asegurar consistencia
|
|
17
|
+
y comportamiento predecible en todo el modelo.
|
|
18
|
+
|
|
19
|
+
Atributos:
|
|
20
|
+
id (UUID): Identificador único universal para la entidad.
|
|
21
|
+
created_at (datetime): Marca de tiempo de la creación de la entidad (UTC).
|
|
22
|
+
updated_at (datetime): Marca de tiempo de la última actualización (UTC).
|
|
23
|
+
is_active (bool): Indicador para borrado lógico (soft delete).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
id: UUID = Field(default_factory=uuid4)
|
|
27
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
28
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
29
|
+
is_active: t.Optional[bool] = True
|
|
30
|
+
|
|
31
|
+
_domain_events: t.List[DomainEvent] = []
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(
|
|
34
|
+
from_attributes=True, # Permite crear modelos desde atributos de objetos (clave para ORMs).
|
|
35
|
+
validate_assignment=True, # Vuelve a validar la entidad cada vez que se modifica un campo.
|
|
36
|
+
# `frozen=True` hace que las entidades sean inmutables, lo cual es un ideal de DDD.
|
|
37
|
+
# Sin embargo, puede complicar el manejo de estado con un ORM como SQLAlchemy,
|
|
38
|
+
# donde los objetos a menudo se modifican y luego se guardan.
|
|
39
|
+
# Lo cambiamos a False para un enfoque más pragmático.
|
|
40
|
+
frozen=False,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def register_event(self, event: DomainEvent):
|
|
44
|
+
"""Añade un evento a la lista de la entidad."""
|
|
45
|
+
self._domain_events.append(event)
|
|
46
|
+
|
|
47
|
+
def pull_domain_events(self) -> t.List[DomainEvent]:
|
|
48
|
+
"""Entrega los eventos y limpia la lista."""
|
|
49
|
+
events = self._domain_events[:]
|
|
50
|
+
self._domain_events.clear()
|
|
51
|
+
return events
|
|
52
|
+
|
|
53
|
+
def clear_domain_events(self):
|
|
54
|
+
"""Limpia la lista de eventos sin entregarlos."""
|
|
55
|
+
self._domain_events.clear()
|
|
56
|
+
|
|
57
|
+
async def deactivate(self):
|
|
58
|
+
"""Desactiva la Entidad(Borrado Logico)"""
|
|
59
|
+
self.is_active = False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AbstractModelMeta(BaseEntity, abc.ABC):
|
|
63
|
+
"""
|
|
64
|
+
Metaclase para resolver un conflicto entre Pydantic y las clases abstractas de Python.
|
|
65
|
+
|
|
66
|
+
Problema:
|
|
67
|
+
- Pydantic (`BaseModel`) usa su propia metaclase para la validación de datos.
|
|
68
|
+
- Las clases abstractas de Python (`abc.ABC`) usan `abc.ABCMeta` para permitir `@abstractmethod`.
|
|
69
|
+
- Una clase no puede tener dos metaclases diferentes.
|
|
70
|
+
|
|
71
|
+
Solución:
|
|
72
|
+
Esta metaclase combina ambas, permitiendo crear clases que son a la vez
|
|
73
|
+
modelos de Pydantic y clases base abstractas.
|
|
74
|
+
|
|
75
|
+
Uso:
|
|
76
|
+
class MiClaseAbstracta(BaseEntity, abc.ABC, metaclass=AbstractModelMeta):
|
|
77
|
+
...
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
pass
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from hexcore.domain.events import DomainEvent as DomainEvent
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
class BaseEntity(BaseModel):
|
|
9
|
+
id: UUID
|
|
10
|
+
created_at: datetime
|
|
11
|
+
updated_at: datetime
|
|
12
|
+
is_active: bool | None
|
|
13
|
+
model_config: Incomplete
|
|
14
|
+
def register_event(self, event: DomainEvent): ...
|
|
15
|
+
def pull_domain_events(self) -> list[DomainEvent]: ...
|
|
16
|
+
def clear_domain_events(self) -> None: ...
|
|
17
|
+
async def deactivate(self) -> None: ...
|
|
18
|
+
|
|
19
|
+
class AbstractModelMeta(BaseEntity, abc.ABC): ...
|