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.
- directed_inputs_class/__init__.py +14 -0
- directed_inputs_class/__main__.py +233 -0
- directed_inputs_class-0.1.0.dist-info/METADATA +113 -0
- directed_inputs_class-0.1.0.dist-info/RECORD +6 -0
- directed_inputs_class-0.1.0.dist-info/WHEEL +4 -0
- directed_inputs_class-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|
+

|
|
48
|
+
|
|
49
|
+
*🛠️ Manage your Python inputs efficiently! 🎯*
|
|
50
|
+
|
|
51
|
+
[](https://github.com/jbcom/directed-inputs-class/actions?query=workflow%3ACI)
|
|
52
|
+
[](https://directed-inputs-class.readthedocs.io/en/latest/?badge=latest)
|
|
53
|
+
[](https://pypi.org/project/directed-inputs-class/)
|
|
54
|
+
[](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,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.
|