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
tiferet/data/feature.py CHANGED
@@ -4,8 +4,9 @@
4
4
  from schematics.types.serializable import serializable
5
5
 
6
6
  #
7
- from ..domain import *
8
- from ..domain.feature import Feature, FeatureCommand
7
+ from ..data import DataObject
8
+ from ..contracts.feature import Feature as FeatureContract, FeatureCommand as FeatureCommandContract
9
+ from ..models.feature import *
9
10
 
10
11
 
11
12
  class FeatureCommandData(FeatureCommand, DataObject):
@@ -23,11 +24,22 @@ class FeatureCommandData(FeatureCommand, DataObject):
23
24
 
24
25
  # Define the roles for the feature handler data.
25
26
  roles = {
26
- 'to_model': DataObject.allow(),
27
+ 'to_model': DataObject.deny('parameters'),
27
28
  'to_data': DataObject.allow()
28
29
  }
29
30
 
30
- def map(self, role: str = 'to_model', **kwargs) -> FeatureCommand:
31
+ # * attributes
32
+ parameters = DictType(
33
+ StringType(),
34
+ default={},
35
+ serialized_name='params',
36
+ deserialize_from=['params', 'parameters'],
37
+ metadata=dict(
38
+ description='The parameters for the feature.'
39
+ )
40
+ )
41
+
42
+ def map(self, role: str = 'to_model', **kwargs) -> FeatureCommandContract:
31
43
  '''
32
44
  Maps the feature handler data to a feature handler object.
33
45
 
@@ -38,7 +50,10 @@ class FeatureCommandData(FeatureCommand, DataObject):
38
50
  :return: A new feature handler object.
39
51
  :rtype: f.FeatureCommand
40
52
  '''
41
- return super().map(FeatureCommand, role, **kwargs)
53
+ return super().map(FeatureCommand,
54
+ role,
55
+ parameters=self.parameters,
56
+ **kwargs)
42
57
 
43
58
 
44
59
  class FeatureData(Feature, DataObject):
@@ -59,8 +74,9 @@ class FeatureData(Feature, DataObject):
59
74
  'to_model': DataObject.deny('feature_key'),
60
75
  'to_data': DataObject.deny('feature_key', 'group_id', 'id')
61
76
  }
62
-
63
- commands = t.ListType(t.ModelType(FeatureCommandData),
77
+
78
+ # * attributes
79
+ commands = ListType(ModelType(FeatureCommandData),
64
80
  deserialize_from=['handlers', 'functions', 'commands'],)
65
81
 
66
82
  @serializable
@@ -72,7 +88,7 @@ class FeatureData(Feature, DataObject):
72
88
  # Return the feature key.
73
89
  return self.id.split('.')[-1]
74
90
 
75
- def map(self, role: str = 'to_model', **kwargs) -> Feature:
91
+ def map(self, role: str = 'to_model', **kwargs) -> FeatureContract:
76
92
  '''
77
93
  Maps the feature data to a feature object.
78
94
 
@@ -87,6 +103,9 @@ class FeatureData(Feature, DataObject):
87
103
  # Map the feature data to a feature object.
88
104
  return super().map(Feature, role,
89
105
  feature_key=self.feature_key,
106
+ commands=[
107
+ command.map(role, **kwargs) for command in self.commands
108
+ ],
90
109
  **kwargs
91
110
  )
92
111
 
@@ -3,82 +3,23 @@
3
3
  # ** core
4
4
  from typing import Any
5
5
 
6
- # ** infra
7
- from schematics import Model
8
-
9
6
  # ** app
10
- from ..configs import *
11
-
12
-
13
- # *** models
14
-
15
- # ** model: model_object
16
- class ModelObject(Model):
17
- '''
18
- A domain model object.
19
- '''
20
-
21
- # * method: new
22
- @staticmethod
23
- def new(
24
- model_type: type,
25
- validate: bool = True,
26
- strict: bool = True,
27
- **kwargs
28
- ) -> Any:
29
- '''
30
- Initializes a new model object.
31
-
32
- :param model_type: The type of model object to create.
33
- :type model_type: type
34
- :param validate: True to validate the model object.
35
- :type validate: bool
36
- :param strict: True to enforce strict mode for the model object.
37
- :type strict: bool
38
- :param kwargs: Keyword arguments.
39
- :type kwargs: dict
40
- :return: A new model object.
41
- :rtype: Any
42
- '''
43
-
44
- # Create a new model object.
45
- _object = model_type(dict(
46
- **kwargs
47
- ), strict=strict)
48
-
49
- # Validate if specified.
50
- if validate:
51
- _object.validate()
7
+ from ..contracts.settings import ModelContract
8
+ from ..models.settings import *
52
9
 
53
- # Return the new model object.
54
- return _object
55
10
 
11
+ # *** constants
56
12
 
57
- # ** model: entity
58
- class Entity(ModelObject):
59
- '''
60
- A domain model entity.
61
- '''
13
+ # ** constant: default_module_path
14
+ DEFAULT_MODULE_PATH = 'tiferet.contexts.app'
62
15
 
63
- # ** attribute: id
64
- id = StringType(
65
- required=True,
66
- metadata=dict(
67
- description='The entity unique identifier.'
68
- )
69
- )
16
+ # ** constant: default_class_name
17
+ DEFAULT_CLASS_NAME = 'AppInterfaceContext'
70
18
 
71
19
 
72
- # ** model: value_object
73
- class ValueObject(ModelObject):
74
- '''
75
- A domain model value object.
76
- '''
77
-
78
- pass
79
-
20
+ # *** classes
80
21
 
81
- # ** model: data_object
22
+ # ** class: data_object
82
23
  class DataObject(Model):
83
24
  '''
84
25
  A data representation object.
@@ -90,7 +31,7 @@ class DataObject(Model):
90
31
  role: str = 'to_model',
91
32
  validate: bool = True,
92
33
  **kwargs
93
- ) -> ModelObject:
34
+ ) -> ModelContract:
94
35
  '''
95
36
  Maps the model data to a model object.
96
37
 
@@ -102,8 +43,8 @@ class DataObject(Model):
102
43
  :type validate: bool
103
44
  :param kwargs: Additional keyword arguments for mapping.
104
45
  :type kwargs: dict
105
- :return: A new model object.
106
- :rtype: ModelObject
46
+ :return: A new model object as a contract.
47
+ :rtype: ModelContract
107
48
  '''
108
49
 
109
50
  # Get primitive of the model data and merge with the keyword arguments.
@@ -113,7 +54,12 @@ class DataObject(Model):
113
54
  _data[key] = value
114
55
 
115
56
  # Map the data object to a model object.
116
- _object = type.new(**_data, strict=False)
57
+ # Attempt to create a new model object with a custom factory method.
58
+ # If the factory method does not exist, employ the standard method.
59
+ try:
60
+ _object = type.new(**_data, strict=False)
61
+ except Exception:
62
+ _object = ModelObject.new(type, **_data, strict=False)
117
63
 
118
64
  # Validate if specified.
119
65
  if validate:
@@ -158,6 +104,7 @@ class DataObject(Model):
158
104
  # Return the data object.
159
105
  return obj
160
106
 
107
+ # ** method: from_data
161
108
  @staticmethod
162
109
  def from_data(
163
110
  data: type,
@@ -179,6 +126,14 @@ class DataObject(Model):
179
126
  # ** method: allow
180
127
  @staticmethod
181
128
  def allow(*args) -> Any:
129
+ '''
130
+ Creates a whitelist transform for data mapping.
131
+
132
+ :param args: Fields to allow in the transform.
133
+ :type args: tuple
134
+ :return: The whitelist transform.
135
+ :rtype: Any
136
+ '''
182
137
 
183
138
  # Create a whitelist transform.
184
139
  # Create a wholelist transform if no arguments are specified.
@@ -190,30 +145,15 @@ class DataObject(Model):
190
145
  # ** method: deny
191
146
  @staticmethod
192
147
  def deny(*args) -> Any:
148
+ '''
149
+ Creates a blacklist transform for data mapping.
150
+
151
+ :param args: Fields to deny in the transform.
152
+ :type args: tuple
153
+ :return: The blacklist transform.
154
+ :rtype: Any
155
+ '''
193
156
 
194
157
  # Create a blacklist transform.
195
158
  from schematics.transforms import blacklist
196
- return blacklist(*args)
197
-
198
-
199
- # ** model: module_dependency
200
- class ModuleDependency(ValueObject):
201
- '''
202
- A module dependency.
203
- '''
204
-
205
- # * attribute: module_path
206
- module_path = StringType(
207
- required=True,
208
- metadata=dict(
209
- description='The module path.'
210
- )
211
- )
212
-
213
- # * attribute: class_name
214
- class_name = StringType(
215
- required=True,
216
- metadata=dict(
217
- description='The class name.'
218
- )
219
- )
159
+ return blacklist(*args)
File without changes
@@ -0,0 +1,116 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from ..commands import *
5
+ from ..contracts.container import *
6
+
7
+
8
+ # *** handlers
9
+
10
+ # ** handler: container_handler
11
+ class ContainerHandler(ContainerService):
12
+
13
+ '''
14
+ A container handler is a class that is used to create a container object.
15
+ '''
16
+
17
+ # * attribute: container_repo
18
+ container_repo: ContainerRepository
19
+
20
+ # * method: __init__
21
+ def __init__(self, container_repo: ContainerRepository):
22
+ '''
23
+ Initialize the container handler.
24
+
25
+ :param name: The name of the container.
26
+ :type name: str
27
+ :param dependencies: The dependencies.
28
+ :type dependencies: dict
29
+ '''
30
+
31
+ # Assign the container repository.
32
+ self.container_repo = container_repo
33
+
34
+ # * method: list_all
35
+ def list_all(self) -> Tuple[List[ContainerAttribute], Dict[str, str]]:
36
+ '''
37
+ List all container attributes and constants.
38
+
39
+ :return: A tuple containing a list of container attributes and a dictionary of constants.
40
+ :rtype: Tuple[List[ContainerAttribute], Dict[str, str]]
41
+ '''
42
+
43
+ # Retrieve all container attributes and constants from the repository.
44
+ attributes, constants = self.container_repo.list_all()
45
+
46
+ # Return the attributes and constants.
47
+ return attributes, constants
48
+
49
+ # * method: load_constants
50
+ def load_constants(self, attributes: List[ContainerAttribute], constants: Dict[str, str] = {}, flags: List[str] = []) -> Dict[str, str]:
51
+ '''
52
+ Load constants from the container attributes.
53
+
54
+ :param attributes: The list of container attributes.
55
+ :type attributes: List[ContainerAttribute]
56
+ :param constants: The dictionary of constants.
57
+ :type constants: Dict[str, str]
58
+ :return: A dictionary of constants.
59
+ :rtype: Dict[str, str]
60
+ '''
61
+
62
+ # Raise an error if there are no attributes provided.
63
+ if not attributes:
64
+ raise_error.execute(
65
+ 'CONTAINER_ATTRIBUTES_NOT_FOUND',
66
+ 'No container attributes provided to load the container.',
67
+ )
68
+
69
+ # If constants are provided, clean the parameters using the parse_parameter command.
70
+ constants = {k: parse_parameter.execute(v) for k, v in constants.items()}
71
+
72
+ # Iterate through each attribute to clean parameter dictionaries.
73
+ # For each attribute, parse its parameters and add them to the constants dictionary.
74
+ # For each dependency, parse its parameters and add them to the constants dictionary.
75
+ for attr in attributes:
76
+ constants.update({k: parse_parameter.execute(v) for k, v in attr.parameters.items()})
77
+ dependency = attr.get_dependency(flags)
78
+ if dependency:
79
+ constants.update({k: parse_parameter.execute(v) for k, v in dependency.parameters.items()})
80
+
81
+ # Return the updated constants dictionary.
82
+ return constants
83
+
84
+ # * method: get_dependency_type
85
+ def get_dependency_type(self, attribute: ContainerAttribute, flags: List[str] = []) -> type:
86
+ '''
87
+ Get the type of a container attribute.
88
+
89
+ :param attribute: The container attribute.
90
+ :type attribute: ContainerAttribute
91
+ :return: The type of the container attribute.
92
+ :rtype: type
93
+ '''
94
+
95
+ # Check the flagged dependencies for the type first.
96
+ for dep in attribute.dependencies:
97
+ if dep.flag in flags:
98
+ return import_dependency.execute(
99
+ dep.module_path,
100
+ dep.class_name
101
+ )
102
+
103
+ # Otherwise defer to an available default type.
104
+ if attribute.module_path and attribute.class_name:
105
+ return import_dependency.execute(
106
+ attribute.module_path,
107
+ attribute.class_name
108
+ )
109
+
110
+ # If no type is found, raise an error.
111
+ raise_error.execute(
112
+ 'DEPENDENCY_TYPE_NOT_FOUND',
113
+ f'No dependency type found for attribute {attribute.id} with flags {flags}.',
114
+ attribute.id,
115
+ flags
116
+ )
@@ -0,0 +1,49 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from ..contracts.error import *
5
+
6
+
7
+ # *** handlers
8
+
9
+ # ** handler: error_handler
10
+ class ErrorHandler(ErrorService):
11
+ '''
12
+ A handler for error objects.
13
+ '''
14
+
15
+ # * attribute: error_repo
16
+ error_repo: ErrorRepository
17
+
18
+ # * method: init
19
+ def __init__(self, error_repo: ErrorRepository):
20
+ '''
21
+ Initialize the error handler.
22
+
23
+ :param error_repo: The error repository to use for handling errors.
24
+ :type error_repo: ErrorRepository
25
+ '''
26
+
27
+ # Assign the attributes.
28
+ self.error_repo = error_repo
29
+
30
+ # * method: load_errors
31
+ def load_errors(self, configured_errors: List[Error] = []) -> List[Error]:
32
+ '''
33
+ Load errors by their codes.
34
+
35
+ :param configured_errors: The list of hard-coded errors to load.
36
+ :type configured_errors: List[Error]
37
+ :return: The list of loaded errors.
38
+ :rtype: List[Error]
39
+ '''
40
+
41
+ # Load the errors from the repository.
42
+ errors = self.error_repo.list()
43
+
44
+ # If there are configured errors, extend the list with them.
45
+ if configured_errors:
46
+ errors.extend(configured_errors)
47
+
48
+ # Return the list of errors.
49
+ return errors
@@ -0,0 +1,94 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from ..commands import *
5
+ from ..contracts.feature import *
6
+
7
+
8
+ # *** handlers
9
+
10
+ # ** handler: feature_handler
11
+ class FeatureHandler(FeatureService):
12
+ '''
13
+ Feature handler for executing feature requests.
14
+ '''
15
+
16
+ # * attribute: feature_repo
17
+ feature_repo: FeatureRepository
18
+
19
+ # * method: __init__
20
+ def __init__(self, feature_repo: FeatureRepository):
21
+ '''
22
+ Initialize the feature handler.
23
+
24
+ :param feature_repo: The feature repository to use for retrieving features.
25
+ :type feature_repo: FeatureRepository
26
+ '''
27
+
28
+ # Assign the feature repository.
29
+ self.feature_repo = feature_repo
30
+
31
+ # * method: parse_parameter
32
+ def parse_parameter(self, parameter: str, request: Request = None) -> str:
33
+ '''
34
+ Parse a parameter.
35
+
36
+
37
+ :param parameter: The parameter to parse.
38
+ :type parameter: str:
39
+ param request: The request object containing data for parameter parsing.
40
+ :type request: Request
41
+ :return: The parsed parameter.
42
+ :rtype: str
43
+ '''
44
+
45
+ # Parse the parameter if it not a request parameter.
46
+ if not parameter.startswith('$r.'):
47
+ return parse_parameter.execute(parameter)
48
+
49
+ # Raise an error if the request is and the parameter comes from the request.
50
+ if not request and parameter.startswith('$r.'):
51
+ raise_error.execute(
52
+ 'REQUEST_NOT_FOUND',
53
+ 'Request data is not available for parameter parsing.',
54
+ parameter
55
+ )
56
+
57
+ # Parse the parameter from the request if provided.
58
+ result = request.data.get(parameter[3:], None)
59
+
60
+ # Raise an error if the parameter is not found in the request data.
61
+ if result is None:
62
+ raise_error.execute(
63
+ 'PARAMETER_NOT_FOUND',
64
+ f'Parameter {parameter} not found in request data.',
65
+ parameter
66
+ )
67
+
68
+ # Return the parsed parameter.
69
+ return result
70
+
71
+ # * method: get_feature
72
+ def get_feature(self, feature_id: str) -> Feature:
73
+ '''
74
+ Get a feature by its ID.
75
+
76
+ :param feature_id: The ID of the feature to retrieve.
77
+ :type feature_id: str
78
+ :return: The feature model contract.
79
+ :rtype: Feature
80
+ '''
81
+
82
+ # Retrieve the feature from the repository.
83
+ feature = self.feature_repo.get(feature_id)
84
+
85
+ # Verify the feature is not None.
86
+ if not feature:
87
+ raise_error.execute(
88
+ 'FEATURE_NOT_FOUND',
89
+ f'Feature not found: {feature_id}',
90
+ feature_id
91
+ )
92
+
93
+ # Return the feature.
94
+ return feature
@@ -0,0 +1,4 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from .settings import *
tiferet/models/app.py ADDED
@@ -0,0 +1,150 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from .settings import *
5
+
6
+
7
+ # *** models
8
+
9
+ # ** model: app_attribute
10
+ class AppAttribute(ValueObject):
11
+
12
+ # * attribute: module_path
13
+ module_path = StringType(
14
+ required=True,
15
+ metadata=dict(
16
+ description='The module path for the app dependency.'
17
+ )
18
+ )
19
+
20
+ # * attribute: class_name
21
+ class_name = StringType(
22
+ required=True,
23
+ metadata=dict(
24
+ description='The class name for the app dependency.'
25
+ )
26
+ )
27
+
28
+ # * attribute: attribute_id
29
+ attribute_id = StringType(
30
+ required=True,
31
+ metadata=dict(
32
+ description='The attribute id for the application dependency.'
33
+ ),
34
+ )
35
+
36
+ # * attribute: parameters
37
+ parameters = DictType(
38
+ StringType,
39
+ default={},
40
+ metadata=dict(
41
+ description='The parameters for the application dependency.'
42
+ ),
43
+ )
44
+
45
+
46
+ # ** model: app_interface
47
+ class AppInterface(Entity):
48
+ '''
49
+ The base application interface object.
50
+ '''
51
+
52
+ # * attribute: name
53
+ name = StringType(
54
+ required=True,
55
+ metadata=dict(
56
+ description='The name of the application interface.'
57
+ ),
58
+ )
59
+
60
+ # * attribute: module_path
61
+ module_path = StringType(
62
+ required=True,
63
+ metadata=dict(
64
+ description='The module path for the application instance context.'
65
+ ),
66
+ )
67
+
68
+ # * attribute: class_name
69
+ class_name = StringType(
70
+ required=True,
71
+ metadata=dict(
72
+ description='The class name for the application instance context.'
73
+ ),
74
+ )
75
+
76
+ # attribute: feature_flag
77
+ feature_flag = StringType(
78
+ default='default',
79
+ metadata=dict(
80
+ description='The feature flag.'
81
+ ),
82
+ )
83
+
84
+ # attribute: data_flag
85
+ data_flag = StringType(
86
+ default='default',
87
+ metadata=dict(
88
+ description='The data flag.'
89
+ ),
90
+ )
91
+
92
+ # * attribute: attributes
93
+ attributes = ListType(
94
+ ModelType(AppAttribute),
95
+ required=True,
96
+ default=[],
97
+ metadata=dict(
98
+ description='The application instance attributes.'
99
+ ),
100
+ )
101
+
102
+ # * attribute: constants
103
+ constants = DictType(
104
+ StringType,
105
+ default={},
106
+ metadata=dict(
107
+ description='The application dependency constants.'
108
+ ),
109
+ )
110
+
111
+ # * method: add_attribute
112
+ def add_attribute(self, module_path: str, class_name: str, attribute_id: str):
113
+ '''
114
+ Add a dependency attribute to the app interface.
115
+
116
+ :param module_path: The module path for the app dependency attribute.
117
+ :type module_path: str
118
+ :param class_name: The class name for the app dependency attribute.
119
+ :type class_name: str
120
+ :param attribute_id: The id for the app dependency attribute.
121
+ :type attribute_id: str
122
+ :return: The added dependency.
123
+ :rtype: AppDependency
124
+ '''
125
+
126
+ # Create a new AppDependency object.
127
+ dependency = ValueObject.new(
128
+ AppAttribute,
129
+ module_path=module_path,
130
+ class_name=class_name,
131
+ attribute_id=attribute_id
132
+ )
133
+
134
+ # Add the dependency to the list of dependencies.
135
+ self.attributes.append(dependency)
136
+
137
+ # * method: get_attribute
138
+ def get_attribute(self, attribute_id: str) -> AppAttribute:
139
+ '''
140
+ Get the dependency attribute by attribute id.
141
+
142
+ :param attribute_id: The attribute id of the dependency attribute.
143
+ :type attribute_id: str
144
+ :return: The dependency attribute.
145
+ :rtype: AppAttribute
146
+ '''
147
+
148
+ # Get the dependency attribute by attribute id.
149
+ return next((attr for attr in self.attributes if attr.attribute_id == attribute_id), None)
150
+