directed-inputs-class 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.
@@ -0,0 +1,14 @@
1
+ """Directed Inputs Class.
2
+
3
+ This package provides tools for managing and processing directed inputs from
4
+ various sources like environment variables, stdin, and predefined dictionaries.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .__main__ import DirectedInputsClass
10
+
11
+
12
+ __version__ = "0.1.0"
13
+
14
+ __all__ = ["DirectedInputsClass"]
@@ -0,0 +1,233 @@
1
+ """Module to handle directed inputs for the DirectedInputsClass library.
2
+
3
+ This module provides functionality for managing inputs from various sources
4
+ (environment, stdin) and allows for dynamic merging, freezing, and thawing
5
+ of inputs. It includes methods to decode inputs from JSON, YAML, and Base64
6
+ formats, as well as handling boolean and integer conversions.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import binascii
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ from copy import deepcopy
17
+ from typing import Any
18
+
19
+ from case_insensitive_dict import CaseInsensitiveDict
20
+ from deepmerge import Merger # type: ignore[import-untyped]
21
+ from extended_data_types import (
22
+ base64_decode,
23
+ decode_json,
24
+ decode_yaml,
25
+ is_nothing,
26
+ strtobool,
27
+ )
28
+ from yaml import YAMLError
29
+
30
+
31
+ class DirectedInputsClass:
32
+ """A class to manage and process directed inputs from environment variables.
33
+
34
+ stdin, or provided dictionaries.
35
+
36
+ Attributes:
37
+ inputs (CaseInsensitiveDict): Dictionary to store inputs.
38
+ frozen_inputs (CaseInsensitiveDict): Dictionary to store frozen inputs.
39
+ from_stdin (bool): Flag indicating if inputs were read from stdin.
40
+ merger (Merger): Object to manage deep merging of dictionaries.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ inputs: Any | None = None,
46
+ from_environment: bool = True,
47
+ from_stdin: bool = False,
48
+ ):
49
+ """Initializes the DirectedInputsClass with the provided inputs.
50
+
51
+ Optionally loading additional inputs from environment variables and stdin.
52
+
53
+ Args:
54
+ inputs (Any | None): Initial inputs to be processed.
55
+ from_environment (bool): Whether to load inputs from environment variables.
56
+ from_stdin (bool): Whether to load inputs from stdin.
57
+ """
58
+ if inputs is None:
59
+ inputs = {}
60
+
61
+ if from_environment:
62
+ env_inputs = dict(os.environ)
63
+ env_inputs.update(inputs)
64
+ inputs = env_inputs
65
+
66
+ if from_stdin and not strtobool(os.getenv("OVERRIDE_STDIN", "False")):
67
+ inputs_from_stdin = sys.stdin.read()
68
+
69
+ if not is_nothing(inputs_from_stdin):
70
+ try:
71
+ stdin_inputs = json.loads(inputs_from_stdin)
72
+ stdin_inputs.update(inputs)
73
+ inputs = stdin_inputs
74
+ except json.JSONDecodeError as exc:
75
+ message = f"Failed to decode stdin:\n{inputs_from_stdin}"
76
+ raise RuntimeError(message) from exc
77
+
78
+ self.from_stdin = from_stdin
79
+ self.inputs: CaseInsensitiveDict[str, Any] = CaseInsensitiveDict(inputs)
80
+ self.frozen_inputs: CaseInsensitiveDict[str, Any] = CaseInsensitiveDict()
81
+ self.merger = Merger(
82
+ [(list, ["append"]), (dict, ["merge"]), (set, ["union"])],
83
+ ["override"],
84
+ ["override"],
85
+ )
86
+
87
+ def get_input(
88
+ self,
89
+ k: str,
90
+ default: Any | None = None,
91
+ required: bool = False,
92
+ is_bool: bool = False,
93
+ is_integer: bool = False,
94
+ ) -> Any:
95
+ """Retrieves an input by key, with options for type conversion and default values.
96
+
97
+ Args:
98
+ k (str): The key for the input.
99
+ default (Any | None): The default value if the key is not found.
100
+ required (bool): Whether the input is required. Raises an error if required and not found.
101
+ is_bool (bool): Whether to convert the input to a boolean.
102
+ is_integer (bool): Whether to convert the input to an integer.
103
+
104
+ Returns:
105
+ Any: The retrieved input, potentially converted or defaulted.
106
+ """
107
+ inp = self.inputs.get(k, default)
108
+
109
+ if is_nothing(inp):
110
+ inp = default
111
+
112
+ if is_bool:
113
+ inp = strtobool(inp)
114
+
115
+ if is_integer and inp is not None:
116
+ try:
117
+ inp = int(inp)
118
+ except TypeError as exc:
119
+ message = f"Input {k} not an integer: {inp}"
120
+ raise RuntimeError(message) from exc
121
+
122
+ if is_nothing(inp) and required:
123
+ message = f"Required input {k} not passed from inputs:\n{self.inputs}"
124
+ raise RuntimeError(message)
125
+
126
+ return inp
127
+
128
+ def decode_input(
129
+ self,
130
+ k: str,
131
+ default: Any | None = None,
132
+ required: bool = False,
133
+ decode_from_json: bool = False,
134
+ decode_from_yaml: bool = False,
135
+ decode_from_base64: bool = False,
136
+ allow_none: bool = True,
137
+ ) -> Any:
138
+ """Decodes an input value, optionally from Base64, JSON, or YAML.
139
+
140
+ Args:
141
+ k (str): The key for the input.
142
+ default (Any | None): The default value if the key is not found.
143
+ required (bool): Whether the input is required. Raises an error if required and not found.
144
+ decode_from_json (bool): Whether to decode the input from JSON format.
145
+ decode_from_yaml (bool): Whether to decode the input from YAML format.
146
+ decode_from_base64 (bool): Whether to decode the input from Base64.
147
+ allow_none (bool): Whether to allow None as a valid return value.
148
+
149
+ Returns:
150
+ Any: The decoded input, potentially converted or defaulted.
151
+ """
152
+ conf = self.get_input(k, default=default, required=required)
153
+
154
+ if conf is None or conf == default:
155
+ return conf
156
+
157
+ if decode_from_base64:
158
+ try:
159
+ conf = base64_decode(
160
+ conf,
161
+ unwrap_raw_data=decode_from_json or decode_from_yaml,
162
+ encoding="json" if decode_from_json else "yaml",
163
+ )
164
+ except binascii.Error as exc:
165
+ message = f"Failed to decode {conf} from base64"
166
+ raise RuntimeError(message) from exc
167
+
168
+ if isinstance(conf, memoryview):
169
+ conf = conf.tobytes().decode("utf-8")
170
+ elif isinstance(conf, (bytes, bytearray)):
171
+ try:
172
+ conf = conf.decode("utf-8")
173
+ except UnicodeDecodeError as exc:
174
+ message = f"Failed to decode bytes to string: {conf}"
175
+ raise RuntimeError(message) from exc
176
+
177
+ if decode_from_yaml:
178
+ try:
179
+ conf = decode_yaml(conf)
180
+ except YAMLError as exc:
181
+ message = f"Failed to decode {conf} from YAML"
182
+ raise RuntimeError(message) from exc
183
+ elif decode_from_json:
184
+ try:
185
+ conf = decode_json(conf)
186
+ except json.JSONDecodeError as exc:
187
+ message = f"Failed to decode {conf} from JSON"
188
+ raise RuntimeError(message) from exc
189
+
190
+ if conf is None and not allow_none:
191
+ return default
192
+
193
+ return conf
194
+
195
+ def freeze_inputs(self) -> CaseInsensitiveDict[str, Any]:
196
+ """Freezes the current inputs, preventing further modifications until thawed.
197
+
198
+ Returns:
199
+ CaseInsensitiveDict: The frozen inputs.
200
+ """
201
+ if is_nothing(self.frozen_inputs):
202
+ self.frozen_inputs = deepcopy(self.inputs)
203
+ self.inputs = CaseInsensitiveDict()
204
+
205
+ return self.frozen_inputs
206
+
207
+ def thaw_inputs(self) -> CaseInsensitiveDict[str, Any]:
208
+ """Thaws the inputs, merging the frozen inputs back into the current inputs.
209
+
210
+ Returns:
211
+ CaseInsensitiveDict: The thawed inputs.
212
+ """
213
+ if is_nothing(self.inputs):
214
+ self.inputs = deepcopy(self.frozen_inputs)
215
+ self.frozen_inputs = CaseInsensitiveDict()
216
+ return self.inputs
217
+
218
+ self.inputs = self.merger.merge(
219
+ deepcopy(self.inputs), deepcopy(self.frozen_inputs)
220
+ )
221
+ self.frozen_inputs = CaseInsensitiveDict()
222
+ return self.inputs
223
+
224
+ def shift_inputs(self) -> CaseInsensitiveDict[str, Any]:
225
+ """Shifts between frozen and thawed inputs.
226
+
227
+ Returns:
228
+ CaseInsensitiveDict: The resulting inputs after the shift.
229
+ """
230
+ if is_nothing(self.frozen_inputs):
231
+ return self.freeze_inputs()
232
+
233
+ return self.thaw_inputs()
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.3
2
+ Name: directed-inputs-class
3
+ Version: 0.1.0
4
+ Summary: Directed inputs class consumes and processes inputs from sources beyond args and kwargs
5
+ Project-URL: Documentation, https://github.com/jbcom/directed-inputs-class#readme
6
+ Project-URL: Issues, https://github.com/jbcom/directed-inputs-class/issues
7
+ Project-URL: Source, https://github.com/jbcom/directed-inputs-class
8
+ Author-email: Jon Bogaty <jon@jonbogaty.com>
9
+ Maintainer-email: Jon Bogaty <jon@jonbogaty.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: python3
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.8
22
+ Requires-Dist: case-insensitive-dictionary>=0.2.1
23
+ Requires-Dist: deepmerge>=1.1.1
24
+ Requires-Dist: extended-data-types>=1.0.2
25
+ Requires-Dist: future>=1.0.0
26
+ Provides-Extra: docs
27
+ Requires-Dist: docutils>=0.17; extra == 'docs'
28
+ Requires-Dist: myst-parser>=3.0.1; extra == 'docs'
29
+ Requires-Dist: sphinx-autodoc2>=0.5.0; extra == 'docs'
30
+ Requires-Dist: sphinx<7.5,>=7.2; extra == 'docs'
31
+ Requires-Dist: sphinxawesome-theme>=5.2.0; extra == 'docs'
32
+ Provides-Extra: tests
33
+ Requires-Dist: coverage[toml]>=7.6.0; extra == 'tests'
34
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'tests'
35
+ Requires-Dist: pytest-mock>=3.14.0; extra == 'tests'
36
+ Requires-Dist: pytest-xdist>=3.6.1; extra == 'tests'
37
+ Requires-Dist: pytest>=8.2.2; extra == 'tests'
38
+ Provides-Extra: typing
39
+ Requires-Dist: mypy>=1.0.0; extra == 'typing'
40
+ Requires-Dist: sortedcontainers-stubs>=2.4.2; extra == 'typing'
41
+ Requires-Dist: types-pyyaml>=6.0.12.20240724; extra == 'typing'
42
+ Description-Content-Type: text/markdown
43
+
44
+
45
+ # Directed Inputs Class
46
+
47
+ ![Directed Inputs Class Logo](docs/_static/logo.webp)
48
+
49
+ *🛠️ Manage your Python inputs efficiently! 🎯*
50
+
51
+ [![CI Status](https://github.com/jbcom/directed-inputs-class/workflows/CI/badge.svg)](https://github.com/jbcom/directed-inputs-class/actions?query=workflow%3ACI)
52
+ [![Documentation Status](https://readthedocs.org/projects/directed-inputs-class/badge/?version=latest)](https://directed-inputs-class.readthedocs.io/en/latest/?badge=latest)
53
+ [![PyPI Package latest release](https://img.shields.io/pypi/v/directed-inputs-class.svg)](https://pypi.org/project/directed-inputs-class/)
54
+ [![Supported versions](https://img.shields.io/pypi/pyversions/directed-inputs-class.svg)](https://pypi.org/project/directed-inputs-class/)
55
+
56
+ Directed Inputs Class is a Python library that provides a flexible and robust interface for managing inputs from various sources such as environment variables, stdin, and predefined dictionaries. It offers features like input freezing, thawing, and advanced decoding utilities.
57
+
58
+ ## Key Features
59
+
60
+ - 🧩 **Environment Variable Integration** - Seamlessly integrates environment variables into your inputs.
61
+ - 📥 **Stdin Input Handling** - Read and merge inputs from stdin with optional overrides.
62
+ - ❄️ **Input Freezing and Thawing** - Freeze inputs to prevent modifications, and thaw them when needed.
63
+ - 🔄 **Advanced Decoding Utilities** - Decode inputs from Base64, JSON, and YAML formats with error handling.
64
+ - 🔧 **Type Conversion** - Convert inputs to boolean or integer types with robust error handling.
65
+
66
+ ### Example Usage
67
+
68
+ ```python
69
+ from directed_inputs_class import DirectedInputsClass
70
+
71
+ # Initialize with environment variables and stdin
72
+ dic = DirectedInputsClass(from_environment=True, from_stdin=True)
73
+
74
+ # Retrieve and decode an input
75
+ decoded_value = dic.decode_input("example_key", decode_from_base64=True)
76
+ print(decoded_value)
77
+ ```
78
+
79
+ ### Freezing and Thawing Inputs
80
+
81
+ ```python
82
+ from directed_inputs_class import DirectedInputsClass
83
+
84
+ # Initialize with some inputs
85
+ dic = DirectedInputsClass(inputs={"key1": "value1"})
86
+
87
+ # Freeze the inputs
88
+ frozen_inputs = dic.freeze_inputs()
89
+ print(frozen_inputs) # Outputs: {'key1': 'value1'}
90
+
91
+ # Thaw the inputs
92
+ thawed_inputs = dic.thaw_inputs()
93
+ print(thawed_inputs) # Outputs: {'key1': 'value1'}
94
+ ```
95
+
96
+ For more usage examples, see the [Usage](https://directed-inputs-class.readthedocs.io/en/latest/usage.md) documentation.
97
+
98
+ ## Contributing
99
+
100
+ Contributions are welcome! Please see the [Contributing Guidelines](https://github.com/jbcom/directed-inputs-class/blob/main/CONTRIBUTING.md) for more information.
101
+
102
+ ## Credit
103
+
104
+ Directed Inputs Class is written and maintained by [Your Name](mailto:yourname@example.com).
105
+
106
+ ## Project Links
107
+
108
+ - [**Get Help**](https://stackoverflow.com/questions/tagged/directed-inputs-class) (use the *directed-inputs-class* tag on
109
+ Stack Overflow)
110
+ - [**PyPI**](https://pypi.org/project/directed-inputs-class/)
111
+ - [**GitHub**](https://github.com/jbcom/directed-inputs-class)
112
+ - [**Documentation**](https://directed-inputs-class.readthedocs.io/en/latest/)
113
+ - [**Changelog**](https://github.com/jbcom/directed-inputs-class/tree/main/CHANGELOG.md)
@@ -0,0 +1,6 @@
1
+ directed_inputs_class/__init__.py,sha256=OnoxTORWlE7Qc3FDFsUyPCD3xiTpcHrx48KQUArrVjk,326
2
+ directed_inputs_class/__main__.py,sha256=KgrpMDfbqTxmiGXgjuvYJxXDyeBQQ7NTmdK9Vxl98Fs,8144
3
+ directed_inputs_class-0.1.0.dist-info/METADATA,sha256=CbMQrwl3rJfoBjwH7wqVozAh1xF4KLvH-DN7epY_RwI,5071
4
+ directed_inputs_class-0.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
5
+ directed_inputs_class-0.1.0.dist-info/licenses/LICENSE,sha256=0bZwPE-woPfUEqxkVpNKtp9j9x3XLYfixj1X9YQf5hE,1067
6
+ directed_inputs_class-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jon Bogaty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.