tiferet 1.0.0a0__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.
- app/__init__.py +0 -0
- app/clients/__init__.py +0 -0
- app/clients/yaml.py +93 -0
- app/commands/__init__.py +0 -0
- app/commands/container.py +54 -0
- app/commands/error.py +21 -0
- app/commands/feature.py +90 -0
- app/configs/__init__.py +69 -0
- app/configs/app.py +16 -0
- app/configs/container.py +91 -0
- app/contexts/__init__.py +2 -0
- app/contexts/app.py +130 -0
- app/contexts/container.py +167 -0
- app/contexts/env.py +109 -0
- app/contexts/error.py +95 -0
- app/contexts/feature.py +79 -0
- app/contexts/request.py +108 -0
- app/data/__init__.py +4 -0
- app/data/app.py +227 -0
- app/data/container.py +179 -0
- app/data/error.py +99 -0
- app/data/feature.py +132 -0
- app/domain/__init__.py +5 -0
- app/domain/app.py +330 -0
- app/domain/container.py +141 -0
- app/domain/core.py +199 -0
- app/domain/error.py +136 -0
- app/domain/feature.py +176 -0
- app/repos/__init__.py +0 -0
- app/repos/app.py +102 -0
- app/repos/container.py +164 -0
- app/repos/error.py +173 -0
- app/repos/feature.py +169 -0
- app/services/__init__.py +4 -0
- app/services/cli.py +186 -0
- app/services/container.py +44 -0
- tiferet-1.0.0a0.dist-info/LICENSE +28 -0
- tiferet-1.0.0a0.dist-info/METADATA +13 -0
- tiferet-1.0.0a0.dist-info/RECORD +41 -0
- tiferet-1.0.0a0.dist-info/WHEEL +5 -0
- tiferet-1.0.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
# *** imports
|
2
|
+
|
3
|
+
# ** core
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
# ** app
|
7
|
+
from ..configs import container
|
8
|
+
from ..domain import *
|
9
|
+
from ..services import container_service
|
10
|
+
from ..repos.container import ContainerRepository
|
11
|
+
|
12
|
+
|
13
|
+
# *** contexts
|
14
|
+
|
15
|
+
# ** contexts: container_context
|
16
|
+
class ContainerContext(Model):
|
17
|
+
'''
|
18
|
+
A container context is a class that is used to create a container object.
|
19
|
+
'''
|
20
|
+
|
21
|
+
# * attribute: attributes
|
22
|
+
attributes = DictType(
|
23
|
+
ModelType(ContainerAttribute),
|
24
|
+
default={},
|
25
|
+
required=True,
|
26
|
+
metadata=dict(
|
27
|
+
description='The container attributes.'
|
28
|
+
),
|
29
|
+
)
|
30
|
+
|
31
|
+
# * attribute: constants
|
32
|
+
constants = DictType(
|
33
|
+
StringType,
|
34
|
+
default={},
|
35
|
+
metadata=dict(
|
36
|
+
description='The container constants.'
|
37
|
+
),
|
38
|
+
)
|
39
|
+
|
40
|
+
# * attribute: feature_flag
|
41
|
+
feature_flag = StringType(
|
42
|
+
required=True,
|
43
|
+
default='core',
|
44
|
+
metadata=dict(
|
45
|
+
description='The feature flag.'
|
46
|
+
),
|
47
|
+
)
|
48
|
+
|
49
|
+
# * attribute: data_flag
|
50
|
+
data_flag = StringType(
|
51
|
+
required=True,
|
52
|
+
metadata=dict(
|
53
|
+
description='The data flag.'
|
54
|
+
),
|
55
|
+
)
|
56
|
+
|
57
|
+
# * method: init
|
58
|
+
def __init__(self, interface_id: str, container_repo: ContainerRepository, feature_flag: str, data_flag: str):
|
59
|
+
'''
|
60
|
+
Initialize the container context.
|
61
|
+
|
62
|
+
:param interface_id: The interface ID.
|
63
|
+
:type interface_id: str
|
64
|
+
:param container_repo: The container repository.
|
65
|
+
:type container_repo: ContainerRepository
|
66
|
+
:param interface_flag: The interface flag.
|
67
|
+
:type interface_flag: str
|
68
|
+
:param feature_flag: The feature flag.
|
69
|
+
:type feature_flag: str
|
70
|
+
:param data_flag: The data flag.
|
71
|
+
:type data_flag: str
|
72
|
+
'''
|
73
|
+
|
74
|
+
# Then set the interface id and injector flags.
|
75
|
+
self.interface_id = interface_id
|
76
|
+
self.feature_flag = feature_flag
|
77
|
+
self.data_flag = data_flag
|
78
|
+
|
79
|
+
# Add the default container attributes first if any.
|
80
|
+
self.attributes = vars(container)
|
81
|
+
|
82
|
+
# Get and set attributes and constants.
|
83
|
+
attrs, consts = container_repo.list_all()
|
84
|
+
|
85
|
+
# Add the attributes to the context.
|
86
|
+
for attr in attrs:
|
87
|
+
attribute: ContainerAttribute = self.attributes[attr.id]
|
88
|
+
|
89
|
+
# If the attribute already exists, set the dependencies.
|
90
|
+
if attr.id in self.attributes:
|
91
|
+
for dep in attr.dependencies:
|
92
|
+
attribute.set_dependency(dep)
|
93
|
+
continue
|
94
|
+
|
95
|
+
# Otherwise, add the attribute.
|
96
|
+
self.attributes[attr.id] = attr
|
97
|
+
|
98
|
+
# Add the constants to the context.
|
99
|
+
self.constants = consts
|
100
|
+
|
101
|
+
# * method: get_dependency
|
102
|
+
def get_dependency(self, attribute_id: str):
|
103
|
+
'''
|
104
|
+
Get a dependency from the container.
|
105
|
+
|
106
|
+
:param attribute_id: The attribute id of the dependency.
|
107
|
+
:type attribute_id: str
|
108
|
+
:return: The attribute value.
|
109
|
+
:rtype: Any
|
110
|
+
'''
|
111
|
+
|
112
|
+
# Create the injector.
|
113
|
+
injector = self.create_injector()
|
114
|
+
|
115
|
+
# Get attribute.
|
116
|
+
return getattr(injector, attribute_id)
|
117
|
+
|
118
|
+
# * method: create_injector
|
119
|
+
def create_injector(self, **kwargs) -> Any:
|
120
|
+
'''
|
121
|
+
Add a container to the context.
|
122
|
+
|
123
|
+
:param kwargs: Additional keyword arguments.
|
124
|
+
:type kwargs: dict
|
125
|
+
:return: The container injector object.
|
126
|
+
:rtype: Any
|
127
|
+
'''
|
128
|
+
|
129
|
+
# Import dependencies.
|
130
|
+
dependencies = {}
|
131
|
+
for attribute_id in self.attributes:
|
132
|
+
attribute = self.attributes[attribute_id]
|
133
|
+
flag_map = dict(
|
134
|
+
feature=self.feature_flag,
|
135
|
+
data=self.data_flag,
|
136
|
+
)
|
137
|
+
dependencies[attribute_id] = self.import_dependency(attribute, flag_map[attribute.type])
|
138
|
+
|
139
|
+
# Create container.
|
140
|
+
return container_service.create_injector(self,
|
141
|
+
self.interface_id,
|
142
|
+
**self.constants,
|
143
|
+
**dependencies,
|
144
|
+
**kwargs)
|
145
|
+
|
146
|
+
# * method: import_dependency
|
147
|
+
def import_dependency(self, attribute: ContainerAttribute, flag: str) -> Any:
|
148
|
+
'''
|
149
|
+
Import a container attribute dependency from its configured Python module.
|
150
|
+
|
151
|
+
:param attribute: The container attribute.
|
152
|
+
:type attribute: ContainerAttribute
|
153
|
+
:param flag: The flag for the dependency.
|
154
|
+
:type flag: str
|
155
|
+
:return: The dependency.
|
156
|
+
:rtype: Any
|
157
|
+
'''
|
158
|
+
|
159
|
+
# Get the dependency.
|
160
|
+
dependency = attribute.get_dependency(flag)
|
161
|
+
|
162
|
+
# If there is no dependency and the attribute is a feature, import the default feature.
|
163
|
+
if not dependency and attribute.type == 'feature':
|
164
|
+
dependency = attribute.get_dependency('core')
|
165
|
+
|
166
|
+
# Import the dependency.
|
167
|
+
return container_service.import_dependency(dependency.module_path, dependency.class_name)
|
app/contexts/env.py
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# *** imports
|
2
|
+
|
3
|
+
# ** infra
|
4
|
+
from schematics import Model
|
5
|
+
|
6
|
+
# ** app
|
7
|
+
from .app import AppInterfaceContext
|
8
|
+
from ..services import container_service
|
9
|
+
from ..domain import *
|
10
|
+
from ..repos.app import AppRepository
|
11
|
+
|
12
|
+
|
13
|
+
# *** contexts
|
14
|
+
|
15
|
+
# ** context: environment_context
|
16
|
+
class EnvironmentContext(Model):
|
17
|
+
'''
|
18
|
+
An environment context is a class that is used to create and run the app interface context.
|
19
|
+
'''
|
20
|
+
|
21
|
+
# * attribute: interfaces
|
22
|
+
interfaces = DictType(
|
23
|
+
ModelType(AppInterface),
|
24
|
+
default={},
|
25
|
+
metadata=dict(
|
26
|
+
description='The app interfaces keyed by interface ID.'
|
27
|
+
),
|
28
|
+
)
|
29
|
+
|
30
|
+
# * method: init
|
31
|
+
def __init__(self, **kwargs):
|
32
|
+
'''
|
33
|
+
Initialize the environment context.
|
34
|
+
|
35
|
+
:param kwargs: Additional keyword arguments.
|
36
|
+
:type kwargs: dict
|
37
|
+
'''
|
38
|
+
|
39
|
+
# Load the app repository.
|
40
|
+
app_repo = self.load_app_repo()
|
41
|
+
|
42
|
+
# Load the interface configuration.
|
43
|
+
self.interfaces = {interface.id: interface for interface in app_repo.list_interfaces()}
|
44
|
+
|
45
|
+
# * method: start
|
46
|
+
def start(self, interface_id: str, **kwargs):
|
47
|
+
'''
|
48
|
+
Start the environment context.
|
49
|
+
|
50
|
+
:param kwargs: Additional keyword arguments.
|
51
|
+
:type kwargs: dict
|
52
|
+
'''
|
53
|
+
|
54
|
+
# Load the app context.
|
55
|
+
app_context = self.load_app_context(interface_id)
|
56
|
+
|
57
|
+
# Run the app context.
|
58
|
+
app_context.run(
|
59
|
+
interface_id=interface_id,
|
60
|
+
**kwargs
|
61
|
+
)
|
62
|
+
|
63
|
+
# * method: load_app_repo
|
64
|
+
def load_app_repo(self) -> AppRepository:
|
65
|
+
'''
|
66
|
+
Load the app interface repository.
|
67
|
+
|
68
|
+
:return: The app repository.
|
69
|
+
:rtype: AppRepository
|
70
|
+
'''
|
71
|
+
|
72
|
+
# Load the app repository configuration.
|
73
|
+
from ..configs.app import APP_REPO
|
74
|
+
|
75
|
+
# Return the app repository.
|
76
|
+
return container_service.import_dependency(APP_REPO.module_path, APP_REPO.class_name)(**APP_REPO.params)
|
77
|
+
|
78
|
+
# * method: load_app_context
|
79
|
+
def load_app_context(self, interface_id: str) -> AppInterfaceContext:
|
80
|
+
'''
|
81
|
+
Load the app context.
|
82
|
+
|
83
|
+
:param container: The app container.
|
84
|
+
:type container: AppContainer
|
85
|
+
:return: The app context.
|
86
|
+
:rtype: AppContext
|
87
|
+
'''
|
88
|
+
|
89
|
+
# Get the app interface.
|
90
|
+
app_interface: AppInterface = self.interfaces.get(interface_id)
|
91
|
+
|
92
|
+
# Get the dependencies for the app interface.
|
93
|
+
dependencies = dict(
|
94
|
+
interface_id=app_interface.id,
|
95
|
+
feature_flag=app_interface.feature_flag,
|
96
|
+
data_flag=app_interface.data_flag,
|
97
|
+
**app_interface.constants
|
98
|
+
)
|
99
|
+
for dep in app_interface.get_dependencies():
|
100
|
+
dependencies[dep.attribute_id] = container_service.import_dependency(dep.module_path, dep.class_name)
|
101
|
+
|
102
|
+
# Create the injector from the dependencies, constants, and the app interface.
|
103
|
+
injector = container_service.create_injector(
|
104
|
+
app_interface.id
|
105
|
+
**dependencies
|
106
|
+
)
|
107
|
+
|
108
|
+
# Return the app context.
|
109
|
+
return getattr(injector, 'app_context')
|
app/contexts/error.py
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# *** imports
|
2
|
+
|
3
|
+
# ** core
|
4
|
+
from typing import Any, Tuple
|
5
|
+
|
6
|
+
# ** app
|
7
|
+
from ..domain import *
|
8
|
+
from ..repos.error import ErrorRepository
|
9
|
+
|
10
|
+
|
11
|
+
# *** contexts
|
12
|
+
|
13
|
+
# ** context: error_context
|
14
|
+
class ErrorContext(Model):
|
15
|
+
'''
|
16
|
+
The error context object.
|
17
|
+
'''
|
18
|
+
|
19
|
+
# * attribute: errors
|
20
|
+
errors = DictType(
|
21
|
+
ModelType(Error),
|
22
|
+
required=True,
|
23
|
+
metadata=dict(
|
24
|
+
description='The errors lookup.'
|
25
|
+
)
|
26
|
+
)
|
27
|
+
|
28
|
+
# * method: init
|
29
|
+
def __init__(self, error_repo: ErrorRepository):
|
30
|
+
'''
|
31
|
+
Initialize the error context object.
|
32
|
+
|
33
|
+
:param error_repo: The error repository.
|
34
|
+
:type error_repo: ErrorRepository
|
35
|
+
'''
|
36
|
+
|
37
|
+
# Create the errors lookup from the error repository.
|
38
|
+
errors = {error.name: error for error in error_repo.list()}
|
39
|
+
|
40
|
+
# Set the errors lookup and validate.
|
41
|
+
super().__init__(dict(errors=errors))
|
42
|
+
self.validate()
|
43
|
+
|
44
|
+
# * method: handle_error
|
45
|
+
def handle_error(self, execute_feature: Any) -> Tuple[bool, Any]:
|
46
|
+
'''
|
47
|
+
Handle an error.
|
48
|
+
|
49
|
+
:param func: The execute feature function to handle.
|
50
|
+
:type func: function
|
51
|
+
:return: Whether the error was handled.
|
52
|
+
:rtype: bool
|
53
|
+
'''
|
54
|
+
|
55
|
+
# Execute the feature function and handle the errors.
|
56
|
+
try:
|
57
|
+
execute_feature()
|
58
|
+
return (False, None)
|
59
|
+
except AssertionError as e:
|
60
|
+
return (True, self.format_error_response(str(e)))
|
61
|
+
|
62
|
+
# * method: format_error_response
|
63
|
+
def format_error_response(self, error_message: str, lang: str = 'en_US', **kwargs) -> Any:
|
64
|
+
'''
|
65
|
+
Format the error response.
|
66
|
+
|
67
|
+
:param error_message: The error message.
|
68
|
+
:type error_message: str
|
69
|
+
:param kwargs: Additional keyword arguments.
|
70
|
+
:type kwargs: dict
|
71
|
+
:return: The formatted error message.
|
72
|
+
:rtype: Any
|
73
|
+
'''
|
74
|
+
|
75
|
+
# Split error message.
|
76
|
+
try:
|
77
|
+
error_name, error_data = error_message.split(': ')
|
78
|
+
except ValueError:
|
79
|
+
error_name = error_message
|
80
|
+
error_data = None
|
81
|
+
|
82
|
+
# Format error data if present.
|
83
|
+
error_data = error_data.split(', ') if error_data else None
|
84
|
+
|
85
|
+
# Get error.
|
86
|
+
error = self.errors.get(error_name)
|
87
|
+
|
88
|
+
# Set error response.
|
89
|
+
error_response = dict(
|
90
|
+
message=error.format(lang, *error_data if error_data else []),
|
91
|
+
**kwargs
|
92
|
+
)
|
93
|
+
|
94
|
+
# Return error response.
|
95
|
+
return error_response
|
app/contexts/feature.py
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# *** imports
|
2
|
+
|
3
|
+
# ** app
|
4
|
+
from .container import ContainerContext
|
5
|
+
from .request import RequestContext
|
6
|
+
from ..domain import *
|
7
|
+
from ..repos.feature import FeatureRepository
|
8
|
+
|
9
|
+
|
10
|
+
# *** contexts
|
11
|
+
|
12
|
+
# ** context: feature_context
|
13
|
+
class FeatureContext(Model):
|
14
|
+
|
15
|
+
# * attribute: features
|
16
|
+
features = DictType(
|
17
|
+
ModelType(Feature),
|
18
|
+
required=True,
|
19
|
+
metadata=dict(
|
20
|
+
description='The features lookup.'
|
21
|
+
)
|
22
|
+
)
|
23
|
+
|
24
|
+
# * attribute: container
|
25
|
+
container = ModelType(
|
26
|
+
ContainerContext,
|
27
|
+
required=True,
|
28
|
+
metadata=dict(
|
29
|
+
description='The container context.'
|
30
|
+
),
|
31
|
+
)
|
32
|
+
|
33
|
+
# * method: init
|
34
|
+
def __init__(self, feature_repo: FeatureRepository, container: ContainerContext):
|
35
|
+
|
36
|
+
# Set the features.
|
37
|
+
self.features = {feature.id: feature for feature in feature_repo.list()}
|
38
|
+
|
39
|
+
# Set the container context.
|
40
|
+
self.container = container
|
41
|
+
|
42
|
+
# * method: execute
|
43
|
+
def execute(self, request: RequestContext, debug: bool = False, **kwargs):
|
44
|
+
'''
|
45
|
+
Execute the feature request.
|
46
|
+
|
47
|
+
:param request: The request context object.
|
48
|
+
:type request: r.RequestContext
|
49
|
+
:param debug: Debug flag.
|
50
|
+
:type debug: bool
|
51
|
+
:param kwargs: Additional keyword arguments.
|
52
|
+
:type kwargs: dict
|
53
|
+
'''
|
54
|
+
|
55
|
+
# Iterate over the feature commands.
|
56
|
+
for command in self.features[request.feature_id].commands:
|
57
|
+
|
58
|
+
# Get the feature command handler instance.
|
59
|
+
handler = self.container.get_dependency(command.attribute_id)
|
60
|
+
|
61
|
+
# Execute the handler function.
|
62
|
+
# Handle assertion errors if pass on error is not set.
|
63
|
+
try:
|
64
|
+
result = handler.execute(
|
65
|
+
**request.data,
|
66
|
+
**command.params,
|
67
|
+
**kwargs)
|
68
|
+
except AssertionError as e:
|
69
|
+
if not command.pass_on_error:
|
70
|
+
raise e
|
71
|
+
|
72
|
+
# Return the result to the session context if return to data is set.
|
73
|
+
if command.return_to_data:
|
74
|
+
request.data[command.data_key] = result
|
75
|
+
continue
|
76
|
+
|
77
|
+
# Set the result in the request context.
|
78
|
+
if result:
|
79
|
+
request.set_result(result)
|
app/contexts/request.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# *** imports
|
2
|
+
|
3
|
+
# ** core
|
4
|
+
from typing import Dict
|
5
|
+
|
6
|
+
# ** app
|
7
|
+
from ..domain import *
|
8
|
+
|
9
|
+
|
10
|
+
# *** contexts
|
11
|
+
|
12
|
+
# ** context: request_context
|
13
|
+
class RequestContext(Model):
|
14
|
+
'''
|
15
|
+
The context for an application request.
|
16
|
+
'''
|
17
|
+
|
18
|
+
# * attribute: feature_id
|
19
|
+
feature_id = StringType(
|
20
|
+
required=True,
|
21
|
+
metadata=dict(
|
22
|
+
description='The feature identifier for the request.'
|
23
|
+
)
|
24
|
+
)
|
25
|
+
|
26
|
+
# * attribute: headers
|
27
|
+
headers = DictType(
|
28
|
+
StringType(),
|
29
|
+
metadata=dict(
|
30
|
+
description='The request headers.'
|
31
|
+
)
|
32
|
+
)
|
33
|
+
|
34
|
+
# * attribute: data
|
35
|
+
data = DictType(
|
36
|
+
StringType(),
|
37
|
+
metadata=dict(
|
38
|
+
description='The request data.'
|
39
|
+
)
|
40
|
+
)
|
41
|
+
|
42
|
+
# * attribute: result
|
43
|
+
result = StringType(
|
44
|
+
metadata=dict(
|
45
|
+
description='The request result.'
|
46
|
+
)
|
47
|
+
)
|
48
|
+
|
49
|
+
# * method: init
|
50
|
+
def __init__(self, feature_id: str, headers: Dict[str, str], data: Dict[str, str]):
|
51
|
+
'''
|
52
|
+
Initialize the request context object.
|
53
|
+
|
54
|
+
:param feature_id: The feature identifier.
|
55
|
+
:type feature_id: str
|
56
|
+
:param headers: The request headers.
|
57
|
+
:type headers: dict
|
58
|
+
:param data: The request data.
|
59
|
+
:type data: dict
|
60
|
+
:param kwargs: Additional keyword arguments.
|
61
|
+
:type kwargs: dict
|
62
|
+
'''
|
63
|
+
|
64
|
+
# Set the context attributes.
|
65
|
+
self.feature_id = feature_id
|
66
|
+
self.headers = headers
|
67
|
+
self.data = data
|
68
|
+
|
69
|
+
# Validate the context.
|
70
|
+
self.validate()
|
71
|
+
|
72
|
+
# * method: set_result
|
73
|
+
def set_result(self, result: Any):
|
74
|
+
'''
|
75
|
+
Set the serialized result value.
|
76
|
+
|
77
|
+
:param result: The result object.
|
78
|
+
:type result: Any
|
79
|
+
'''
|
80
|
+
|
81
|
+
# Import the json module.
|
82
|
+
import json
|
83
|
+
|
84
|
+
# Set the result as a serialized empty dictionary if it is None.
|
85
|
+
if not result:
|
86
|
+
self.result = json.dumps({})
|
87
|
+
return
|
88
|
+
|
89
|
+
# If the result is a Model, convert it to a primitive dictionary and serialize it.
|
90
|
+
if isinstance(result, Model):
|
91
|
+
self.result = json.dumps(result.to_primitive())
|
92
|
+
return
|
93
|
+
|
94
|
+
# If the result is not a list, it must be a dict, so serialize it and set it.
|
95
|
+
if type(self.result) != list:
|
96
|
+
self.result = json.dumps(result)
|
97
|
+
return
|
98
|
+
|
99
|
+
# If the result is a list, convert each item to a primitive dictionary.
|
100
|
+
result_list = []
|
101
|
+
for item in result:
|
102
|
+
if isinstance(item, Model):
|
103
|
+
result_list.append(item.to_primitive())
|
104
|
+
else:
|
105
|
+
result_list.append(item)
|
106
|
+
|
107
|
+
# Serialize the result and set it.
|
108
|
+
self.result = json.dumps(result_list)
|
app/data/__init__.py
ADDED