wizlib 3.3.5__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.5
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.5"
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"
@@ -1,4 +1,5 @@
1
1
  from argparse import Namespace
2
+ from functools import cached_property
2
3
  from pathlib import Path
3
4
  import os
4
5
  from dataclasses import dataclass
@@ -7,10 +8,9 @@ import shlex
7
8
  import subprocess
8
9
  from unittest.mock import patch
9
10
 
10
- from yaml import load
11
- from yaml import Loader
12
- from wizlib.handler import Handler
11
+ import yaml
13
12
 
13
+ from wizlib.handler import Handler
14
14
  from wizlib.error import ConfigHandlerError
15
15
  from wizlib.parser import WizParser
16
16
 
@@ -27,20 +27,32 @@ class ConfigHandler(Handler):
27
27
 
28
28
  name = 'config'
29
29
 
30
- def __init__(self, file=None, yaml=None):
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)"""
31
44
  self.file = file
32
- self.yaml_dict = yaml
45
+ self.injected_data = data
33
46
  self.cache = {}
34
47
 
35
- @property
36
- def yaml(self):
37
- if hasattr(self, '_yaml'):
38
- return self._yaml
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."""
39
52
 
40
53
  # If yaml_dict was provided, use it directly
41
- if self.yaml_dict is not None:
42
- self._yaml = self.yaml_dict
43
- return self._yaml
54
+ if self.injected_data is not None:
55
+ return self.injected_data
44
56
 
45
57
  path = None
46
58
  if self.file:
@@ -56,35 +68,41 @@ class ConfigHandler(Handler):
56
68
  path = homepath
57
69
  if path:
58
70
  with open(path) as file:
59
- self._yaml = load(file, Loader=Loader)
60
- return self._yaml
71
+ data = yaml.safe_load(file)
72
+ return data
61
73
 
62
74
  @staticmethod
63
75
  def env(name):
64
76
  if (envvar := name.upper().replace('-', '_')) in os.environ:
65
77
  return os.environ[envvar]
66
78
 
67
- def get(self, key: str):
68
- """Return the value for the requested config entry"""
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."""
69
86
 
70
87
  # If we already found the value, return it
71
- if key in self.cache:
72
- return self.cache[key]
88
+ if data_path in self.cache:
89
+ return self.cache[data_path]
73
90
 
74
91
  # Environment variables take precedence
75
- if (result := self.env(key)):
76
- self.cache[key] = result
92
+ if (result := self.env(data_path)):
93
+ self.cache[data_path] = result
77
94
  return result
78
95
 
79
- # Otherwise look at the YAML
80
- if (yaml := self.yaml):
81
- split = key.split('-')
82
- while (val := split.pop(0)) and (val in yaml):
83
- yaml = yaml[val] if val in yaml else None
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
84
101
  if not split:
85
- result = evaluate_yaml_value(yaml)
86
- self.cache[key] = result
87
- return result
102
+ if isinstance(data, str):
103
+ data = evaluate_string(data)
104
+ self.cache[data_path] = data
105
+ return data
88
106
 
89
107
  @classmethod
90
108
  def fake(cls, **vals):
@@ -95,14 +113,14 @@ class ConfigHandler(Handler):
95
113
 
96
114
 
97
115
  def os_process(match):
98
- """Run a subprocess"""
116
+ """Run a subprocess in shell form"""
99
117
  command_string = match.group(1).strip()
100
118
  command = shlex.split(command_string)
101
119
  result = subprocess.run(command, capture_output=True)
102
120
  return result.stdout.decode().strip()
103
121
 
104
122
 
105
- def evaluate_yaml_value(yaml: str) -> str:
106
- """When getting a value from YAML, evaluate shell commands"""
123
+ def evaluate_string(yaml: str) -> str:
124
+ """Evaluate shell commands in string values"""
107
125
  text = yaml.strip()
108
126
  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