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/repos/error.py ADDED
@@ -0,0 +1,173 @@
1
+ # *** imports
2
+
3
+ # ** core
4
+ from typing import List, Dict
5
+
6
+ # ** app
7
+ from ..domain.error import Error
8
+ from ..clients import yaml as yaml_client
9
+ from ..data.error import ErrorData
10
+
11
+
12
+ # *** repository
13
+
14
+ # ** interface: error_repository
15
+ class ErrorRepository(object):
16
+
17
+ # * method: exists
18
+ def exists(self, id: str, **kwargs) -> bool:
19
+ '''
20
+ Check if the error exists.
21
+
22
+ :param id: The error id.
23
+ :type id: str
24
+ :param kwargs: Additional keyword arguments.
25
+ :type kwargs: dict
26
+ :return: Whether the error exists.
27
+ :rtype: bool
28
+ '''
29
+
30
+ # Not implemented.
31
+ raise NotImplementedError()
32
+
33
+ # * method: get
34
+ def get(self, id: str) -> Error:
35
+ '''
36
+ Get the error.
37
+
38
+ :param id: The error id.
39
+ :type id: str
40
+ :return: The error.
41
+ :rtype: Error
42
+ '''
43
+
44
+ # Not implemented.
45
+ raise NotImplementedError()
46
+
47
+ # * method: list
48
+ def list(self) -> List[Error]:
49
+ '''
50
+ List all errors.
51
+
52
+ :return: The list of errors.
53
+ :rtype: List[Error]
54
+ '''
55
+
56
+ # Not implemented.
57
+ raise NotImplementedError()
58
+
59
+ # * method: save
60
+ def save(self, error: Error):
61
+ '''
62
+ Save the error.
63
+
64
+ :param error: The error.
65
+ :type error: Error
66
+ '''
67
+
68
+ # Not implemented.
69
+ raise NotImplementedError
70
+
71
+
72
+ # ** proxy: yaml_proxy
73
+ class YamlProxy(ErrorRepository):
74
+ '''
75
+ The YAML proxy for the error repository
76
+ '''
77
+
78
+ # * field: config_file
79
+ config_file: str
80
+
81
+ # * method: init
82
+ def __init__(self, error_config_file: str):
83
+ '''
84
+ Initialize the yaml proxy.
85
+
86
+ :param error_config_file: The error configuration file.
87
+ :type error_config_file: str
88
+ '''
89
+
90
+ # Set the base path.
91
+ self.config_file = error_config_file
92
+
93
+ # * method: exists
94
+ def exists(self, id: str, **kwargs) -> bool:
95
+ '''
96
+ Check if the error exists.
97
+
98
+ :param id: The error id.
99
+ :type id: str
100
+ :param kwargs: Additional keyword arguments.
101
+ :type kwargs: dict
102
+ :return: Whether the error exists.
103
+ :rtype: bool
104
+ '''
105
+
106
+ # Load the error data from the yaml configuration file.
107
+ data: List[ErrorData] = yaml_client.load(
108
+ self.config_file,
109
+ create_data=lambda data: ErrorData.from_yaml_data(
110
+ id=id, **data),
111
+ start_node=lambda data: data.get('errors').get(id))
112
+
113
+ # Return whether the error exists.
114
+ return data is not None
115
+
116
+ # * method: get
117
+ def get(self, id: str) -> Error:
118
+ '''
119
+ Get the error.
120
+
121
+ :param id: The error id.
122
+ :type id: str
123
+ :return: The error.
124
+ :rtype: Error
125
+ '''
126
+
127
+ # Load the error data from the yaml configuration file.
128
+ _data: ErrorData = yaml_client.load(
129
+ self.config_file,
130
+ create_data=lambda data: ErrorData.from_yaml_data(
131
+ id=id, **data),
132
+ start_node=lambda data: data.get('errors').get(id))
133
+
134
+ # Return the error object.
135
+ return _data.map('to_object.yaml')
136
+
137
+ # * method: list
138
+ def list(self) -> List[Error]:
139
+ '''
140
+ List all errors.
141
+
142
+ :return: The list of errors.
143
+ :rtype: List[Error]
144
+ '''
145
+
146
+ # Load the error data from the yaml configuration file.
147
+ _data: Dict[str, ErrorData] = yaml_client.load(
148
+ self.config_file,
149
+ create_data=lambda data: ErrorData.from_yaml_data(
150
+ id=id, **data),
151
+ start_node=lambda data: data.get('errors'))
152
+
153
+ # Return the error object.
154
+ return [data.map('to_object.yaml') for data in _data.values()]
155
+
156
+ # * method: save
157
+ def save(self, error: Error):
158
+ '''
159
+ Save the error.
160
+
161
+ :param error: The error.
162
+ :type error: Error
163
+ '''
164
+
165
+ # Create updated error data.
166
+ error_data = ErrorData.new(**error.to_primitive())
167
+
168
+ # Update the error data.
169
+ yaml_client.save(
170
+ path=self.config_file,
171
+ data=error_data,
172
+ data_save_path=f'errors/{error.name}',
173
+ )
app/repos/feature.py ADDED
@@ -0,0 +1,169 @@
1
+ from typing import Dict, Any, List
2
+
3
+ from ..data.feature import FeatureData
4
+ from ..domain.feature import Feature
5
+
6
+ from ..clients import yaml as yaml_client
7
+
8
+
9
+ class FeatureRepository(object):
10
+ '''
11
+ Feature repository interface.
12
+ '''
13
+
14
+ def exists(self, id: str) -> bool:
15
+ '''
16
+ Verifies if the feature exists.
17
+
18
+ :param id: The feature id.
19
+ :type id: str
20
+ :return: Whether the feature exists.
21
+ :rtype: bool
22
+ '''
23
+
24
+ raise NotImplementedError()
25
+
26
+ def get(self, id: str) -> Feature:
27
+ '''
28
+ Get the feature by id.
29
+
30
+ :param id: The feature id.
31
+ :type id: str
32
+ :return: The feature object.
33
+ :rtype: f.Feature
34
+ '''
35
+
36
+ raise NotImplementedError()
37
+
38
+ def list(self, group_id: str = None) -> List[Feature]:
39
+ '''
40
+ List the features.
41
+
42
+ :param group_id: The group id.
43
+ :type group_id: str
44
+ :return: The list of features.
45
+ :rtype: list
46
+ '''
47
+
48
+ raise NotImplementedError
49
+
50
+ def save(self, feature: Feature):
51
+ '''
52
+ Save the feature.
53
+
54
+ :param feature: The feature object.
55
+ :type feature: f.Feature
56
+ '''
57
+
58
+ raise NotImplementedError()
59
+
60
+
61
+ class YamlProxy(FeatureRepository):
62
+ '''
63
+ Yaml repository for features.
64
+ '''
65
+
66
+ def __init__(self, feature_yaml_base_path: str):
67
+ '''
68
+ Initialize the yaml repository.
69
+
70
+ :param feature_yaml_base_path: The base path to the yaml file.
71
+ :type feature_yaml_base_path: str
72
+ '''
73
+
74
+ # Set the base path.
75
+ self.base_path = feature_yaml_base_path
76
+
77
+ def exists(self, id: str) -> bool:
78
+ '''
79
+ Verifies if the feature exists.
80
+
81
+ :param id: The feature id.
82
+ :type id: str
83
+ :return: Whether the feature exists.
84
+ :rtype: bool
85
+ '''
86
+
87
+ # Retrieve the feature by id.
88
+ feature = self.get(id)
89
+
90
+ # Return whether the feature exists.
91
+ return feature is not None
92
+
93
+ def get(self, id: str) -> Feature:
94
+ '''
95
+ Get the feature by id.
96
+
97
+ :param id: The feature id.
98
+ :type id: str
99
+ :return: The feature object.
100
+ '''
101
+
102
+ # Get context group and feature key from the id.
103
+ group_id, feature_key = id.split('.')
104
+
105
+ # Load feature data from yaml.
106
+ _data: FeatureData = yaml_client.load(
107
+ self.base_path,
108
+ create_data=lambda data: FeatureData.from_yaml_data(
109
+ id=id,
110
+ group_id=group_id,
111
+ **data
112
+ ),
113
+ start_node=lambda data: data.get('features').get('groups').get(group_id).get('features').get(feature_key)
114
+ )
115
+
116
+ # Return None if feature data is not found.
117
+ if not _data:
118
+ return None
119
+
120
+ # Return feature.
121
+ return _data.map('to_object.yaml')
122
+
123
+ def list(self, group_id: str = None) -> List[Feature]:
124
+ '''
125
+ List the features.
126
+
127
+ :param group_id: The group id.
128
+ :type group_id: str
129
+ :return: The list of features.
130
+ :rtype: list
131
+ '''
132
+
133
+ # Load all feature data from yaml.
134
+ features = yaml_client.load(
135
+ self.base_path,
136
+ create_data=lambda data: [FeatureData.from_yaml_data(
137
+ id=id,
138
+ **feature_data
139
+ ) for id, feature_data in data.items()],
140
+ start_node=lambda data: data.get('features')
141
+ )
142
+
143
+ # Filter features by group id.
144
+ if group_id:
145
+ features = [feature for feature in features if feature.group_id == group_id]
146
+
147
+ # Return the list of features.
148
+ return [feature.map('to_object.yaml') for feature in features]
149
+
150
+ def save(self, feature: Feature):
151
+ '''
152
+ Save the feature.
153
+
154
+ :param feature: The feature object.
155
+ :type feature: f.Feature
156
+ '''
157
+
158
+ # Create updated feature data.
159
+ feature_data = FeatureData.new(**feature.to_primitive())
160
+
161
+ # Update the feature data.
162
+ yaml_client.save(
163
+ self.base_path,
164
+ data=feature_data,
165
+ data_save_path=f'features/{feature.group_id}.{feature_data.feature_key}'
166
+ )
167
+
168
+ # Return the updated feature object.
169
+ return feature_data.map('to_object.yaml')
@@ -0,0 +1,4 @@
1
+ from . import cli as cli_service
2
+ from . import container as container_service
3
+ from . import object as object_service
4
+ from . import sync as sync_service
app/services/cli.py ADDED
@@ -0,0 +1,186 @@
1
+ from typing import List, Dict, Any
2
+ import argparse
3
+
4
+ from ..contexts.request import RequestContext
5
+ from ..domain.cli import CliInterface
6
+ from ..domain.cli import CliArgument
7
+
8
+
9
+ def create_argument_data(cli_argument: CliArgument):
10
+
11
+ # List fields to be excluded.
12
+ exclude_fields = ['name_or_flags', 'arg_type', 'to_data']
13
+
14
+ # Exclude unneeded fields if the argument contains an action.
15
+ if cli_argument.action:
16
+ exclude_fields.append('nargs')
17
+ exclude_fields.append('type')
18
+ exclude_fields.append('choices')
19
+
20
+ # Set the data type for the argument.
21
+ if cli_argument.type == 'str':
22
+ data_type = str
23
+ elif cli_argument.type == 'int':
24
+ data_type = int
25
+ elif cli_argument.type == 'float':
26
+ data_type = float
27
+
28
+ # For each name or flag in the argument,
29
+ for name in cli_argument.name_or_flags:
30
+
31
+ # If the name or flag is a positional argument,
32
+ if not name.startswith('--'):
33
+
34
+ # Remove the required field.
35
+ exclude_fields.append('required')
36
+
37
+ # Assemble the argument data.
38
+ argument_data = cli_argument.to_primitive()
39
+
40
+ # Set the data type.
41
+ argument_data['type'] = data_type
42
+
43
+ # For each field in the excluded fields,
44
+ for field in exclude_fields:
45
+
46
+ # Remove the field from the argument data if it is present.
47
+ try:
48
+ del argument_data[field]
49
+ except KeyError:
50
+ pass
51
+
52
+ # Return the argument data.
53
+ return argument_data
54
+
55
+
56
+ def create_headers(data: dict):
57
+ headers = dict(
58
+ group_id=data.pop('group'),
59
+ command_id=data.pop('command'),
60
+ )
61
+ headers['id'] = f"{headers['group_id']}.{headers['command_id']}".replace(
62
+ '-', '_')
63
+ return headers
64
+
65
+
66
+ def create_cli_parser(cli_interface: CliInterface):
67
+
68
+ # Format commands into a dictionary lookup by group id.
69
+ commands = {}
70
+ for command in cli_interface.commands:
71
+ group_id = command.group_id
72
+ if group_id not in commands:
73
+ commands[group_id] = []
74
+ commands[group_id].append(command)
75
+
76
+ # Create parser.
77
+ parser = argparse.ArgumentParser()
78
+
79
+ # Add command subparsers
80
+ command_subparsers = parser.add_subparsers(dest='group')
81
+ for group_id, commands in commands.items():
82
+ group_name = group_id.replace('_', '-')
83
+ command_subparser = command_subparsers.add_parser(
84
+ group_name)
85
+ subcommand_subparsers = command_subparser.add_subparsers(
86
+ dest='command')
87
+ for command in commands:
88
+ command_name = command.id.split('.')[-1].replace('_', '-')
89
+ subcommand_subparser = subcommand_subparsers.add_parser(
90
+ command_name)
91
+ for argument in command.arguments:
92
+ subcommand_subparser.add_argument(
93
+ *argument.name_or_flags, **create_argument_data(argument))
94
+ for argument in cli_interface.parent_arguments:
95
+ subcommand_subparser.add_argument(
96
+ *argument.name_or_flags, **create_argument_data(argument))
97
+
98
+ return parser
99
+
100
+
101
+ def create_request(request: argparse.Namespace, cli_interface: CliInterface, **kwargs) -> RequestContext:
102
+
103
+ # Convert argparse.Namespace to dictionary.
104
+ data = vars(request)
105
+
106
+ # Create header values.
107
+ headers = create_headers(data)
108
+
109
+ # Get the command from the CLI interface.
110
+ command = cli_interface.get_command(**headers)
111
+
112
+ # Create map of arguments to their data attribute names.
113
+ argument_map = {arg.get_name(): arg for arg in command.arguments}
114
+
115
+ # Map the data to the request context.
116
+ for key, value in data.items():
117
+ if value is None:
118
+ continue
119
+ argument = argument_map.get(key)
120
+ data[key] = map_object_input(value, argument)
121
+
122
+ # Create request context.
123
+ return RequestContext(
124
+ feature_id=command.feature_id,
125
+ data=data,
126
+ headers=headers,
127
+ **headers,
128
+ **kwargs
129
+ )
130
+
131
+
132
+ def map_object_input(data: Any, argument: CliArgument):
133
+
134
+ # If the argument is not input to data,
135
+ if not argument.to_data:
136
+
137
+ # Return the data.
138
+ return data
139
+
140
+ # If the argument is a dictionary,
141
+ if argument.nargs:
142
+
143
+ # If the argument is a list, split the data by the delimiter.
144
+ result = {}
145
+
146
+ # For each item in the data, split the item into key and value.
147
+ for item in data:
148
+
149
+ # Split the item into key and value.
150
+ key, value = item.split('=')
151
+
152
+ # Add the key and value to the result.
153
+ result[key] = value
154
+
155
+ # Return result.
156
+ return result
157
+
158
+ # If the argument is an object list,
159
+ if argument.action == 'append':
160
+
161
+ # Create a list to store the result.
162
+ result = []
163
+
164
+ # For each row object in the data,
165
+ for row in data:
166
+
167
+ # Create an object to store the key value pairs.
168
+ obj = {}
169
+
170
+ # Split the row by the delimiter.
171
+ items = row.split(';')
172
+
173
+ # For each item in the row,
174
+ for item in items:
175
+
176
+ # Split the item into key and value.
177
+ key, value = item.split('=')
178
+
179
+ # Add the key and value to the object.
180
+ obj[key] = value
181
+
182
+ # Add the object to the result.
183
+ result.append(obj)
184
+
185
+ # Return result.
186
+ return result
@@ -0,0 +1,44 @@
1
+ # *** imports
2
+
3
+ # ** core
4
+ from typing import Any
5
+
6
+ # ** infra
7
+ from dependencies import Injector
8
+
9
+
10
+ # *** functions
11
+
12
+ # ** function: import_dependency
13
+ def import_dependency(module_path: str, class_name: str) -> Any:
14
+ '''
15
+ Import an object dependency from its configured Python module.
16
+
17
+ :param module_path: The module path.
18
+ :type module_path: str
19
+ :param class_name: The class name.
20
+ :type class_name: str
21
+ :return: The dependency.
22
+ :rtype: Any
23
+ '''
24
+
25
+ # Import module.
26
+ from importlib import import_module
27
+ return getattr(import_module(module_path), class_name)
28
+
29
+
30
+ # ** function: create_injector
31
+ def create_injector(name: str, **dependencies) -> Any:
32
+ '''
33
+ Create an injector object with the given dependencies.
34
+
35
+ :param name: The name of the injector.
36
+ :type name: str
37
+ :param dependencies: The dependencies.
38
+ :type dependencies: dict
39
+ :return: The injector object.
40
+ :rtype: Any
41
+ '''
42
+
43
+ # Create container.
44
+ return type(f'{name.capitalize()}Container', (Injector,), {**dependencies})
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, Great Strength Systems LLC
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.1
2
+ Name: tiferet
3
+ Version: 1.0.0a0
4
+ Summary: A multi-purpose application framework embodying beauty in form.
5
+ Home-page: https://github.com/greatstrength/app
6
+ Download-URL: https://github.com/greatstrength/app
7
+ Author: Andrew Shatz
8
+ Author-email: andrew@greatstrength.me
9
+ License: BSD 3
10
+ License-File: LICENSE
11
+ Requires-Dist: schematics >=2.1.1
12
+ Requires-Dist: pyyaml >=6.0.1
13
+
@@ -0,0 +1,41 @@
1
+ app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ app/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ app/clients/yaml.py,sha256=-Eo8PX2oPpVxv1c-cpkNOrGhxx9T7XPYoAyGi1DxMi4,2454
4
+ app/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ app/commands/container.py,sha256=L2IKJqEbRglw_LC144oYAkARr2Rixj8IFuC6Ro1jAqA,1789
6
+ app/commands/error.py,sha256=ixMYH0yrEWlMAVGDSRke3IGoWaOShfmiXntrUC2b3wY,573
7
+ app/commands/feature.py,sha256=tXa-MDf14ByIJqwQFeSUu2QLUjHPdsyvFNhj1XeaQVk,2507
8
+ app/configs/__init__.py,sha256=NfT6XFNcJznOLXjMuNmj3XeaOPVWwbc-N4kVWaVjuD0,845
9
+ app/configs/app.py,sha256=YWKVPTCaCVwpmTbdUdeZya-IfJvxxenGQpcUD7YbU54,302
10
+ app/configs/container.py,sha256=x9JwWi7XRRnoVahf858REYNY3ZxCaznjGJUqoAK1FZs,2106
11
+ app/contexts/__init__.py,sha256=0HzyDBIyyF8fZ0gOXekzfzHWVkbLFUx9eLLzuooEzFs,63
12
+ app/contexts/app.py,sha256=MPRFu3ut7DOmc3i3PqLR96ymvWMShW6BUbY9eG8i6Eg,3296
13
+ app/contexts/container.py,sha256=RI33tK9XF18PaUfOp64ddMZGOCMIy3XNgQMUtJ_jYNE,4924
14
+ app/contexts/env.py,sha256=bbvZzDy075SuKL2ZIfDEE5yD1Hbk-4XLGe4eC_tT8e8,3080
15
+ app/contexts/error.py,sha256=VO3Wgkmf5bdK6LoT0xhQn1WeSAVulQwUhvBAsoUPd1c,2503
16
+ app/contexts/feature.py,sha256=u7wEhdfTLeWKIv5i2nzBcu4cAcVkm6iqNNznczPmHjc,2293
17
+ app/contexts/request.py,sha256=TOECa0V1wKuNGCYFIRKxwsZVpLtP0FS_Nm96DaEVsWU,2726
18
+ app/data/__init__.py,sha256=ts3bkFftArKTgUJj7q65Sob4fC6jOqfYNxm6cqjFr_w,74
19
+ app/data/app.py,sha256=8kv7tDJUjl70bqdpiMXXs6e89zDQIvtlDSOq42iyyX0,6312
20
+ app/data/container.py,sha256=jTQlT4D-lLC_QLNAmMara6O78g667GXk1LU_EyuX_HU,5180
21
+ app/data/error.py,sha256=lfGO3SjtsxSv6d7iKDeVw3vXfocCk29g4-uwC3jnaig,2377
22
+ app/data/feature.py,sha256=s_aqVYhH7VKzXpWM3d-uvSv3_r8A0LgObv3Agg-CKoc,3697
23
+ app/domain/__init__.py,sha256=uC1lHXzRnpoHqfgPcUraea5F_T1Nsx0wBau0paslsQg,146
24
+ app/domain/app.py,sha256=t_TcemJfpSa_CQfjcSfRRo2iFS1ilanfpHTiHx1vM8w,8528
25
+ app/domain/container.py,sha256=Kw_xNn5_tWghcx3Dl_vPEdwZ2b8u23ZpbgMQ_bkUKaw,3666
26
+ app/domain/core.py,sha256=a1nJvH7bCqP_JvGrkMDClH8nef2L3WHpmTV6ksmdZ4c,4600
27
+ app/domain/error.py,sha256=myUpdB4rgmbVBwIP8Pa88uastTSjPFGqrSwqR9C-VYg,3371
28
+ app/domain/feature.py,sha256=RhedOKb8nG1D0J9b_aPVtJc_rQjLXwOpwpIsmzrH21o,4552
29
+ app/repos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ app/repos/app.py,sha256=VptevFxeST5slbk_em7dazXgz5U8f_XLDsGdM0Gq3qE,2696
31
+ app/repos/container.py,sha256=2Medggn4BZrelnZ-cDZfFFwHaDB80R7iFJ5OF-6E62g,4811
32
+ app/repos/error.py,sha256=c3Bs2QTktMuJWGSRF8rKeTN5vFEsaOycUdYD7QSIDEc,4224
33
+ app/repos/feature.py,sha256=uR4s-n28J4WCO4Npi6AzOxk5R9I6HuUMHDzoK1Xo_80,4361
34
+ app/services/__init__.py,sha256=CIyUCm2oFrhZvCLPCPz3UG-eghdkIBgqayxoOr-QrdE,151
35
+ app/services/cli.py,sha256=aqrn7kxqe2xTJWBSs5hDiyD0ND50mPVDIJf_C87YgRk,5364
36
+ app/services/container.py,sha256=yZU09pziCMz74UIRqiM_gHNqNE4Hml555WEuNoJlEDA,1048
37
+ tiferet-1.0.0a0.dist-info/LICENSE,sha256=e8_GutFM0sxbRlgUaeVsGvJ5uE-KvruLApOzIoHy_zU,1513
38
+ tiferet-1.0.0a0.dist-info/METADATA,sha256=kxGZQgEojSufeAWAvLtuL0RV4-Z58KIBcI9hUkbQ5kE,386
39
+ tiferet-1.0.0a0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
40
+ tiferet-1.0.0a0.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
41
+ tiferet-1.0.0a0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.5.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ app