sincpro-framework 1.0.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.
- sincpro_framework-1.0.1/LICENSE.md +61 -0
- sincpro_framework-1.0.1/PKG-INFO +206 -0
- sincpro_framework-1.0.1/README.md +189 -0
- sincpro_framework-1.0.1/pyproject.toml +43 -0
- sincpro_framework-1.0.1/sincpro_framework/__init__.py +3 -0
- sincpro_framework-1.0.1/sincpro_framework/bus.py +128 -0
- sincpro_framework-1.0.1/sincpro_framework/exceptions.py +14 -0
- sincpro_framework-1.0.1/sincpro_framework/ioc.py +109 -0
- sincpro_framework-1.0.1/sincpro_framework/sincpro_abstractions.py +43 -0
- sincpro_framework-1.0.1/sincpro_framework/sincpro_logger.py +14 -0
- sincpro_framework-1.0.1/sincpro_framework/use_bus.py +103 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Copyright (c) 2023 Sincpro S.R.L
|
|
2
|
+
LICENCIA EMPRESARIAL DE USO DE SOFTWARE
|
|
3
|
+
|
|
4
|
+
Términos y Condiciones
|
|
5
|
+
|
|
6
|
+
1. Definiciones
|
|
7
|
+
|
|
8
|
+
1.1. "Software" hace referencia a cualquier programa de computadora y su documentación relacionada desarrollados
|
|
9
|
+
por Sincpro Developers.
|
|
10
|
+
|
|
11
|
+
1.2. "Cliente" se refiere a cualquier entidad o individuo que obtiene una licencia para utilizar el Software bajo
|
|
12
|
+
los términos y condiciones establecidos en este documento.
|
|
13
|
+
|
|
14
|
+
2. Concesión de Licencia
|
|
15
|
+
Sincpro Developers otorga al Cliente una licencia no exclusiva, no transferible y limitada para utilizar el Software
|
|
16
|
+
de acuerdo con los términos y condiciones establecidos en este documento.
|
|
17
|
+
|
|
18
|
+
3. Propiedad y Derechos de Autor
|
|
19
|
+
|
|
20
|
+
3.1. El Software es propiedad exclusiva de Sincpro Developers y está protegido por leyes de derechos de autor y otras
|
|
21
|
+
leyes de propiedad intelectual.
|
|
22
|
+
|
|
23
|
+
3.2. El Cliente reconoce y acepta que no adquiere ningún derecho de propiedad sobre el Software, excepto los derechos
|
|
24
|
+
limitados otorgados por esta licencia.
|
|
25
|
+
|
|
26
|
+
4. Alcance de Uso
|
|
27
|
+
|
|
28
|
+
4.1. El Cliente está autorizado a utilizar el Software únicamente para los fines y en la forma especificados en la
|
|
29
|
+
documentación proporcionada por Sincpro Developers.
|
|
30
|
+
|
|
31
|
+
4.2. El uso del Software por parte del Cliente está limitado al número de usuarios y ubicaciones geográficas
|
|
32
|
+
especificados en la documentación.
|
|
33
|
+
|
|
34
|
+
5. Restricciones
|
|
35
|
+
|
|
36
|
+
5.1. Queda estrictamente prohibido al Cliente: (a) copiar, modificar o crear trabajos derivados del Software;
|
|
37
|
+
(b) sublicenciar, transferir o distribuir el Software a terceros;
|
|
38
|
+
(c) utilizar el Software de manera que infrinja las leyes aplicables.
|
|
39
|
+
|
|
40
|
+
6. Sanciones y Consecuencias
|
|
41
|
+
|
|
42
|
+
6.1. El uso no autorizado o en violación de los términos de esta licencia puede resultar en acciones legales y
|
|
43
|
+
sanciones que Sincpro Developers considera necesarias y apropiadas.
|
|
44
|
+
|
|
45
|
+
7. Protección Legal
|
|
46
|
+
|
|
47
|
+
7.1. Estos términos y condiciones están sujetos a las leyes y regulaciones aplicables. El Cliente acepta someterse
|
|
48
|
+
a la jurisdicción de los tribunales competentes en caso de disputa relacionada con el uso del Software.
|
|
49
|
+
|
|
50
|
+
8. Cambios y Actualizaciones
|
|
51
|
+
|
|
52
|
+
8.1. Sincpro Developers se reserva el derecho de actualizar o modificar estos términos y condiciones en cualquier
|
|
53
|
+
momento. Dichos cambios serán efectivos a partir de la fecha de notificación al Cliente.
|
|
54
|
+
|
|
55
|
+
9. Aceptación
|
|
56
|
+
|
|
57
|
+
9.1. Al utilizar el Software, el Cliente acepta y se compromete a cumplir con los términos y condiciones establecidos
|
|
58
|
+
en este documento.
|
|
59
|
+
|
|
60
|
+
Para cualquier consulta sobre esta licencia o para solicitar permisos adicionales, comuníquese con Sincpro Developers
|
|
61
|
+
en Bolivia, Cochabamba Mayor rocha #161 y ayacucho o informacion@sincpro.com.bo
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sincpro-framework
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Sincpro framework to use DDD, Clean architecture, Hexagonal architecture
|
|
5
|
+
License: Copyright (c) 2023 Sincpro S.R.L
|
|
6
|
+
Author: Gutierrez Andres
|
|
7
|
+
Author-email: andru1236@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: dependency-injector (>=4.42.0,<5.0.0)
|
|
15
|
+
Requires-Dist: logzero (>=1.7.0,<2.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
## Sincpro Framework (Application layer framework)
|
|
19
|
+
|
|
20
|
+
The main goal of this framework is create a `proxy` and `facade` component/entity, using a pattern `bus` components that wraps all the application layer, allowing to the developer import all the business logic ready to be executed in any context
|
|
21
|
+
|
|
22
|
+
### Main features:
|
|
23
|
+
- Allow to inject `dependencies` provided by the user
|
|
24
|
+
- Allow to inject `error handler` at the top level
|
|
25
|
+
- Execute the business logic in a `bus` component only providing the DTO(`Data transfer object`)
|
|
26
|
+
- Allow to execute decoupled (`use case`, `feature` , `business logic`)
|
|
27
|
+
- using the component `Feature`
|
|
28
|
+
- Allow to execute several `use cases` based on abstraction called `ApplicationService`
|
|
29
|
+
- This one contains the `feature bus` and the developer will be able to execute the `use case` in any context
|
|
30
|
+
|
|
31
|
+
#### Example usage
|
|
32
|
+
|
|
33
|
+
#### Init the framework
|
|
34
|
+
|
|
35
|
+
This framework provide a command bus that will provide to the developer register use cases
|
|
36
|
+
|
|
37
|
+
- `Feature` -> This component will be used to execute decoupled business logic
|
|
38
|
+
- `ApplicationService` -> This component has injected the `feature bus` and the developer will be able
|
|
39
|
+
to execute the registered `Feature`
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# Import the framework
|
|
43
|
+
from sincpro_framework import (
|
|
44
|
+
UseFramework,
|
|
45
|
+
ApplicationService,
|
|
46
|
+
DataTransferObject,
|
|
47
|
+
Feature as _Feature
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Define exceptions this is optional but is a good practice
|
|
51
|
+
class GlobalError(Exception): pass
|
|
52
|
+
class AppServiceError(Exception): pass
|
|
53
|
+
class FeatureError(Exception): pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Some dependencies as function
|
|
57
|
+
def call_print(algo):
|
|
58
|
+
print(f"Calling from closure {algo}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def global_error_handler(error):
|
|
62
|
+
"""
|
|
63
|
+
Global layer to handle an error
|
|
64
|
+
This function will catch all the exceptions that were
|
|
65
|
+
raised
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(error, GlobalError):
|
|
68
|
+
print("ERROR HANDLEADO")
|
|
69
|
+
else:
|
|
70
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def app_error_handler(error):
|
|
75
|
+
"""
|
|
76
|
+
We add a handler for `application service` errors
|
|
77
|
+
"""
|
|
78
|
+
if isinstance(error, AppServiceError):
|
|
79
|
+
print("APPLICATION HANDLER ERROR")
|
|
80
|
+
raise GlobalError("ERROR DESEDE APPLICATION")
|
|
81
|
+
else:
|
|
82
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def feat_error_handler(error):
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
if isinstance(error, FeatureError):
|
|
90
|
+
print("FEATURE HANDLER ERROR")
|
|
91
|
+
raise AppServiceError("ERROR DESEDE FEATURE")
|
|
92
|
+
else:
|
|
93
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
framework_sp = UseFramework()
|
|
97
|
+
# You can add as many dependencies as you want
|
|
98
|
+
# Also need to provide a name to the dependency, so in the context of the framework
|
|
99
|
+
# you will be able to call the injedted dependency
|
|
100
|
+
framework_sp.add_dependency("andres", call_print)
|
|
101
|
+
framework_sp.add_global_error_handler(global_error_handler)
|
|
102
|
+
framework_sp.add_app_service_error_handler(app_error_handler)
|
|
103
|
+
framework_sp.add_feature_error_handler(feat_error_handler)
|
|
104
|
+
|
|
105
|
+
# -------------------------------------------- #
|
|
106
|
+
# Buena practica en caso de querer tipar las injecciones de dependiencias
|
|
107
|
+
# se sugiere crear una clase `Feature` o `ApplicationService` que hereden de las abtracciones
|
|
108
|
+
# por ejemplo estamos importando `Feature` original pero como `_Feature` para no confundir
|
|
109
|
+
from typing import TypeAlias
|
|
110
|
+
class Feature(_Feature, ABC):
|
|
111
|
+
andres: TypeAlias = call_print
|
|
112
|
+
|
|
113
|
+
# de esta forma el IDE o editor nos ayudara a ver los tipos de las injecciones de dependencias
|
|
114
|
+
# Recordar utilizar la clase Feature de este modulo en lugar de `sincpro_framework.Feature` ya que este contiene
|
|
115
|
+
# los tipos de las injecciones de dependencias y normalmente este contiene toda la configuracion
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
once you initialize the bus, you will be able to import the framework in your application layer
|
|
120
|
+
|
|
121
|
+
this framework should be used in the `application layer`
|
|
122
|
+
|
|
123
|
+
**pattern to import the framework** follow the next snippet
|
|
124
|
+
```python
|
|
125
|
+
# Path file: src.apps.siat_api
|
|
126
|
+
# Application layer main entry: __init__.py
|
|
127
|
+
# import the initialized framework
|
|
128
|
+
from src.apps.some_application.infrastructure.sp_framework import (
|
|
129
|
+
ApplicationService,
|
|
130
|
+
DataTransferObject,
|
|
131
|
+
Feature,
|
|
132
|
+
framework_sp,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# the `use_case` represent the application layer inside of it
|
|
136
|
+
# you should find all the `Feature` and `ApplicationService`
|
|
137
|
+
from .use_case import *
|
|
138
|
+
|
|
139
|
+
# Require to export to be consumed for the infrastructure layer or the more external layer
|
|
140
|
+
__all__ = [
|
|
141
|
+
"use_case",
|
|
142
|
+
"framework_sp",
|
|
143
|
+
"Feature",
|
|
144
|
+
"DataTransferObject",
|
|
145
|
+
"ApplicationService",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# then import the framework_sp from any another consumer
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Once you have the framework imported you will be able to register the `use cases`(Features or ServiceApplication)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
|
|
157
|
+
from src.apps.some_application import framework_sp
|
|
158
|
+
|
|
159
|
+
# -------------------------------------------- #
|
|
160
|
+
# Feature
|
|
161
|
+
# -------------------------------------------- #
|
|
162
|
+
|
|
163
|
+
@dataclass(kw_only=True)
|
|
164
|
+
class CommandRegisterServiceConfiguration(DataTransferObject):
|
|
165
|
+
comm: str
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass(kw_only=True)
|
|
169
|
+
class ResponseRegisterServiceConfiguration(DataTransferObject):
|
|
170
|
+
comm: str
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@framework_sp.feature(CommandRegisterServiceConfiguration)
|
|
174
|
+
class RegisterServiceConfiguration(Feature):
|
|
175
|
+
def execute(
|
|
176
|
+
self, dto: CommandRegisterServiceConfiguration
|
|
177
|
+
) -> ResponseRegisterServiceConfiguration:
|
|
178
|
+
raise FeatureError("EError de feature")
|
|
179
|
+
return ResponseRegisterServiceConfiguration(comm=dto.comm)
|
|
180
|
+
|
|
181
|
+
# -------------------------------------------- #
|
|
182
|
+
# Application service
|
|
183
|
+
# -------------------------------------------- #
|
|
184
|
+
@dataclass(kw_only=True)
|
|
185
|
+
class CommandRegisterServiceConfiguration2(DataTransferObject):
|
|
186
|
+
comm: str
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(kw_only=True)
|
|
190
|
+
class ResponseRegisterServiceConfiguration(DataTransferObject):
|
|
191
|
+
comm: str
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@framework_sp.app_service(CommandRegisterServiceConfiguration2)
|
|
195
|
+
class RegisterServiceConfiguration(ApplicationService):
|
|
196
|
+
def execute(
|
|
197
|
+
self, dto: CommandRegisterServiceConfiguration2
|
|
198
|
+
) -> ResponseRegisterServiceConfiguration:
|
|
199
|
+
self.andres("Hello") # -> Dependency injected by the user
|
|
200
|
+
self.feature_bus.execute(CommandRegisterServiceConfiguration(comm="Hello")) #-> Decoupled feature
|
|
201
|
+
raise AppServiceError("Testing Global error")
|
|
202
|
+
return ResponseRegisterServiceConfiguration(comm=dto.comm)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
## Sincpro Framework (Application layer framework)
|
|
2
|
+
|
|
3
|
+
The main goal of this framework is create a `proxy` and `facade` component/entity, using a pattern `bus` components that wraps all the application layer, allowing to the developer import all the business logic ready to be executed in any context
|
|
4
|
+
|
|
5
|
+
### Main features:
|
|
6
|
+
- Allow to inject `dependencies` provided by the user
|
|
7
|
+
- Allow to inject `error handler` at the top level
|
|
8
|
+
- Execute the business logic in a `bus` component only providing the DTO(`Data transfer object`)
|
|
9
|
+
- Allow to execute decoupled (`use case`, `feature` , `business logic`)
|
|
10
|
+
- using the component `Feature`
|
|
11
|
+
- Allow to execute several `use cases` based on abstraction called `ApplicationService`
|
|
12
|
+
- This one contains the `feature bus` and the developer will be able to execute the `use case` in any context
|
|
13
|
+
|
|
14
|
+
#### Example usage
|
|
15
|
+
|
|
16
|
+
#### Init the framework
|
|
17
|
+
|
|
18
|
+
This framework provide a command bus that will provide to the developer register use cases
|
|
19
|
+
|
|
20
|
+
- `Feature` -> This component will be used to execute decoupled business logic
|
|
21
|
+
- `ApplicationService` -> This component has injected the `feature bus` and the developer will be able
|
|
22
|
+
to execute the registered `Feature`
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# Import the framework
|
|
26
|
+
from sincpro_framework import (
|
|
27
|
+
UseFramework,
|
|
28
|
+
ApplicationService,
|
|
29
|
+
DataTransferObject,
|
|
30
|
+
Feature as _Feature
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Define exceptions this is optional but is a good practice
|
|
34
|
+
class GlobalError(Exception): pass
|
|
35
|
+
class AppServiceError(Exception): pass
|
|
36
|
+
class FeatureError(Exception): pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Some dependencies as function
|
|
40
|
+
def call_print(algo):
|
|
41
|
+
print(f"Calling from closure {algo}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def global_error_handler(error):
|
|
45
|
+
"""
|
|
46
|
+
Global layer to handle an error
|
|
47
|
+
This function will catch all the exceptions that were
|
|
48
|
+
raised
|
|
49
|
+
"""
|
|
50
|
+
if isinstance(error, GlobalError):
|
|
51
|
+
print("ERROR HANDLEADO")
|
|
52
|
+
else:
|
|
53
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def app_error_handler(error):
|
|
58
|
+
"""
|
|
59
|
+
We add a handler for `application service` errors
|
|
60
|
+
"""
|
|
61
|
+
if isinstance(error, AppServiceError):
|
|
62
|
+
print("APPLICATION HANDLER ERROR")
|
|
63
|
+
raise GlobalError("ERROR DESEDE APPLICATION")
|
|
64
|
+
else:
|
|
65
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def feat_error_handler(error):
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
if isinstance(error, FeatureError):
|
|
73
|
+
print("FEATURE HANDLER ERROR")
|
|
74
|
+
raise AppServiceError("ERROR DESEDE FEATURE")
|
|
75
|
+
else:
|
|
76
|
+
raise Exception("ERROR NO HANDLEADO")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
framework_sp = UseFramework()
|
|
80
|
+
# You can add as many dependencies as you want
|
|
81
|
+
# Also need to provide a name to the dependency, so in the context of the framework
|
|
82
|
+
# you will be able to call the injedted dependency
|
|
83
|
+
framework_sp.add_dependency("andres", call_print)
|
|
84
|
+
framework_sp.add_global_error_handler(global_error_handler)
|
|
85
|
+
framework_sp.add_app_service_error_handler(app_error_handler)
|
|
86
|
+
framework_sp.add_feature_error_handler(feat_error_handler)
|
|
87
|
+
|
|
88
|
+
# -------------------------------------------- #
|
|
89
|
+
# Buena practica en caso de querer tipar las injecciones de dependiencias
|
|
90
|
+
# se sugiere crear una clase `Feature` o `ApplicationService` que hereden de las abtracciones
|
|
91
|
+
# por ejemplo estamos importando `Feature` original pero como `_Feature` para no confundir
|
|
92
|
+
from typing import TypeAlias
|
|
93
|
+
class Feature(_Feature, ABC):
|
|
94
|
+
andres: TypeAlias = call_print
|
|
95
|
+
|
|
96
|
+
# de esta forma el IDE o editor nos ayudara a ver los tipos de las injecciones de dependencias
|
|
97
|
+
# Recordar utilizar la clase Feature de este modulo en lugar de `sincpro_framework.Feature` ya que este contiene
|
|
98
|
+
# los tipos de las injecciones de dependencias y normalmente este contiene toda la configuracion
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
once you initialize the bus, you will be able to import the framework in your application layer
|
|
103
|
+
|
|
104
|
+
this framework should be used in the `application layer`
|
|
105
|
+
|
|
106
|
+
**pattern to import the framework** follow the next snippet
|
|
107
|
+
```python
|
|
108
|
+
# Path file: src.apps.siat_api
|
|
109
|
+
# Application layer main entry: __init__.py
|
|
110
|
+
# import the initialized framework
|
|
111
|
+
from src.apps.some_application.infrastructure.sp_framework import (
|
|
112
|
+
ApplicationService,
|
|
113
|
+
DataTransferObject,
|
|
114
|
+
Feature,
|
|
115
|
+
framework_sp,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# the `use_case` represent the application layer inside of it
|
|
119
|
+
# you should find all the `Feature` and `ApplicationService`
|
|
120
|
+
from .use_case import *
|
|
121
|
+
|
|
122
|
+
# Require to export to be consumed for the infrastructure layer or the more external layer
|
|
123
|
+
__all__ = [
|
|
124
|
+
"use_case",
|
|
125
|
+
"framework_sp",
|
|
126
|
+
"Feature",
|
|
127
|
+
"DataTransferObject",
|
|
128
|
+
"ApplicationService",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# then import the framework_sp from any another consumer
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Once you have the framework imported you will be able to register the `use cases`(Features or ServiceApplication)
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
|
|
140
|
+
from src.apps.some_application import framework_sp
|
|
141
|
+
|
|
142
|
+
# -------------------------------------------- #
|
|
143
|
+
# Feature
|
|
144
|
+
# -------------------------------------------- #
|
|
145
|
+
|
|
146
|
+
@dataclass(kw_only=True)
|
|
147
|
+
class CommandRegisterServiceConfiguration(DataTransferObject):
|
|
148
|
+
comm: str
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(kw_only=True)
|
|
152
|
+
class ResponseRegisterServiceConfiguration(DataTransferObject):
|
|
153
|
+
comm: str
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@framework_sp.feature(CommandRegisterServiceConfiguration)
|
|
157
|
+
class RegisterServiceConfiguration(Feature):
|
|
158
|
+
def execute(
|
|
159
|
+
self, dto: CommandRegisterServiceConfiguration
|
|
160
|
+
) -> ResponseRegisterServiceConfiguration:
|
|
161
|
+
raise FeatureError("EError de feature")
|
|
162
|
+
return ResponseRegisterServiceConfiguration(comm=dto.comm)
|
|
163
|
+
|
|
164
|
+
# -------------------------------------------- #
|
|
165
|
+
# Application service
|
|
166
|
+
# -------------------------------------------- #
|
|
167
|
+
@dataclass(kw_only=True)
|
|
168
|
+
class CommandRegisterServiceConfiguration2(DataTransferObject):
|
|
169
|
+
comm: str
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(kw_only=True)
|
|
173
|
+
class ResponseRegisterServiceConfiguration(DataTransferObject):
|
|
174
|
+
comm: str
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@framework_sp.app_service(CommandRegisterServiceConfiguration2)
|
|
178
|
+
class RegisterServiceConfiguration(ApplicationService):
|
|
179
|
+
def execute(
|
|
180
|
+
self, dto: CommandRegisterServiceConfiguration2
|
|
181
|
+
) -> ResponseRegisterServiceConfiguration:
|
|
182
|
+
self.andres("Hello") # -> Dependency injected by the user
|
|
183
|
+
self.feature_bus.execute(CommandRegisterServiceConfiguration(comm="Hello")) #-> Decoupled feature
|
|
184
|
+
raise AppServiceError("Testing Global error")
|
|
185
|
+
return ResponseRegisterServiceConfiguration(comm=dto.comm)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "sincpro-framework"
|
|
3
|
+
version = "1.0.1"
|
|
4
|
+
description = "Sincpro framework to use DDD, Clean architecture, Hexagonal architecture"
|
|
5
|
+
authors = ["Gutierrez Andres <andru1236@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "sincpro_framework"}]
|
|
8
|
+
license = "Copyright (c) 2023 Sincpro S.R.L"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.10"
|
|
12
|
+
logzero = "^1.7.0"
|
|
13
|
+
dependency-injector = "^4.42.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
isort = "^5.12.0"
|
|
18
|
+
black = "^24.8.0"
|
|
19
|
+
pytest = "^8.3.3"
|
|
20
|
+
ipython = "^8.28.0"
|
|
21
|
+
autoflake = "^2.3.1"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["poetry-core"]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|
|
26
|
+
|
|
27
|
+
[tool.pylint]
|
|
28
|
+
disable = [
|
|
29
|
+
"missing-docstring",
|
|
30
|
+
"fixme",
|
|
31
|
+
"C0103",
|
|
32
|
+
"C0301",
|
|
33
|
+
"R0903",
|
|
34
|
+
"C0413",
|
|
35
|
+
"W1203",
|
|
36
|
+
"I1101",
|
|
37
|
+
"W1309",
|
|
38
|
+
"R0902",
|
|
39
|
+
"C0415",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.black]
|
|
43
|
+
line-length = 94
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
3
|
+
from .exceptions import DTOAlreadyRegistered, UnknownDTOToExecute
|
|
4
|
+
from .sincpro_abstractions import ApplicationService, Bus, DataTransferObject, Feature
|
|
5
|
+
from .sincpro_logger import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FeatureBus(Bus):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.feature_registry = dict()
|
|
11
|
+
self.handle_error: Optional[Callable] = None
|
|
12
|
+
|
|
13
|
+
def register_feature(self, dto: DataTransferObject, feature: Feature) -> bool:
|
|
14
|
+
if dto.__name__ in self.feature_registry:
|
|
15
|
+
raise DTOAlreadyRegistered(
|
|
16
|
+
f"Data transfer object {dto.__name__} is already registered"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger.info("Registering feature")
|
|
20
|
+
self.feature_registry[dto.__name__] = feature
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
24
|
+
logger.info(f"Executing feature dto: [{dto.__class__.__name__}]")
|
|
25
|
+
logger.debug(f"{dto}")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
response = self.feature_registry[dto.__class__.__name__].execute(dto)
|
|
29
|
+
logger.debug(f"{response}")
|
|
30
|
+
return response
|
|
31
|
+
|
|
32
|
+
except Exception as error:
|
|
33
|
+
if self.handle_error:
|
|
34
|
+
return self.handle_error(error)
|
|
35
|
+
|
|
36
|
+
logger.error(str(error), exc_info=True)
|
|
37
|
+
raise error
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ApplicationServiceBus(Bus):
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.app_service_registry = dict()
|
|
43
|
+
self.handle_error: Optional[Callable] = None
|
|
44
|
+
|
|
45
|
+
def register_app_service(
|
|
46
|
+
self, dto: DataTransferObject, app_service: ApplicationService
|
|
47
|
+
) -> bool:
|
|
48
|
+
if dto.__name__ in self.app_service_registry:
|
|
49
|
+
raise DTOAlreadyRegistered(
|
|
50
|
+
f"Data transfer object {dto.__name__} is already registered"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
logger.info("Registering application service")
|
|
54
|
+
self.app_service_registry[dto.__name__] = app_service
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
58
|
+
logger.info(f"Executing app service dto: [{dto.__class__.__name__}]")
|
|
59
|
+
logger.debug(f"{dto}")
|
|
60
|
+
try:
|
|
61
|
+
response = self.app_service_registry[dto.__class__.__name__].execute(dto)
|
|
62
|
+
logger.debug(f"{response}")
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
except Exception as error:
|
|
66
|
+
if self.handle_error:
|
|
67
|
+
return self.handle_error(error)
|
|
68
|
+
|
|
69
|
+
logger.error(str(error), exc_info=True)
|
|
70
|
+
raise error
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------------------------
|
|
74
|
+
# Pattern Facade bus
|
|
75
|
+
# ---------------------------------------------------------------------------------------------
|
|
76
|
+
class FrameworkBus(Bus):
|
|
77
|
+
def __init__(self, feature_bus: FeatureBus, app_service_bus: ApplicationServiceBus):
|
|
78
|
+
self.feature_bus = feature_bus
|
|
79
|
+
self.app_service_bus = app_service_bus
|
|
80
|
+
self.handle_error: Optional[Callable] = None
|
|
81
|
+
|
|
82
|
+
registered_features = set(self.feature_bus.feature_registry.keys())
|
|
83
|
+
registered_app_services = set(self.app_service_bus.app_service_registry.keys())
|
|
84
|
+
logger.debug("Framework bus created")
|
|
85
|
+
logger.debug(f"Registered features: {registered_features}")
|
|
86
|
+
logger.debug(f"Registered app services: {registered_app_services}")
|
|
87
|
+
|
|
88
|
+
intersection_dtos = registered_features.intersection(registered_app_services)
|
|
89
|
+
if intersection_dtos:
|
|
90
|
+
logger.error(
|
|
91
|
+
f"Features and app services have the same name: {registered_features.intersection(registered_app_services)}"
|
|
92
|
+
)
|
|
93
|
+
raise DTOAlreadyRegistered(
|
|
94
|
+
f"Data transfer object {intersection_dtos} is present in application services and features, Change "
|
|
95
|
+
f"the name of the feature or create another framework instance to handle in doupled wat"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
99
|
+
try:
|
|
100
|
+
dto_name = dto.__class__.__name__
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
dto_name in self.app_service_bus.app_service_registry
|
|
104
|
+
and dto_name in self.feature_bus.feature_registry
|
|
105
|
+
):
|
|
106
|
+
raise DTOAlreadyRegistered(
|
|
107
|
+
f"Data transfer object {dto_name} is present in application services and features, Change the "
|
|
108
|
+
f"name of the feature or create another framework instance to handle in doupled wat"
|
|
109
|
+
)
|
|
110
|
+
if dto_name in self.feature_bus.feature_registry:
|
|
111
|
+
response = self.feature_bus.execute(dto)
|
|
112
|
+
return response
|
|
113
|
+
|
|
114
|
+
if dto_name in self.app_service_bus.app_service_registry:
|
|
115
|
+
response = self.app_service_bus.execute(dto)
|
|
116
|
+
return response
|
|
117
|
+
|
|
118
|
+
raise UnknownDTOToExecute(
|
|
119
|
+
f"the DTO {dto_name} was not able to execute nothing review if the decorators are used properly, "
|
|
120
|
+
f"otherwise the DTO {dto_name} was never register using the decorator"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
except Exception as error:
|
|
124
|
+
if self.handle_error:
|
|
125
|
+
return self.handle_error(error)
|
|
126
|
+
|
|
127
|
+
logger.error(str(error), exc_info=True)
|
|
128
|
+
raise error
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import Dict, Union
|
|
2
|
+
|
|
3
|
+
from dependency_injector import containers, providers
|
|
4
|
+
|
|
5
|
+
from .bus import ApplicationServiceBus, FeatureBus, FrameworkBus
|
|
6
|
+
from .exceptions import DTOAlreadyRegistered
|
|
7
|
+
from .sincpro_abstractions import ApplicationService, Feature
|
|
8
|
+
from .sincpro_logger import logger
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------------------------
|
|
11
|
+
# Container Definition
|
|
12
|
+
# ---------------------------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FrameworkContainer(containers.DynamicContainer):
|
|
16
|
+
injected_dependencies = providers.Dict()
|
|
17
|
+
|
|
18
|
+
# atomic layer
|
|
19
|
+
feature_registry: Dict[str, Feature] = providers.Dict({})
|
|
20
|
+
feature_bus: FeatureBus = providers.Factory(FeatureBus)
|
|
21
|
+
|
|
22
|
+
# orchestration layer
|
|
23
|
+
app_service_registry: Dict[str, ApplicationService] = providers.Dict({})
|
|
24
|
+
app_service_bus: ApplicationServiceBus = providers.Factory(
|
|
25
|
+
ApplicationServiceBus,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Facade
|
|
29
|
+
framework_bus: FrameworkBus = providers.Factory(
|
|
30
|
+
FrameworkBus, feature_bus, app_service_bus
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------------------------
|
|
35
|
+
# Build processes
|
|
36
|
+
# ---------------------------------------------------------------------------------------------
|
|
37
|
+
def inject_feature_to_bus(framework_container: FrameworkContainer, dto: Union[str, list]):
|
|
38
|
+
def inner_fn(decorated_class):
|
|
39
|
+
dtos = dto
|
|
40
|
+
if not isinstance(dto, list):
|
|
41
|
+
dtos = [dto]
|
|
42
|
+
|
|
43
|
+
for data_transfer_object in dtos:
|
|
44
|
+
if data_transfer_object.__name__ in framework_container.feature_registry.kwargs:
|
|
45
|
+
raise DTOAlreadyRegistered(
|
|
46
|
+
f"The DTO: [{data_transfer_object.__name__} from {data_transfer_object.__module__}] is already registered"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger.info(f"Registering feature: [{data_transfer_object.__name__}]")
|
|
50
|
+
|
|
51
|
+
# --------------------------------------------------------------------
|
|
52
|
+
# Register DTO to fn builder that will return the feature instance
|
|
53
|
+
framework_container.feature_registry = providers.Dict(
|
|
54
|
+
{
|
|
55
|
+
**{
|
|
56
|
+
data_transfer_object.__name__: providers.Factory(
|
|
57
|
+
decorated_class,
|
|
58
|
+
)
|
|
59
|
+
},
|
|
60
|
+
**framework_container.feature_registry.kwargs,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
framework_container.feature_bus.add_attributes(
|
|
65
|
+
feature_registry=framework_container.feature_registry
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return decorated_class
|
|
69
|
+
|
|
70
|
+
return inner_fn
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def inject_app_service_to_bus(framework_container: FrameworkContainer, dto: Union[str, list]):
|
|
74
|
+
def inner_fn(decorated_class):
|
|
75
|
+
dtos = dto
|
|
76
|
+
if not isinstance(dto, list):
|
|
77
|
+
dtos = [dto]
|
|
78
|
+
|
|
79
|
+
for data_transfer_object in dtos:
|
|
80
|
+
if (
|
|
81
|
+
data_transfer_object.__name__
|
|
82
|
+
in framework_container.app_service_registry.kwargs
|
|
83
|
+
):
|
|
84
|
+
raise DTOAlreadyRegistered(
|
|
85
|
+
f"The DTO: [{data_transfer_object.__name__} from {data_transfer_object.__module__}] is already registered"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
logger.info(f"Registering application service: [{data_transfer_object.__name__}]")
|
|
89
|
+
|
|
90
|
+
# --------------------------------------------------------------------
|
|
91
|
+
# Register DTO to fn builder that will return the service instance
|
|
92
|
+
framework_container.app_service_registry = providers.Dict(
|
|
93
|
+
{
|
|
94
|
+
**{
|
|
95
|
+
data_transfer_object.__name__: providers.Factory(
|
|
96
|
+
decorated_class, framework_container.feature_bus
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
**framework_container.app_service_registry.kwargs,
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
framework_container.app_service_bus.add_attributes(
|
|
104
|
+
app_service_registry=framework_container.app_service_registry
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return decorated_class
|
|
108
|
+
|
|
109
|
+
return inner_fn
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataTransferObject:
|
|
5
|
+
"""
|
|
6
|
+
Abstraction that represent a object that will travel through to any layer
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Bus(ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
13
|
+
"""
|
|
14
|
+
:param dto:
|
|
15
|
+
:return:
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Feature(ABC):
|
|
20
|
+
def __init__(self, *args, **kwargs):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
25
|
+
"""
|
|
26
|
+
:param dto: Any command or event
|
|
27
|
+
:return:
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ApplicationService(ABC):
|
|
32
|
+
feature_bus: Bus
|
|
33
|
+
|
|
34
|
+
def __init__(self, feature_bus: Bus, *args, **kwargs):
|
|
35
|
+
self.feature_bus = feature_bus
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def execute(self, dto: DataTransferObject) -> DataTransferObject:
|
|
39
|
+
"""
|
|
40
|
+
Main difference between Feature and ApplicationService can execute features internally
|
|
41
|
+
:param dto: Any command or event
|
|
42
|
+
:return: Any response
|
|
43
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import logzero
|
|
5
|
+
|
|
6
|
+
log_level = os.getenv("SP_FRAMEWORK_LOG_LEVEL", "DEBUG")
|
|
7
|
+
log_output_file = os.getenv("SP_FRAMEWORK_LOG_OUTPUT_FILE", "/tmp/sp_framework.log")
|
|
8
|
+
logzero.loglevel(log_level)
|
|
9
|
+
|
|
10
|
+
if log_level == "DEBUG":
|
|
11
|
+
logzero.loglevel(logging.DEBUG)
|
|
12
|
+
|
|
13
|
+
logzero.logfile(log_output_file)
|
|
14
|
+
logger = logzero.logger
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
from typing import Any, Callable, Optional
|
|
3
|
+
|
|
4
|
+
from . import ioc
|
|
5
|
+
from .bus import FrameworkBus
|
|
6
|
+
from .exceptions import DependencyAlreadyRegistered, SincproFrameworkNotBuilt
|
|
7
|
+
from .sincpro_abstractions import DataTransferObject
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UseFramework:
|
|
11
|
+
"""
|
|
12
|
+
Main class to use the framework, this is the main entry point to configure the framework
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._sp_container = ioc.FrameworkContainer()
|
|
17
|
+
# Decorators
|
|
18
|
+
self.feature = partial(ioc.inject_feature_to_bus, self._sp_container)
|
|
19
|
+
self.app_service = partial(ioc.inject_app_service_to_bus, self._sp_container)
|
|
20
|
+
|
|
21
|
+
# Registry for dynamic dep injection
|
|
22
|
+
self.dynamic_dep_registry = dict()
|
|
23
|
+
|
|
24
|
+
# Error handlers
|
|
25
|
+
self.global_error_handler: Optional[Callable] = None
|
|
26
|
+
self.feature_error_handler: Optional[Callable] = None
|
|
27
|
+
self.app_service_error_handler: Optional[Callable] = None
|
|
28
|
+
|
|
29
|
+
self.was_initialized: bool = False
|
|
30
|
+
self.bus: Optional[FrameworkBus] = None
|
|
31
|
+
|
|
32
|
+
def __call__(self, dto: DataTransferObject) -> Optional[DataTransferObject]:
|
|
33
|
+
"""
|
|
34
|
+
Main function to execute the framework
|
|
35
|
+
:param dto:
|
|
36
|
+
:return: Any response
|
|
37
|
+
"""
|
|
38
|
+
if not self.was_initialized:
|
|
39
|
+
self._add_dependencies_provided_by_user()
|
|
40
|
+
self._add_error_handlers_provided_by_user()
|
|
41
|
+
self.was_initialized = True
|
|
42
|
+
self.bus: FrameworkBus = self._sp_container.framework_bus()
|
|
43
|
+
|
|
44
|
+
if self.bus is None:
|
|
45
|
+
raise SincproFrameworkNotBuilt(
|
|
46
|
+
"Check the decorators are rigistering the features and app services, check the imports of each "
|
|
47
|
+
"feature and app service"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return self.bus.execute(dto)
|
|
51
|
+
|
|
52
|
+
def add_dependency(self, name, dep: Any):
|
|
53
|
+
if name in self.dynamic_dep_registry:
|
|
54
|
+
raise DependencyAlreadyRegistered(f"The dependency {name} is already injected")
|
|
55
|
+
self.dynamic_dep_registry[name] = dep
|
|
56
|
+
|
|
57
|
+
def add_global_error_handler(self, handler: Callable):
|
|
58
|
+
if not callable(handler):
|
|
59
|
+
raise TypeError("The handler must be a callable")
|
|
60
|
+
self.global_error_handler = handler
|
|
61
|
+
|
|
62
|
+
def add_feature_error_handler(self, handler: Callable):
|
|
63
|
+
if not callable(handler):
|
|
64
|
+
raise TypeError("The handler must be a callable")
|
|
65
|
+
self.feature_error_handler = handler
|
|
66
|
+
|
|
67
|
+
def add_app_service_error_handler(self, handler: Callable):
|
|
68
|
+
if not callable(handler):
|
|
69
|
+
raise TypeError("The handler must be a callable")
|
|
70
|
+
self.app_service_error_handler = handler
|
|
71
|
+
|
|
72
|
+
def _add_dependencies_provided_by_user(self):
|
|
73
|
+
if "feature_registry" in self._sp_container.feature_bus.attributes:
|
|
74
|
+
feature_registry = self._sp_container.feature_bus.attributes[
|
|
75
|
+
"feature_registry"
|
|
76
|
+
].kwargs
|
|
77
|
+
|
|
78
|
+
for dto, feature in feature_registry.items():
|
|
79
|
+
feature.add_attributes(**self.dynamic_dep_registry)
|
|
80
|
+
|
|
81
|
+
if "app_service_registry" in self._sp_container.app_service_bus.attributes:
|
|
82
|
+
app_service_registry = self._sp_container.app_service_bus.attributes[
|
|
83
|
+
"app_service_registry"
|
|
84
|
+
].kwargs
|
|
85
|
+
|
|
86
|
+
for dto, app_service in app_service_registry.items():
|
|
87
|
+
app_service.add_attributes(**self.dynamic_dep_registry)
|
|
88
|
+
|
|
89
|
+
def _add_error_handlers_provided_by_user(self):
|
|
90
|
+
if self.global_error_handler:
|
|
91
|
+
self._sp_container.framework_bus.add_attributes(
|
|
92
|
+
handle_error=self.global_error_handler
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if self.feature_error_handler:
|
|
96
|
+
self._sp_container.feature_bus.add_attributes(
|
|
97
|
+
handle_error=self.feature_error_handler
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if self.app_service_error_handler:
|
|
101
|
+
self._sp_container.app_service_bus.add_attributes(
|
|
102
|
+
handle_error=self.app_service_error_handler
|
|
103
|
+
)
|