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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wizlib
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: Framework for flexible and powerful command-line applications
5
5
  License: MIT
6
6
  Author: Steampunk Wizard
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "wizlib"
3
- version = "3.3.4"
3
+ version = "3.3.6"
4
4
  description = "Framework for flexible and powerful command-line applications"
5
5
  authors = ["Steampunk Wizard <wizlib@steampunkwizard.ca>"]
6
6
  license = "MIT"
@@ -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