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.
@@ -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,3 @@
1
+ from .sincpro_abstractions import ApplicationService, DataTransferObject, Feature
2
+ from .sincpro_logger import logger
3
+ from .use_bus import UseFramework
@@ -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,14 @@
1
+ class DTOAlreadyRegistered(Exception):
2
+ pass
3
+
4
+
5
+ class DependencyAlreadyRegistered(Exception):
6
+ pass
7
+
8
+
9
+ class UnknownDTOToExecute(Exception):
10
+ pass
11
+
12
+
13
+ class SincproFrameworkNotBuilt(Exception):
14
+ pass
@@ -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
+ )