cndi 0.1.0__py3-none-any.whl
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.
- cndi/__init__.py +4 -0
- cndi/annotations/__init__.py +416 -0
- cndi/annotations/component.py +11 -0
- cndi/annotations/events.py +79 -0
- cndi/annotations/threads.py +35 -0
- cndi/autoconfiguration/__init__.py +0 -0
- cndi/autoconfiguration/configure.py +11 -0
- cndi/binders/__init__.py +0 -0
- cndi/binders/consts.py +1 -0
- cndi/binders/message/__init__.py +152 -0
- cndi/binders/message/mqtt.py +21 -0
- cndi/binders/message/rabbitmq.py +163 -0
- cndi/binders/message/s3.py +72 -0
- cndi/binders/message/utils.py +26 -0
- cndi/consts.py +22 -0
- cndi/env.py +197 -0
- cndi/exception/__init__.py +7 -0
- cndi/flask/__init__.py +0 -0
- cndi/flask/flask_app.py +106 -0
- cndi/healthchecks.py +52 -0
- cndi/initializers.py +150 -0
- cndi/minio/__init__.py +19 -0
- cndi/rasa/__init__.py +0 -0
- cndi/rasa/configs.py +68 -0
- cndi/resources.py +36 -0
- cndi/secrets/__init__.py +0 -0
- cndi/secrets/vault.py +46 -0
- cndi/tasks/__init__.py +93 -0
- cndi/tests.py +21 -0
- cndi/utils.py +81 -0
- cndi/validation/__init__.py +19 -0
- cndi/version.py +1 -0
- cndi-0.1.0.dist-info/METADATA +53 -0
- cndi-0.1.0.dist-info/RECORD +46 -0
- cndi-0.1.0.dist-info/WHEEL +5 -0
- cndi-0.1.0.dist-info/licenses/LICENSE +339 -0
- cndi-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/cndi/__init__.py +0 -0
- tests/cndi/annotations/__init__.py +0 -0
- tests/cndi/annotations/test_app_initializer.py +14 -0
- tests/cndi/annotations/test_components.py +38 -0
- tests/cndi/binders/__init__.py +0 -0
- tests/cndi/binders/message/__init__.py +74 -0
- tests/test_load_envs.py +22 -0
- tests/test_resource_finder.py +33 -0
cndi/__init__.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides functionality for managing components and beans in the application.
|
|
3
|
+
|
|
4
|
+
It includes functionality for importing modules, normalizing module and class names, and managing various stores for beans, components, profiles, conditional rendering, and overrides.
|
|
5
|
+
|
|
6
|
+
Variables:
|
|
7
|
+
validatedBeans: A list of validated beans.
|
|
8
|
+
beans: A list of beans.
|
|
9
|
+
autowires: A list of autowires.
|
|
10
|
+
components: A list of components.
|
|
11
|
+
beanStore: A dictionary storing beans.
|
|
12
|
+
componentStore: A dictionary storing components.
|
|
13
|
+
profilesStores: A dictionary storing profiles.
|
|
14
|
+
conditionalRender: A dictionary storing conditional rendering settings.
|
|
15
|
+
overrideStore: A dictionary storing overrides.
|
|
16
|
+
|
|
17
|
+
Functions:
|
|
18
|
+
importModuleName: Imports a module given its full name.
|
|
19
|
+
normaliseModuleAndClassName: Normalizes a module and class name.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import types
|
|
25
|
+
from typing import Callable, Any
|
|
26
|
+
|
|
27
|
+
from cndi.annotations.component import ComponentClass
|
|
28
|
+
from cndi.env import RCN_ACTIVE_PROFILE
|
|
29
|
+
from cndi.exception import BeanNotFoundException
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("cndi.annotations")
|
|
32
|
+
|
|
33
|
+
class SingletonContext:
|
|
34
|
+
"""Singleton context manager for storing beans, components, and related data."""
|
|
35
|
+
_instance = None
|
|
36
|
+
_frozen = False
|
|
37
|
+
|
|
38
|
+
def __new__(cls):
|
|
39
|
+
if cls._instance is None:
|
|
40
|
+
cls._instance = super(SingletonContext, cls).__new__(cls)
|
|
41
|
+
cls._instance.validatedBeans = []
|
|
42
|
+
cls._instance.beans = []
|
|
43
|
+
cls._instance.autowires = []
|
|
44
|
+
cls._instance.components = []
|
|
45
|
+
cls._instance.beanStore = {}
|
|
46
|
+
cls._instance.componentStore = {}
|
|
47
|
+
cls._instance.profilesStores = {}
|
|
48
|
+
cls._instance.conditionalRender = {}
|
|
49
|
+
cls._instance.overrideStore = {}
|
|
50
|
+
return cls._instance
|
|
51
|
+
|
|
52
|
+
def freeze(self):
|
|
53
|
+
"""Freeze the context to make all stored values immutable."""
|
|
54
|
+
self.validatedBeans = tuple(self.validatedBeans)
|
|
55
|
+
self.beans = tuple(self.beans)
|
|
56
|
+
self.autowires = tuple(self.autowires)
|
|
57
|
+
self.components = tuple(self.components)
|
|
58
|
+
self.beanStore = types.MappingProxyType(self.beanStore)
|
|
59
|
+
self.componentStore = types.MappingProxyType(self.componentStore)
|
|
60
|
+
self.profilesStores = types.MappingProxyType(self.profilesStores)
|
|
61
|
+
self.conditionalRender = types.MappingProxyType(self.conditionalRender)
|
|
62
|
+
self.overrideStore = types.MappingProxyType(self.overrideStore)
|
|
63
|
+
SingletonContext._frozen = True
|
|
64
|
+
|
|
65
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
66
|
+
if SingletonContext._frozen:
|
|
67
|
+
raise AttributeError(f"Cannot modify frozen context attribute: {name}")
|
|
68
|
+
super().__setattr__(name, value)
|
|
69
|
+
|
|
70
|
+
context = SingletonContext()
|
|
71
|
+
|
|
72
|
+
from functools import wraps
|
|
73
|
+
import importlib
|
|
74
|
+
import copy
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def importModuleName(fullname):
|
|
78
|
+
modules = fullname.split('.')
|
|
79
|
+
module = importlib.import_module(modules[-1], package='.'.join(modules[:-1]))
|
|
80
|
+
return module
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def normaliseModuleAndClassName(name):
|
|
84
|
+
nameList: list = name.split(".")
|
|
85
|
+
if "__init__" in nameList:
|
|
86
|
+
nameList.remove("__init__")
|
|
87
|
+
return '.'.join(nameList)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def getBeanObject(objectType):
|
|
91
|
+
"""
|
|
92
|
+
Retrieves a bean object from the bean storage.
|
|
93
|
+
|
|
94
|
+
This function queries the bean storage for a bean of the specified type. If the bean is found, it returns a new instance of the bean if the 'newInstance' attribute of the bean is True, otherwise it returns the existing instance.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
objectType: The type of the bean to retrieve.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
An instance of the bean of the specified type.
|
|
101
|
+
"""
|
|
102
|
+
if isinstance(objectType, type):
|
|
103
|
+
objectType = normaliseModuleAndClassName('.'.join([objectType.__module__, objectType.__name__]))
|
|
104
|
+
bean = queryBeanStorage(objectType)
|
|
105
|
+
objectInstance = bean['objectInstance']
|
|
106
|
+
return copy.deepcopy(objectInstance) if bean['newInstance'] else objectInstance
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def queryBeanStorage(fullname):
|
|
110
|
+
objectType = normaliseModuleAndClassName(fullname)
|
|
111
|
+
bean = context.beanStore[objectType]
|
|
112
|
+
return bean
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AutowiredClass:
|
|
116
|
+
def __init__(self, required, func, kwargs: dict):
|
|
117
|
+
self.fullname = '.'.join([func.__qualname__])
|
|
118
|
+
self.className = normaliseModuleAndClassName('.'.join(func.__qualname__.split(".")[:-1]))
|
|
119
|
+
self.func = func
|
|
120
|
+
self.kwargs = kwargs
|
|
121
|
+
self.required = required
|
|
122
|
+
self.context = SingletonContext()
|
|
123
|
+
|
|
124
|
+
def dependencyInject(self):
|
|
125
|
+
dependencies = self.calculateDependencies()
|
|
126
|
+
dependencyNotFound = list()
|
|
127
|
+
for dependency in dependencies:
|
|
128
|
+
if dependency not in self.context.beanStore:
|
|
129
|
+
dependencyNotFound.append(dependency)
|
|
130
|
+
|
|
131
|
+
if len(dependencyNotFound) > 0:
|
|
132
|
+
logger.warning(f"Skipping {self.fullname}")
|
|
133
|
+
assert not self.required, "Could not initialize " + self.fullname + " with beans " + str(
|
|
134
|
+
dependencyNotFound)
|
|
135
|
+
|
|
136
|
+
kwargs = self.kwargs
|
|
137
|
+
args = dict()
|
|
138
|
+
for (key, value) in kwargs.items():
|
|
139
|
+
fullName = normaliseModuleAndClassName('.'.join([value.__module__, value.__name__]))
|
|
140
|
+
if fullName in self.context.beanStore:
|
|
141
|
+
args[key] = getBeanObject(fullName)
|
|
142
|
+
|
|
143
|
+
if self.className in self.context.beanStore:
|
|
144
|
+
self.func(self.context.beanStore[self.className], **args)
|
|
145
|
+
else:
|
|
146
|
+
logger.debug(f"{self.className} {self.fullname}")
|
|
147
|
+
self.func(**args)
|
|
148
|
+
|
|
149
|
+
def calculateDependencies(self):
|
|
150
|
+
return list(
|
|
151
|
+
map(lambda dependency: normaliseModuleAndClassName('.'.join([dependency.__module__, dependency.__name__])),
|
|
152
|
+
self.kwargs.values()))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def OverrideBeanType(type: object):
|
|
156
|
+
"""
|
|
157
|
+
OverrideBeanType is used to override the current annotated @Component class to be injected as some other object
|
|
158
|
+
|
|
159
|
+
:param type: Class type to override while performing Dependency Injection
|
|
160
|
+
:return: function wrapper
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def inner_function(func):
|
|
164
|
+
@wraps(func)
|
|
165
|
+
def wrapper(*args, **kwargs):
|
|
166
|
+
return func(*args, **kwargs)
|
|
167
|
+
|
|
168
|
+
fullname = '.'.join([wrapper.__module__, wrapper.__name__])
|
|
169
|
+
|
|
170
|
+
context.overrideStore[fullname] = {
|
|
171
|
+
"func": wrapper,
|
|
172
|
+
"overrideType": type
|
|
173
|
+
}
|
|
174
|
+
return wrapper
|
|
175
|
+
|
|
176
|
+
return inner_function
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def queryOverideBeanStore(fullname):
|
|
180
|
+
if fullname in context.overrideStore:
|
|
181
|
+
return context.overrideStore[fullname]
|
|
182
|
+
else:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def Component(func: object):
|
|
187
|
+
"""
|
|
188
|
+
A decorator that registers a class as a component.
|
|
189
|
+
|
|
190
|
+
When a class is decorated with @Component, the AppInitializer tries to automatically initialize the class. The class is registered with its full name, which is constructed from the module name and the class name.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
func: The class to be registered as a component.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
The decorated class.
|
|
197
|
+
"""
|
|
198
|
+
@wraps(func)
|
|
199
|
+
def wrapper(*args, **kwargs):
|
|
200
|
+
return func(*args, **kwargs)
|
|
201
|
+
|
|
202
|
+
moduleName = wrapper.__module__[:-9] if wrapper.__module__.endswith(".__init__") else wrapper.__module__
|
|
203
|
+
componentFullName = '.'.join([moduleName, wrapper.__qualname__])
|
|
204
|
+
logger.debug(f"Registering Function name " + componentFullName)
|
|
205
|
+
duplicateComponents = list(filter(lambda component: component.fullname == componentFullName, context.components))
|
|
206
|
+
if duplicateComponents.__len__() > 0:
|
|
207
|
+
logger.debug(f"Duplicate Component found for: {duplicateComponents}")
|
|
208
|
+
else:
|
|
209
|
+
context.components.append(ComponentClass(**{
|
|
210
|
+
'fullname': componentFullName,
|
|
211
|
+
'func': wrapper,
|
|
212
|
+
'annotations': wrapper.__init__.__annotations__ if "__annotations__" in dir(wrapper.__init__) else {}
|
|
213
|
+
}))
|
|
214
|
+
return wrapper
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def validateBean(fullname):
|
|
218
|
+
"""
|
|
219
|
+
Validates Beans before performing the dependency injection
|
|
220
|
+
:param fullname: bean class full classpath name
|
|
221
|
+
:return: Boolean type
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
profile = queryProfileData(fullname)
|
|
225
|
+
condition = queryContitionalRenderingStore(fullname)
|
|
226
|
+
if profile is None and condition is None:
|
|
227
|
+
return True
|
|
228
|
+
flag = True
|
|
229
|
+
|
|
230
|
+
if profile is not None:
|
|
231
|
+
profileNames = set(profile['profiles'])
|
|
232
|
+
environmentProfiles = set(map(lambda x: x.strip(), os.environ[RCN_ACTIVE_PROFILE].split(",")))
|
|
233
|
+
intersectionProfiles = profileNames.intersection(environmentProfiles)
|
|
234
|
+
|
|
235
|
+
flag &= intersectionProfiles.__len__() >= 1
|
|
236
|
+
|
|
237
|
+
if condition is not None:
|
|
238
|
+
callback = condition['callback']
|
|
239
|
+
callbackValue = callback(condition['func'])
|
|
240
|
+
flag &= bool(callbackValue)
|
|
241
|
+
|
|
242
|
+
if flag is False:
|
|
243
|
+
logger.debug("Validation Failed for Bean " + fullname)
|
|
244
|
+
|
|
245
|
+
return flag
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def Bean(newInstance=False):
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
:param newInstance:
|
|
252
|
+
:return:
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def inner_function(func):
|
|
256
|
+
annotations = func.__annotations__
|
|
257
|
+
returnType = annotations['return']
|
|
258
|
+
del annotations['return']
|
|
259
|
+
|
|
260
|
+
@wraps(func)
|
|
261
|
+
def wrapper(*args, **kwargs):
|
|
262
|
+
return func(*args, **kwargs)
|
|
263
|
+
|
|
264
|
+
fullname = '.'.join([returnType.__module__, returnType.__name__])
|
|
265
|
+
annotations = dict(
|
|
266
|
+
map(lambda key: (key, '.'.join([annotations[key].__module__, annotations[key].__qualname__])), annotations))
|
|
267
|
+
|
|
268
|
+
if validateBean(fullname):
|
|
269
|
+
context.beans.append({
|
|
270
|
+
'name': fullname,
|
|
271
|
+
'newInstance': newInstance,
|
|
272
|
+
'object': wrapper,
|
|
273
|
+
'fullname': wrapper.__qualname__,
|
|
274
|
+
'kwargs': annotations,
|
|
275
|
+
'index': len(context.beans)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return wrapper
|
|
279
|
+
else:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
return inner_function
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def ConditionalRendering(callback=lambda method: True, overrideFullName = None):
|
|
286
|
+
"""
|
|
287
|
+
A decorator that conditionally renders a class based on a callback function.
|
|
288
|
+
|
|
289
|
+
This decorator checks the return value of the provided callback function. If the callback returns True, the class is rendered. If the callback returns False, the class is not rendered.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
callback: A function that determines whether the class should be rendered. This function should take no arguments and return a boolean.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The decorated class, if the callback returns True. None, if the callback returns False.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def inner_function(func: object):
|
|
299
|
+
@wraps(func)
|
|
300
|
+
def wrapper(*args, **kwargs):
|
|
301
|
+
return func(*args, **kwargs)
|
|
302
|
+
|
|
303
|
+
fullname = ".".join([wrapper.__module__, wrapper.__qualname__]) if overrideFullName is None else overrideFullName
|
|
304
|
+
|
|
305
|
+
context.conditionalRender[fullname] = {
|
|
306
|
+
"func": wrapper,
|
|
307
|
+
"callback": callback
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return wrapper
|
|
311
|
+
|
|
312
|
+
return inner_function
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def queryContitionalRenderingStore(fullname):
|
|
316
|
+
if fullname in context.conditionalRender:
|
|
317
|
+
return context.conditionalRender[fullname]
|
|
318
|
+
else:
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
def constructKeyWordArguments(annotations, required=True):
|
|
322
|
+
kwargs = dict()
|
|
323
|
+
for key, classObject in annotations.items():
|
|
324
|
+
beanName = f"{classObject.__module__}.{classObject.__name__}"
|
|
325
|
+
if beanName in context.beanStore:
|
|
326
|
+
kwargs[key] = getBeanObject(beanName)
|
|
327
|
+
elif required:
|
|
328
|
+
raise BeanNotFoundException(f"Following Bean failed to load in SingletonContext: "+beanName)
|
|
329
|
+
else:
|
|
330
|
+
logger.warn(f"Bean not found {beanName} and required is set to false")
|
|
331
|
+
return kwargs
|
|
332
|
+
|
|
333
|
+
def Profile(profiles=["default"]):
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
:param profiles:
|
|
337
|
+
:return:
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
def inner_function(func: Callable):
|
|
341
|
+
@wraps(func)
|
|
342
|
+
def wrapper(*args, **kwargs):
|
|
343
|
+
return func(*args, **kwargs)
|
|
344
|
+
|
|
345
|
+
fullname = ".".join([wrapper.__module__, wrapper.__qualname__])
|
|
346
|
+
context.profilesStores[fullname] = {
|
|
347
|
+
"func": wrapper,
|
|
348
|
+
"profiles": profiles
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return wrapper
|
|
352
|
+
|
|
353
|
+
return inner_function
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def queryProfileData(fullname):
|
|
357
|
+
if fullname in context.profilesStores:
|
|
358
|
+
return context.profilesStores.get(fullname)
|
|
359
|
+
else:
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def Autowired(required=True):
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
:param required:
|
|
367
|
+
:return:
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def inner_function(func: object):
|
|
371
|
+
annotations = func.__annotations__
|
|
372
|
+
|
|
373
|
+
@wraps(func)
|
|
374
|
+
def wrapper(*args, **kwargs):
|
|
375
|
+
return func(*args, **kwargs)
|
|
376
|
+
|
|
377
|
+
context.autowires.append(AutowiredClass(required=required, **{
|
|
378
|
+
'kwargs': annotations,
|
|
379
|
+
'func': wrapper
|
|
380
|
+
}))
|
|
381
|
+
|
|
382
|
+
return wrapper
|
|
383
|
+
|
|
384
|
+
return inner_function
|
|
385
|
+
|
|
386
|
+
def getBean(beans, name):
|
|
387
|
+
return list(filter(lambda x: x['name'] == name, beans))[0]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def workOrder(beans):
|
|
391
|
+
allBeanNames = list(map(lambda bean: bean['name'], beans))
|
|
392
|
+
beanQueue = list(filter(lambda bean: len(bean['kwargs']) == 0, beans))
|
|
393
|
+
beanIndexes = list(map(lambda bean: bean['index'], beanQueue))
|
|
394
|
+
|
|
395
|
+
beanDependents = list(filter(lambda bean: bean['index'] not in beanIndexes, beans))
|
|
396
|
+
beanQueueNames = list(map(lambda bean: bean['name'], beanQueue))
|
|
397
|
+
|
|
398
|
+
for i in range(len(beanQueue)):
|
|
399
|
+
beanQueue[i]['index'] = i
|
|
400
|
+
|
|
401
|
+
for dependents in beanDependents:
|
|
402
|
+
args = list(dependents['kwargs'].values())
|
|
403
|
+
flag = True
|
|
404
|
+
for argClassName in args:
|
|
405
|
+
if (argClassName not in beanQueueNames and argClassName in allBeanNames) or argClassName in beanQueueNames:
|
|
406
|
+
flag = flag and True
|
|
407
|
+
dependents['index'] = getBean(beans, argClassName)['index'] + max(beanIndexes)
|
|
408
|
+
else:
|
|
409
|
+
flag = False
|
|
410
|
+
|
|
411
|
+
if flag:
|
|
412
|
+
beanQueue.append(dependents)
|
|
413
|
+
beanQueueNames.append(dependents['name'])
|
|
414
|
+
|
|
415
|
+
assert len(beanQueue) == len(beans), "Somebeans were not initialized properly"
|
|
416
|
+
return list(sorted(beanQueue, key=lambda x: x['index']))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class ComponentClass:
|
|
2
|
+
def __init__(self, fullname, func, annotations):
|
|
3
|
+
self.fullname = fullname
|
|
4
|
+
self.func = func
|
|
5
|
+
self.annotations = annotations
|
|
6
|
+
|
|
7
|
+
def getInnerAutowiredClasses(self, autowires):
|
|
8
|
+
return list(filter(
|
|
9
|
+
lambda autowire: autowire.className == self.fullname,
|
|
10
|
+
autowires
|
|
11
|
+
))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import logging
|
|
4
|
+
from concurrent.futures.thread import ThreadPoolExecutor
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import Dict, Callable
|
|
7
|
+
|
|
8
|
+
from cndi.annotations import Component, constructKeyWordArguments, ConditionalRendering
|
|
9
|
+
from cndi.consts import RCN_EVENTS_ENABLE, RCN_EVENTS_NUM_THREADS
|
|
10
|
+
from cndi.env import getContextEnvironment
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class BuiltInEventsTypes:
|
|
15
|
+
ON_ENV_LOAD="on_env_load"
|
|
16
|
+
ON_CONTEXT_LOAD="on_context_load"
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass
|
|
19
|
+
class Event(object):
|
|
20
|
+
def __init__(self, eventType,
|
|
21
|
+
eventCallback, kwargs=None):
|
|
22
|
+
if kwargs is None:
|
|
23
|
+
kwargs = {}
|
|
24
|
+
self.eventType = eventType
|
|
25
|
+
self.eventCallback: Callable = eventCallback
|
|
26
|
+
self.kwargs = kwargs
|
|
27
|
+
|
|
28
|
+
REGISTERED_EVENTS: Dict[str, dict[str, Event]] = dict()
|
|
29
|
+
def register_event(event: Event, func_name: str):
|
|
30
|
+
if event.eventType not in REGISTERED_EVENTS:
|
|
31
|
+
REGISTERED_EVENTS[event.eventType] = dict()
|
|
32
|
+
|
|
33
|
+
REGISTERED_EVENTS[event.eventType][func_name] = event
|
|
34
|
+
|
|
35
|
+
def OnEvent(event):
|
|
36
|
+
def inner_function(func):
|
|
37
|
+
annotations = func.__annotations__
|
|
38
|
+
|
|
39
|
+
func_name = '.'.join([func.__module__, func.__qualname__])
|
|
40
|
+
@wraps(func)
|
|
41
|
+
def wrapper(*args, **kwargs):
|
|
42
|
+
if asyncio.iscoroutinefunction(func):
|
|
43
|
+
return asyncio.run(func(*args, **kwargs))
|
|
44
|
+
return func(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
register_event(Event(eventType=event, eventCallback=wrapper, kwargs=annotations), func_name)
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
return inner_function
|
|
50
|
+
|
|
51
|
+
class EventNotFound(Exception):
|
|
52
|
+
def __init__(self, *args):
|
|
53
|
+
super().__init__( *args)
|
|
54
|
+
|
|
55
|
+
@Component
|
|
56
|
+
@ConditionalRendering(callback=lambda x: getContextEnvironment(RCN_EVENTS_ENABLE, defaultValue=False, castFunc=bool)
|
|
57
|
+
and getContextEnvironment(RCN_EVENTS_NUM_THREADS, defaultValue=1, castFunc=int) > 0)
|
|
58
|
+
class EventBus:
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self._executor = ThreadPoolExecutor(max_workers=getContextEnvironment(RCN_EVENTS_NUM_THREADS, defaultValue=1, castFunc=int))
|
|
61
|
+
|
|
62
|
+
def publish(self, event_name: str, data=None) -> None:
|
|
63
|
+
if data is None:
|
|
64
|
+
data = {}
|
|
65
|
+
if event_name in REGISTERED_EVENTS:
|
|
66
|
+
for func_name, event in REGISTERED_EVENTS[event_name].items():
|
|
67
|
+
kwargs = {
|
|
68
|
+
**constructKeyWordArguments(event.kwargs, required=False),
|
|
69
|
+
**data
|
|
70
|
+
}
|
|
71
|
+
kwargs = dict(map(lambda x: [x, kwargs[x]], set(event.kwargs.keys()).intersection(kwargs.keys())))
|
|
72
|
+
self._executor.submit(event.eventCallback, **kwargs)
|
|
73
|
+
else:
|
|
74
|
+
logger.warning(f"{event_name} event not found, please check the decorators")
|
|
75
|
+
|
|
76
|
+
def subscribe(self, event_name: str, event: Event) -> None:
|
|
77
|
+
if event_name not in REGISTERED_EVENTS:
|
|
78
|
+
REGISTERED_EVENTS[event_name] = dict()
|
|
79
|
+
REGISTERED_EVENTS[event_name][event.eventType] = event
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Thread
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from cndi.annotations import Component, ConditionalRendering
|
|
6
|
+
from cndi.consts import RCN_ENABLE_CONTEXT_THREADS
|
|
7
|
+
from cndi.env import getContextEnvironment
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
def isContextThreadEnable(dependent):
|
|
12
|
+
enabled = getContextEnvironment(RCN_ENABLE_CONTEXT_THREADS, defaultValue=False, castFunc=bool)
|
|
13
|
+
if not enabled:
|
|
14
|
+
log.warning(f"{dependent} Component depends on {__name__}.{ContextThreads.__name__}")
|
|
15
|
+
log.warning(f"SingletonContext Threads is disable in property please enable it by setting {RCN_ENABLE_CONTEXT_THREADS} to true")
|
|
16
|
+
|
|
17
|
+
return enabled
|
|
18
|
+
|
|
19
|
+
@Component
|
|
20
|
+
@ConditionalRendering(callback=lambda x: getContextEnvironment(RCN_ENABLE_CONTEXT_THREADS, defaultValue=False, castFunc=bool))
|
|
21
|
+
class ContextThreads:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.threads: List[Thread] = list()
|
|
24
|
+
|
|
25
|
+
def add_thread(self, thread):
|
|
26
|
+
self.threads.append(thread)
|
|
27
|
+
|
|
28
|
+
def clean_up(self):
|
|
29
|
+
exitedThread = []
|
|
30
|
+
for thread in self.threads:
|
|
31
|
+
if not thread.is_alive():
|
|
32
|
+
exitedThread.append(thread)
|
|
33
|
+
|
|
34
|
+
for exitThread in exitedThread:
|
|
35
|
+
self.threads.remove(exitThread)
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from cndi.annotations import Component, ConditionalRendering
|
|
2
|
+
from cndi.consts import RCN_AUTO_CONFIGURE_SECRETS_ENABLE
|
|
3
|
+
from cndi.env import getContextEnvironment
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Component
|
|
7
|
+
@ConditionalRendering(callback=lambda x: getContextEnvironment(RCN_AUTO_CONFIGURE_SECRETS_ENABLE, defaultValue=False, castFunc=bool))
|
|
8
|
+
class AutoConfigurationProviders:
|
|
9
|
+
_PROVIDERS = dict()
|
|
10
|
+
def __init__(self):
|
|
11
|
+
pass
|
cndi/binders/__init__.py
ADDED
|
File without changes
|
cndi/binders/consts.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
RCN_MESSAGE_BINDERS="rcn.binders.message"
|