wizlib 3.3.4__tar.gz → 3.3.6__tar.gz
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.
Potentially problematic release.
This version of wizlib might be problematic. Click here for more details.
- {wizlib-3.3.4 → wizlib-3.3.6}/PKG-INFO +1 -1
- {wizlib-3.3.4 → wizlib-3.3.6}/pyproject.toml +1 -1
- wizlib-3.3.6/wizlib/config_handler.py +126 -0
- wizlib-3.3.4/wizlib/config_handler.py +0 -101
- {wizlib-3.3.4 → wizlib-3.3.6}/PACKAGE.md +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/__init__.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/app.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/class_family.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/command.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/error.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/handler.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/io.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/parser.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/stream_handler.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/super_wrapper.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/test_case.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/ui/__init__.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/ui/shell/__init__.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/ui/shell/line_editor.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/ui/shell_ui.py +0 -0
- {wizlib-3.3.4 → wizlib-3.3.6}/wizlib/ui_handler.py +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from argparse import Namespace
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import re
|
|
7
|
+
import shlex
|
|
8
|
+
import subprocess
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from wizlib.handler import Handler
|
|
14
|
+
from wizlib.error import ConfigHandlerError
|
|
15
|
+
from wizlib.parser import WizParser
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConfigHandler(Handler):
|
|
19
|
+
"""
|
|
20
|
+
Handle app-level configuration, where settings could come from specific
|
|
21
|
+
settings (such as from argparse), environment variables, or a YAML file.
|
|
22
|
+
Within the Python code, config keys are underscore-separated all-lower.
|
|
23
|
+
|
|
24
|
+
A ConfigHandler returns null in the case of a missing value, assuming that
|
|
25
|
+
commands can handle their own null cases.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name = 'config'
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def setup(cls, val):
|
|
32
|
+
"""Allow for alternative setup, passing in an injected data value as a
|
|
33
|
+
dict, bypassing file loading, for testing. Possible long-term
|
|
34
|
+
alternative to .fake() below."""
|
|
35
|
+
|
|
36
|
+
if isinstance(val, str) or isinstance(val, Path):
|
|
37
|
+
return cls(file=val)
|
|
38
|
+
elif isinstance(val, dict) or isinstance(val, list):
|
|
39
|
+
return cls(data=val)
|
|
40
|
+
|
|
41
|
+
def __init__(self, file: str = None, data: dict = None):
|
|
42
|
+
"""Initiatlize with either a yaml file path or a data block to inject
|
|
43
|
+
(for testing)"""
|
|
44
|
+
self.file = file
|
|
45
|
+
self.injected_data = data
|
|
46
|
+
self.cache = {}
|
|
47
|
+
|
|
48
|
+
@cached_property
|
|
49
|
+
def data(self):
|
|
50
|
+
"""Returns the full set of configuration data, typically loaded from a
|
|
51
|
+
yaml file. Is cached in code."""
|
|
52
|
+
|
|
53
|
+
# If yaml_dict was provided, use it directly
|
|
54
|
+
if self.injected_data is not None:
|
|
55
|
+
return self.injected_data
|
|
56
|
+
|
|
57
|
+
path = None
|
|
58
|
+
if self.file:
|
|
59
|
+
path = Path(self.file)
|
|
60
|
+
elif self.app and self.app.name:
|
|
61
|
+
localpath = Path.cwd() / f".{self.app.name}.yml"
|
|
62
|
+
homepath = Path.home() / f".{self.app.name}.yml"
|
|
63
|
+
if (envvar := self.env(self.app.name + '-config')):
|
|
64
|
+
path = Path(envvar)
|
|
65
|
+
elif (localpath.is_file()):
|
|
66
|
+
path = localpath
|
|
67
|
+
elif (homepath.is_file()):
|
|
68
|
+
path = homepath
|
|
69
|
+
if path:
|
|
70
|
+
with open(path) as file:
|
|
71
|
+
data = yaml.safe_load(file)
|
|
72
|
+
return data
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def env(name):
|
|
76
|
+
if (envvar := name.upper().replace('-', '_')) in os.environ:
|
|
77
|
+
return os.environ[envvar]
|
|
78
|
+
|
|
79
|
+
def get(self, data_path: str):
|
|
80
|
+
"""Return the value for the requested config entry. If the value is a
|
|
81
|
+
string, evaluate it for shell-type expressions using $(...) syntax. Can
|
|
82
|
+
also return a dict or array. Note that the value returned is cached
|
|
83
|
+
against the data_path, so future calls may not address nested paths.
|
|
84
|
+
|
|
85
|
+
data_path: Hyphen-separated path through the yaml/dict hierarchy."""
|
|
86
|
+
|
|
87
|
+
# If we already found the value, return it
|
|
88
|
+
if data_path in self.cache:
|
|
89
|
+
return self.cache[data_path]
|
|
90
|
+
|
|
91
|
+
# Environment variables take precedence
|
|
92
|
+
if (result := self.env(data_path)):
|
|
93
|
+
self.cache[data_path] = result
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
# Otherwise look at the YAML or injected data
|
|
97
|
+
if (data := self.data):
|
|
98
|
+
split = data_path.split('-')
|
|
99
|
+
while (key := split.pop(0)) and (key in data):
|
|
100
|
+
data = data[key] if key in data else None
|
|
101
|
+
if not split:
|
|
102
|
+
if isinstance(data, str):
|
|
103
|
+
data = evaluate_string(data)
|
|
104
|
+
self.cache[data_path] = data
|
|
105
|
+
return data
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def fake(cls, **vals):
|
|
109
|
+
"""Return a fake ConfigHandler with forced values, for testing"""
|
|
110
|
+
self = cls()
|
|
111
|
+
self.cache = {k.replace('_', '-'): vals[k] for k in vals}
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def os_process(match):
|
|
116
|
+
"""Run a subprocess in shell form"""
|
|
117
|
+
command_string = match.group(1).strip()
|
|
118
|
+
command = shlex.split(command_string)
|
|
119
|
+
result = subprocess.run(command, capture_output=True)
|
|
120
|
+
return result.stdout.decode().strip()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def evaluate_string(yaml: str) -> str:
|
|
124
|
+
"""Evaluate shell commands in string values"""
|
|
125
|
+
text = yaml.strip()
|
|
126
|
+
return re.sub(r'\$\((.*?)\)', os_process, text)
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
from argparse import Namespace
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import os
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
import re
|
|
6
|
-
import shlex
|
|
7
|
-
import subprocess
|
|
8
|
-
from unittest.mock import patch
|
|
9
|
-
|
|
10
|
-
from yaml import load
|
|
11
|
-
from yaml import Loader
|
|
12
|
-
from wizlib.handler import Handler
|
|
13
|
-
|
|
14
|
-
from wizlib.error import ConfigHandlerError
|
|
15
|
-
from wizlib.parser import WizParser
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ConfigHandler(Handler):
|
|
19
|
-
"""
|
|
20
|
-
Handle app-level configuration, where settings could come from specific
|
|
21
|
-
settings (such as from argparse), environment variables, or a YAML file.
|
|
22
|
-
Within the Python code, config keys are underscore-separated all-lower.
|
|
23
|
-
|
|
24
|
-
A ConfigHandler returns null in the case of a missing value, assuming that
|
|
25
|
-
commands can handle their own null cases.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
name = 'config'
|
|
29
|
-
|
|
30
|
-
def __init__(self, file=None):
|
|
31
|
-
self.file = file
|
|
32
|
-
self.cache = {}
|
|
33
|
-
|
|
34
|
-
@property
|
|
35
|
-
def yaml(self):
|
|
36
|
-
if hasattr(self, '_yaml'):
|
|
37
|
-
return self._yaml
|
|
38
|
-
path = None
|
|
39
|
-
if self.file:
|
|
40
|
-
path = Path(self.file)
|
|
41
|
-
elif self.app and self.app.name:
|
|
42
|
-
localpath = Path.cwd() / f".{self.app.name}.yml"
|
|
43
|
-
homepath = Path.home() / f".{self.app.name}.yml"
|
|
44
|
-
if (envvar := self.env(self.app.name + '-config')):
|
|
45
|
-
path = Path(envvar)
|
|
46
|
-
elif (localpath.is_file()):
|
|
47
|
-
path = localpath
|
|
48
|
-
elif (homepath.is_file()):
|
|
49
|
-
path = homepath
|
|
50
|
-
if path:
|
|
51
|
-
with open(path) as file:
|
|
52
|
-
self._yaml = load(file, Loader=Loader)
|
|
53
|
-
return self._yaml
|
|
54
|
-
|
|
55
|
-
@staticmethod
|
|
56
|
-
def env(name):
|
|
57
|
-
if (envvar := name.upper().replace('-', '_')) in os.environ:
|
|
58
|
-
return os.environ[envvar]
|
|
59
|
-
|
|
60
|
-
def get(self, key: str):
|
|
61
|
-
"""Return the value for the requested config entry"""
|
|
62
|
-
|
|
63
|
-
# If we already found the value, return it
|
|
64
|
-
if key in self.cache:
|
|
65
|
-
return self.cache[key]
|
|
66
|
-
|
|
67
|
-
# Environment variables take precedence
|
|
68
|
-
if (result := self.env(key)):
|
|
69
|
-
self.cache[key] = result
|
|
70
|
-
return result
|
|
71
|
-
|
|
72
|
-
# Otherwise look at the YAML
|
|
73
|
-
if (yaml := self.yaml):
|
|
74
|
-
split = key.split('-')
|
|
75
|
-
while (val := split.pop(0)) and (val in yaml):
|
|
76
|
-
yaml = yaml[val] if val in yaml else None
|
|
77
|
-
if not split:
|
|
78
|
-
result = evaluate_yaml_value(yaml)
|
|
79
|
-
self.cache[key] = result
|
|
80
|
-
return result
|
|
81
|
-
|
|
82
|
-
@classmethod
|
|
83
|
-
def fake(cls, **vals):
|
|
84
|
-
"""Return a fake ConfigHandler with forced values, for testing"""
|
|
85
|
-
self = cls()
|
|
86
|
-
self.cache = {k.replace('_', '-'): vals[k] for k in vals}
|
|
87
|
-
return self
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def os_process(match):
|
|
91
|
-
"""Run a subprocess"""
|
|
92
|
-
command_string = match.group(1).strip()
|
|
93
|
-
command = shlex.split(command_string)
|
|
94
|
-
result = subprocess.run(command, capture_output=True)
|
|
95
|
-
return result.stdout.decode().strip()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def evaluate_yaml_value(yaml: str) -> str:
|
|
99
|
-
"""When getting a value from YAML, evaluate shell commands"""
|
|
100
|
-
text = yaml.strip()
|
|
101
|
-
return re.sub(r'\$\((.*?)\)', os_process, text)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|