tiferet 1.0.0a19__py3-none-any.whl → 1.0.0b0__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.
Files changed (59) hide show
  1. tiferet/__init__.py +3 -2
  2. tiferet/commands/__init__.py +6 -0
  3. tiferet/commands/app.py +102 -0
  4. tiferet/commands/core.py +124 -0
  5. tiferet/commands/dependencies.py +76 -0
  6. tiferet/commands/settings.py +101 -0
  7. tiferet/configs/__init__.py +3 -67
  8. tiferet/configs/error.py +48 -0
  9. tiferet/configs/settings.py +37 -0
  10. tiferet/contexts/__init__.py +0 -8
  11. tiferet/contexts/app.py +215 -182
  12. tiferet/contexts/cache.py +76 -0
  13. tiferet/contexts/container.py +92 -190
  14. tiferet/contexts/error.py +78 -80
  15. tiferet/contexts/feature.py +120 -83
  16. tiferet/contracts/__init__.py +4 -0
  17. tiferet/contracts/app.py +92 -0
  18. tiferet/contracts/cache.py +70 -0
  19. tiferet/contracts/container.py +147 -0
  20. tiferet/contracts/error.py +177 -0
  21. tiferet/contracts/feature.py +159 -0
  22. tiferet/contracts/settings.py +34 -0
  23. tiferet/data/__init__.py +3 -4
  24. tiferet/data/app.py +71 -208
  25. tiferet/data/container.py +52 -38
  26. tiferet/data/error.py +15 -24
  27. tiferet/data/feature.py +27 -8
  28. tiferet/{domain/core.py → data/settings.py} +36 -96
  29. tiferet/handlers/__init__.py +0 -0
  30. tiferet/handlers/container.py +116 -0
  31. tiferet/handlers/error.py +49 -0
  32. tiferet/handlers/feature.py +94 -0
  33. tiferet/models/__init__.py +4 -0
  34. tiferet/models/app.py +150 -0
  35. tiferet/models/container.py +135 -0
  36. tiferet/{domain → models}/error.py +86 -36
  37. tiferet/{domain → models}/feature.py +107 -47
  38. tiferet/models/settings.py +148 -0
  39. tiferet/proxies/__init__.py +0 -0
  40. tiferet/proxies/yaml/__init__.py +0 -0
  41. tiferet/{repos → proxies/yaml}/app.py +13 -41
  42. tiferet/{repos → proxies/yaml}/container.py +26 -56
  43. tiferet/{repos → proxies/yaml}/error.py +11 -70
  44. tiferet/proxies/yaml/feature.py +92 -0
  45. {tiferet-1.0.0a19.dist-info → tiferet-1.0.0b0.dist-info}/METADATA +12 -3
  46. tiferet-1.0.0b0.dist-info/RECORD +51 -0
  47. {tiferet-1.0.0a19.dist-info → tiferet-1.0.0b0.dist-info}/WHEEL +1 -1
  48. tiferet/commands/container.py +0 -54
  49. tiferet/commands/error.py +0 -21
  50. tiferet/commands/feature.py +0 -90
  51. tiferet/contexts/request.py +0 -110
  52. tiferet/domain/__init__.py +0 -5
  53. tiferet/domain/app.py +0 -131
  54. tiferet/domain/container.py +0 -141
  55. tiferet/repos/__init__.py +0 -7
  56. tiferet/repos/feature.py +0 -151
  57. tiferet-1.0.0a19.dist-info/RECORD +0 -35
  58. {tiferet-1.0.0a19.dist-info → tiferet-1.0.0b0.dist-info/licenses}/LICENSE +0 -0
  59. {tiferet-1.0.0a19.dist-info → tiferet-1.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,50 +1,13 @@
1
1
  # *** imports
2
2
 
3
3
  # ** core
4
- import os
5
- from typing import Any
4
+ from typing import Any, List
6
5
 
7
6
  # ** app
8
- from ..domain import *
9
- from ..repos.container import ContainerRepository
10
-
11
-
12
- # *** functions
13
-
14
- # ** function: create_injector
15
- def create_injector(name: str, **dependencies) -> Any:
16
- '''
17
- Create an injector object with the given dependencies.
18
-
19
- :param name: The name of the injector.
20
- :type name: str
21
- :param dependencies: The dependencies.
22
- :type dependencies: dict
23
- :return: The injector object.
24
- :rtype: Any
25
- '''
26
-
27
- # Create container.
28
- from dependencies import Injector
29
- return type(f'{name.capitalize()}Container', (Injector,), {**dependencies})
30
-
31
-
32
- # ** function: import_dependency
33
- def import_dependency(module_path: str, class_name: str) -> Any:
34
- '''
35
- Import an object dependency from its configured Python module.
36
-
37
- :param module_path: The module path.
38
- :type module_path: str
39
- :param class_name: The class name.
40
- :type class_name: str
41
- :return: The dependency.
42
- :rtype: Any
43
- '''
44
-
45
- # Import module.
46
- from importlib import import_module
47
- return getattr(import_module(module_path), class_name)
7
+ from .cache import CacheContext
8
+ from ..handlers.container import ContainerService
9
+ from ..commands import *
10
+ from ..commands.dependencies import *
48
11
 
49
12
 
50
13
  # *** contexts
@@ -55,178 +18,117 @@ class ContainerContext(Model):
55
18
  A container context is a class that is used to create a container object.
56
19
  '''
57
20
 
58
- # * attribute: interface_id
59
- interface_id = StringType(
60
- required=True,
61
- metadata=dict(
62
- description='The interface ID.'
63
- ),
64
- )
65
-
66
- # * attribute: attributes
67
- attributes = DictType(
68
- ModelType(ContainerAttribute),
69
- default={},
70
- required=True,
71
- metadata=dict(
72
- description='The container attributes.'
73
- ),
74
- )
75
-
76
- # * attribute: constants
77
- constants = DictType(
78
- StringType,
79
- default={},
80
- metadata=dict(
81
- description='The container constants.'
82
- ),
83
- )
84
-
85
- # * attribute: feature_flag
86
- feature_flag = StringType(
87
- required=True,
88
- default='core',
89
- metadata=dict(
90
- description='The feature flag.'
91
- ),
92
- )
93
-
94
- # * attribute: data_flag
95
- data_flag = StringType(
96
- required=True,
97
- metadata=dict(
98
- description='The data flag.'
99
- ),
100
- )
21
+ # * attribute: cache
22
+ cache: CacheContext
23
+
24
+ # * attribute: container_service
25
+ container_service: ContainerService
26
+
101
27
 
102
28
  # * method: init
103
- def __init__(self, interface_id: str, container_repo: ContainerRepository, feature_flag: str, data_flag: str):
29
+ def __init__(self, container_service: ContainerService, cache: CacheContext = None):
104
30
  '''
105
31
  Initialize the container context.
106
32
 
107
- :param interface_id: The interface ID.
108
- :type interface_id: str
109
- :param container_repo: The container repository.
110
- :type container_repo: ContainerRepository
111
- :param interface_flag: The interface flag.
112
- :type interface_flag: str
113
- :param feature_flag: The feature flag.
114
- :type feature_flag: str
115
- :param data_flag: The data flag.
116
- :type data_flag: str
33
+ :param container_service: The container service to use for executing container requests.
34
+ :type container_service: ContainerService
35
+ :param cache: The cache context to use for caching container data.
36
+ :type cache: CacheContext
117
37
  '''
118
38
 
119
- # Add the attributes and constants as empty dictionaries.
120
- attributes = {}
121
- constants = {}
122
-
123
- # Get and set attributes and constants.
124
- attrs, consts = container_repo.list_all()
125
-
126
- # Parse the constants.
127
- constants.update({key: self.parse_parameter(consts[key]) for key in consts})
128
-
129
- # Add the attributes to the context.
130
- for attr in attrs:
131
-
132
- # If the attribute already exists, set the dependencies.
133
- if attr.id in attributes:
134
- for dep in attr.dependencies:
135
- attr.set_dependency(dep)
136
- continue
137
-
138
- # Otherwise, add the attribute.
139
- attributes[attr.id] = attr
140
-
141
- # Add any parameters as constants.
142
- for dep in attr.dependencies:
143
- for key in dep.parameters:
144
- constants[key] = self.parse_parameter(dep.parameters[key])
145
-
146
- # Add the constants and attributes to the context.
147
- super().__init__(dict(
148
- interface_id=interface_id,
149
- feature_flag=feature_flag,
150
- data_flag=data_flag,
151
- attributes=attributes,
152
- constants=constants,
153
- ))
154
-
39
+ # Assign the attributes.
40
+ self.container_service = container_service
41
+ self.cache = cache if cache else CacheContext()
155
42
 
156
- # * method: parse_environment_parameter
157
- def parse_parameter(self, parameter: str) -> str:
158
-
159
- # If the parameter is an environment variable, get the value.
160
- if parameter.startswith('$env.'):
161
- return os.getenv(parameter[5:])
162
-
163
- # Otherwise, return the parameter.
164
- return parameter
165
-
166
- # * method: get_dependency
167
- def get_dependency(self, attribute_id: str):
43
+ # * method: create_cache_key
44
+ def create_cache_key(self, flags: List[str] = None) -> str:
168
45
  '''
169
- Get a dependency from the container.
46
+ Create a cache key for the container.
170
47
 
171
- :param attribute_id: The attribute id of the dependency.
172
- :type attribute_id: str
173
- :return: The attribute value.
174
- :rtype: Any
48
+ :param flags: The feature or data flags to use.
49
+ :type flags: List[str]
50
+ :return: The cache key.
51
+ :rtype: str
175
52
  '''
176
53
 
177
- # Create the injector.
178
- injector = self.create_injector()
179
-
180
- # Get attribute.
181
- return getattr(injector, attribute_id)
54
+ # Create the cache key from the flags.
55
+ return f"feature_container{'_' + '_'.join(flags) if flags else ''}"
182
56
 
183
- # * method: create_injector
184
- def create_injector(self, **kwargs) -> Any:
57
+ # * method: build_injector
58
+ def build_injector(self,
59
+ flags: List[str] = None,
60
+ ) -> Injector:
185
61
  '''
186
- Add a container to the context.
62
+ Build the container injector.
187
63
 
188
- :param kwargs: Additional keyword arguments.
189
- :type kwargs: dict
64
+ :param flags: The feature or data flags to use.
65
+ :type flags: List[str]
190
66
  :return: The container injector object.
191
- :rtype: Any
67
+ :rtype: Injector
192
68
  '''
193
69
 
194
- # Import dependencies.
195
- dependencies = {}
196
- for attribute_id in self.attributes:
197
- attribute = self.attributes[attribute_id]
198
- flag_map = dict(
199
- feature=self.feature_flag,
200
- data=self.data_flag,
201
- )
202
- dependencies[attribute_id] = self.import_dependency(attribute, flag_map[attribute.type])
70
+ # Create the cache key for the injector from the flags.
71
+ cache_key = self.create_cache_key(flags)
72
+
73
+ # Check if the injector is already cached.
74
+ cached_injector = self.cache.get(cache_key)
75
+ if cached_injector:
76
+ return cached_injector
203
77
 
204
- # Create container.
205
- return create_injector(
206
- self.interface_id,
207
- **self.constants,
208
- **dependencies,
209
- **kwargs)
78
+ # Get all attributes and constants from the container service.
79
+ attributes, constants = self.container_service.list_all()
210
80
 
211
- # * method: import_dependency
212
- def import_dependency(self, attribute: ContainerAttribute, flag: str) -> Any:
81
+ # Load constants from the attributes.
82
+ constants = self.container_service.load_constants(attributes, constants, flags)
83
+
84
+ # Create the dependencies for the injector.
85
+ dependencies = {}
86
+ for attr in attributes:
87
+ try:
88
+ dependencies[attr.id] = self.container_service.get_dependency_type(attr, flags)
89
+ except TiferetError as e:
90
+ raise e
91
+
92
+
93
+ # Create the injector with the dependencies and constants.
94
+ injector = create_injector.execute(
95
+ cache_key,
96
+ dependencies=dependencies,
97
+ **constants
98
+ )
99
+
100
+ # Cache the injector.
101
+ self.cache.set(cache_key, injector)
102
+
103
+ # Return the injector.
104
+ return injector
105
+
106
+ # * method: get_dependency
107
+ def get_dependency(self, attribute_id: str, flags: List[str] = []) -> Any:
213
108
  '''
214
- Import a container attribute dependency from its configured Python module.
109
+ Get an injector dependency by its attribute ID.
215
110
 
216
- :param attribute: The container attribute.
217
- :type attribute: ContainerAttribute
218
- :param flag: The flag for the dependency.
219
- :type flag: str
220
- :return: The dependency.
111
+ :return: The container attribute.
221
112
  :rtype: Any
222
113
  '''
223
114
 
224
- # Get the dependency.
225
- dependency = attribute.get_dependency(flag)
115
+ # Get the cached injector.
116
+ injector = self.build_injector(flags)
226
117
 
227
- # If there is no dependency and the attribute is a feature, import the default feature.
228
- if not dependency and attribute.type == 'feature':
229
- dependency = attribute.get_dependency('core')
118
+ # Get the dependency from the injector.
119
+ dependency = get_dependency.execute(
120
+ injector=injector,
121
+ dependency_name=attribute_id,
122
+ )
123
+
124
+ # Raise an error if the dependency is not found.
125
+ if not dependency:
126
+ raise_error.execute(
127
+ 'CONTAINER_DEPENDENCY_NOT_FOUND',
128
+ f'Dependency with ID {attribute_id} not found in container with flags {flags}.',
129
+ attribute_id,
130
+ flags
131
+ )
230
132
 
231
- # Import the dependency.
232
- return import_dependency(dependency.module_path, dependency.class_name)
133
+ # Return the dependency.
134
+ return dependency
tiferet/contexts/error.py CHANGED
@@ -4,71 +4,97 @@
4
4
  from typing import Any, Tuple, List
5
5
 
6
6
  # ** app
7
- from ..domain import *
8
- from ..repos.error import ErrorRepository
7
+ from .cache import CacheContext
8
+ from ..configs.error import ERRORS
9
+ from ..handlers.error import ErrorService
10
+ from ..models.error import Error, ModelObject
11
+ from ..commands import raise_error, TiferetError
9
12
 
10
13
 
11
14
  # *** contexts
12
15
 
13
16
  # ** context: error_context
14
- class ErrorContext(Model):
17
+ class ErrorContext(object):
15
18
  '''
16
19
  The error context object.
17
20
  '''
18
21
 
19
- # * attribute: errors
20
- errors = DictType(
21
- ModelType(Error),
22
- required=True,
23
- metadata=dict(
24
- description='The errors lookup.'
25
- )
26
- )
22
+ # * attribute: error_service
23
+ error_service: ErrorService
24
+
25
+ # * attribute: cache
26
+ cache: CacheContext
27
27
 
28
28
  # * method: init
29
- def __init__(self, error_repo: ErrorRepository):
29
+ def __init__(self, error_service: ErrorService, cache: CacheContext = None):
30
30
  '''
31
- Initialize the error context object.
32
-
33
- :param error_repo: The error repository.
34
- :type error_repo: ErrorRepository
31
+ Initialize the error context.
32
+
33
+ :param error_service: The error service to use for handling errors.
34
+ :type error_service: ErrorService
35
+ :param cache: The cache context to use for caching error data.
36
+ :type cache: CacheContext
37
+ '''
38
+
39
+ # Assign the attributes.
40
+ self.error_service = error_service
41
+ self.cache = cache if cache else CacheContext()
42
+
43
+ # * method: load_errors
44
+ def load_errors(self) -> List[Error]:
35
45
  '''
46
+ Load errors from the error service.
36
47
 
37
- # Create the errors lookup from the error repository.
38
- errors = {error.id: error for error in error_repo.list()}
48
+ :return: The list of loaded errors.
49
+ :rtype: List[Error]
50
+ '''
39
51
 
40
- # Add custom errors.
41
- errors.update({error.id: error for error in self.load_custom_errors()})
52
+ # Convert configured errors to a list of error objects.
53
+ configured_errors = [
54
+ ModelObject.new(
55
+ Error,
56
+ **error_data
57
+ ) for error_data in ERRORS]
42
58
 
43
- # Set the errors lookup and validate.
44
- super().__init__(dict(errors=errors))
45
- self.validate()
59
+ # Load errors from the error service, including any configured errors.
60
+ return self.error_service.load_errors(configured_errors)
46
61
 
47
- # * method: load_custom_errors
48
- def load_custom_errors(self) -> List[Error]:
62
+
63
+ # * method: get_error_by_code
64
+ def get_error_by_code(self, error_code: str) -> Error:
49
65
  '''
50
- Load custom errors.
66
+ Get an error by its code.
51
67
 
52
- :return: The list of custom errors.
53
- :rtype: list
68
+ :param error_code: The error code to retrieve.
69
+ :type error_code: str
70
+ :return: The error object.
71
+ :rtype: Error
54
72
  '''
55
73
 
56
- # Get custom errors.
57
- return [
58
- Error.new(
59
- name='FEATURE_NOT_FOUND',
60
- error_code='FEATURE_NOT_FOUND',
61
- message=[
62
- ErrorMessage.new(
63
- lang='en_US',
64
- text='The feature with ID was not found: {}'
65
- )
66
- ]
74
+ # Check to see if there is an error map in the cache.
75
+ error_map = self.cache.get('error_map')
76
+
77
+ # If there is no error map or the error code does not exist in the map,
78
+ # we need to load the errors from the error service and create a new error map.
79
+ # This is to ensure that we always have the latest errors available.
80
+ if not error_map or not error_map.get(error_code):
81
+ errors = self.load_errors()
82
+ error_map = {error.error_code: error for error in errors}
83
+ self.cache.set('error_map', error_map)
84
+
85
+ # Raise an error if the error code does not exist in the error map.
86
+ if not error_map or error_code not in error_map:
87
+ raise_error.execute(
88
+ 'ERROR_NOT_FOUND',
89
+ f'Error not found: {error_code}.',
90
+ error_code
67
91
  )
68
- ]
92
+
93
+ # Return the error object from the error map.
94
+ return error_map.get(error_code)
69
95
 
70
96
  # * method: handle_error
71
- def handle_error(self, exception: Exception, lang: str = 'en_US', **kwargs) -> Tuple[bool, Any]:
97
+ def handle_error(self, exception: TiferetError, lang: str = 'en_US', **kwargs) -> Tuple[bool, Any]:
72
98
  '''
73
99
  Handle an error.
74
100
 
@@ -80,47 +106,19 @@ class ErrorContext(Model):
80
106
  :rtype: bool
81
107
  '''
82
108
 
83
- # Execute the feature function and handle the errors.
84
- if isinstance(exception, AssertionError):
85
- return self.format_error_response(str(exception), lang, **kwargs)
109
+ # Raise the exception if it is not a Tiferet error.
110
+ if not isinstance(exception, TiferetError):
111
+ raise exception
86
112
 
87
- # * method: format_error_response
88
- def format_error_response(self, error_message: str, lang: str, **kwargs) -> Any:
89
- '''
90
- Format the error response.
91
-
92
- :param error_message: The error message.
93
- :type error_message: str
94
- :param kwargs: Additional keyword arguments.
95
- :type kwargs: dict
96
- :return: The formatted error message.
97
- :rtype: Any
98
- '''
99
-
100
- # Split error message into error name and data.
101
- message_tokens = error_message.split(': ', 1)
102
-
103
- # Get error name and data.
104
- if len(message_tokens) > 1:
105
- error_name, error_data = message_tokens
106
- else:
107
- error_name = error_message
108
- error_data = None
109
-
110
- # Format error data if present.
111
- error_data = error_data.split(', ') if error_data else None
112
-
113
- # Get error.
114
- error = self.errors.get(error_name)
115
-
116
- # Set error response.
117
- error_response = dict(
118
- message=error.format(lang, *error_data if error_data else []),
119
- error_code=error.error_code,
113
+ # Get the error by its code from the error service.
114
+ error = self.get_error_by_code(exception.error_code)
115
+
116
+ # Format the error response.
117
+ error_response = error.format_response(
118
+ lang,
119
+ *exception.args[1:],
120
120
  **kwargs
121
121
  )
122
122
 
123
- # Return error response.
123
+ # Return the error response.
124
124
  return error_response
125
-
126
-