dyngle 0.4.2__py3-none-any.whl → 0.4.3__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.
Potentially problematic release.
This version of dyngle might be problematic. Click here for more details.
- dyngle/command/run_command.py +4 -1
- dyngle/expression.py +38 -4
- dyngle/template.py +30 -10
- {dyngle-0.4.2.dist-info → dyngle-0.4.3.dist-info}/METADATA +34 -1
- dyngle-0.4.3.dist-info/RECORD +12 -0
- dyngle-0.4.2.dist-info/RECORD +0 -12
- {dyngle-0.4.2.dist-info → dyngle-0.4.3.dist-info}/WHEEL +0 -0
- {dyngle-0.4.2.dist-info → dyngle-0.4.3.dist-info}/entry_points.txt +0 -0
dyngle/command/run_command.py
CHANGED
|
@@ -18,6 +18,8 @@ class RunCommand(DyngleCommand):
|
|
|
18
18
|
def add_args(cls, parser: WizParser):
|
|
19
19
|
super().add_args(parser)
|
|
20
20
|
parser.add_argument('operation', help='Operation name to run')
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'args', nargs='*', help='Optional operation arguments')
|
|
21
23
|
|
|
22
24
|
def handle_vals(self):
|
|
23
25
|
super().handle_vals()
|
|
@@ -37,7 +39,8 @@ class RunCommand(DyngleCommand):
|
|
|
37
39
|
self._validate_operation_exists(operations)
|
|
38
40
|
steps = operations[self.operation]
|
|
39
41
|
data_string = self.app.stream.text
|
|
40
|
-
data = safe_load(data_string)
|
|
42
|
+
data = safe_load(data_string) or {}
|
|
43
|
+
data['args'] = self.args
|
|
41
44
|
for step_template in steps:
|
|
42
45
|
step = Template(step_template).render(data, expressions)
|
|
43
46
|
parts = shlex.split(step)
|
dyngle/expression.py
CHANGED
|
@@ -9,6 +9,8 @@ import json
|
|
|
9
9
|
import re
|
|
10
10
|
import yaml
|
|
11
11
|
|
|
12
|
+
from dyngle.template import Template
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def formatted_datetime(dt: datetime, format_string=None) -> str:
|
|
14
16
|
"""Safe datetime formatting using string operations"""
|
|
@@ -72,9 +74,38 @@ GLOBALS = {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def _evaluate(expression: str,
|
|
77
|
+
def _evaluate(expression: str, locals: dict) -> str:
|
|
78
|
+
"""Evaluate a Python expression with safe globals and user data context.
|
|
79
|
+
|
|
80
|
+
Safely evaluates a Python expression string using a restricted set of
|
|
81
|
+
global functions and modules, combined with user-provided data. The
|
|
82
|
+
expression is evaluated in a sandboxed environment that includes basic
|
|
83
|
+
Python built-ins, mathematical operations, date/time handling, and data
|
|
84
|
+
manipulation utilities.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
expression : str
|
|
89
|
+
A valid Python expression string to be evaluated.
|
|
90
|
+
data : dict
|
|
91
|
+
Dictionary containing variables and values to be made available during
|
|
92
|
+
expression evaluation. Note that hyphens in keys will be replaced by
|
|
93
|
+
underscores to create valid Python names.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
str
|
|
98
|
+
String representation of the evaluated expression result. If the result
|
|
99
|
+
is a tuple, returns the string representation of the last element.
|
|
100
|
+
|
|
101
|
+
Raises
|
|
102
|
+
------
|
|
103
|
+
DyngleError
|
|
104
|
+
If the expression contains invalid variable names that are not found in
|
|
105
|
+
the provided data dictionary or global context.
|
|
106
|
+
"""
|
|
76
107
|
try:
|
|
77
|
-
result = eval(expression, GLOBALS,
|
|
108
|
+
result = eval(expression, GLOBALS, locals)
|
|
78
109
|
except KeyError:
|
|
79
110
|
raise DyngleError(f"The following expression contains " +
|
|
80
111
|
f"at least one invalid name: {expression}")
|
|
@@ -83,6 +114,9 @@ def _evaluate(expression: str, data: dict) -> str:
|
|
|
83
114
|
|
|
84
115
|
|
|
85
116
|
def expression(text: str) -> Callable[[dict], str]:
|
|
86
|
-
def evaluate(data: dict) -> str:
|
|
87
|
-
|
|
117
|
+
def evaluate(data: dict = None) -> str:
|
|
118
|
+
items = data.items() if data else ()
|
|
119
|
+
locals = {k.replace('-', '_'): v for k, v in items}
|
|
120
|
+
def resolve(s): return Template.resolve(s, data)
|
|
121
|
+
return _evaluate(text, locals | {'resolve': resolve})
|
|
88
122
|
return evaluate
|
dyngle/template.py
CHANGED
|
@@ -12,20 +12,40 @@ class Template:
|
|
|
12
12
|
template: str
|
|
13
13
|
|
|
14
14
|
def render(self, data: dict = None, expressions: dict = None) -> str:
|
|
15
|
-
"""Render the template with the provided data.
|
|
15
|
+
"""Render the template with the provided data and expressions.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
data : dict
|
|
20
|
+
String data to insert
|
|
21
|
+
expressions : dict
|
|
22
|
+
Functions to call with data
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
str
|
|
27
|
+
Template rendered with expression resolution and values inserted.
|
|
28
|
+
"""
|
|
29
|
+
|
|
16
30
|
data = data if data else {}
|
|
17
31
|
expressions = expressions if expressions else {}
|
|
18
|
-
resolver = partial(self._resolve,
|
|
32
|
+
resolver = partial(self._resolve, live_data=data | expressions)
|
|
19
33
|
return PATTERN.sub(resolver, self.template)
|
|
20
34
|
|
|
21
|
-
def _resolve(self, match, *,
|
|
22
|
-
"""Resolve a single name/path from the template.
|
|
35
|
+
def _resolve(self, match, *, live_data: dict):
|
|
36
|
+
"""Resolve a single name/path from the template. The argument is a
|
|
37
|
+
merge of the raw data and the expressions, either of which are valid
|
|
38
|
+
substitutions."""
|
|
23
39
|
key = match.group(1).strip()
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
return self.resolve(key, live_data)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def resolve(key: str, live_data: dict):
|
|
44
|
+
parts = key.split('.')
|
|
45
|
+
current = live_data
|
|
46
|
+
for part in parts:
|
|
47
|
+
current = current[part]
|
|
48
|
+
if callable(current):
|
|
49
|
+
return current(live_data)
|
|
26
50
|
else:
|
|
27
|
-
parts = key.split('.')
|
|
28
|
-
current = data
|
|
29
|
-
for part in parts:
|
|
30
|
-
current = current[part]
|
|
31
51
|
return current
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dyngle
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Run lightweight local workflows
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Steampunk Wizard
|
|
@@ -109,6 +109,39 @@ Expressions can use a controlled subset of the Python standard library, includin
|
|
|
109
109
|
- A specialized function called `formatted()` to perform string formatting operations on a `datetime` object
|
|
110
110
|
- A restricted version of `Path()` that only operates within the current working directory
|
|
111
111
|
- Various other useful utilities, mostly read-only, such as the `math` module
|
|
112
|
+
- A special function called `resolve` which resolves data expressions using the same logic as in templates
|
|
113
|
+
|
|
114
|
+
Data keys containing hyphens are converted to valid Python names by replacing hyphens with underscores.
|
|
115
|
+
|
|
116
|
+
Expressions can reference data directly as local names in Python (using the underscore replacements)...
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
dyngle:
|
|
120
|
+
expressions:
|
|
121
|
+
say-hello: >-
|
|
122
|
+
'Hello ' + full_name + '!'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
... or using the `resolve()` function, which also allows expressions to essentially call other expressions, using the same underlying data set.
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
dyngle:
|
|
129
|
+
expressions:
|
|
130
|
+
hello: >-
|
|
131
|
+
'Hello ' + resolve('formal-name') + '!'
|
|
132
|
+
formal-name: >-
|
|
133
|
+
'Ms. ' + full_name
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Note it's also _possible_ to call other expressions by name as functions, if they only return hard-coded values (i.e. constants).
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
dyngle:
|
|
140
|
+
expressions:
|
|
141
|
+
author-name: Francis Potter
|
|
142
|
+
author-hello: >-
|
|
143
|
+
'Hello ' + author_name()
|
|
144
|
+
```
|
|
112
145
|
|
|
113
146
|
## Security
|
|
114
147
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
dyngle/__init__.py,sha256=drqd18F3T7LMTNqAU38WYjanczl1kANAA1vVBjbPEyU,653
|
|
2
|
+
dyngle/__main__.py,sha256=pYRIwzix_AL8CdJaDDis_8yMBBWO2N72NNwkroo1dQo,95
|
|
3
|
+
dyngle/command/__init__.py,sha256=1S86gbef8MYvG-TWD5JRIWzFg7qV5xKhp9QXx9zEx5c,94
|
|
4
|
+
dyngle/command/run_command.py,sha256=fgso_ZadAkA0f1ZjSGAUHs32hCHnX3QE_abnAmvg77g,1854
|
|
5
|
+
dyngle/error.py,sha256=CGcTa8L4O1qsHEYnzp_JBbkvntJTv2Qz46wj_TI8NLk,39
|
|
6
|
+
dyngle/expression.py,sha256=-uLVbrO8ovNZGGLNqMZWIy_StCK-0laZqcQ1gOPhU6w,3476
|
|
7
|
+
dyngle/safe_path.py,sha256=Hk2AhP6e3yKGh3kKrLLwhvAlMNx-j2jObBYJL-_doAU,3339
|
|
8
|
+
dyngle/template.py,sha256=XM5VtOiuMVzNjMTsUdMMfugiygcOuAh-8MedfhEbGn0,1435
|
|
9
|
+
dyngle-0.4.3.dist-info/METADATA,sha256=RpCvS1bRIN-Xcdyw2VIJ7j-zm-BAEVokemfUh52bpr0,4544
|
|
10
|
+
dyngle-0.4.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
11
|
+
dyngle-0.4.3.dist-info/entry_points.txt,sha256=rekiGhtweiHKm9g1jdGb3FhzqDrk1kigJDeSNollZSA,48
|
|
12
|
+
dyngle-0.4.3.dist-info/RECORD,,
|
dyngle-0.4.2.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
dyngle/__init__.py,sha256=drqd18F3T7LMTNqAU38WYjanczl1kANAA1vVBjbPEyU,653
|
|
2
|
-
dyngle/__main__.py,sha256=pYRIwzix_AL8CdJaDDis_8yMBBWO2N72NNwkroo1dQo,95
|
|
3
|
-
dyngle/command/__init__.py,sha256=1S86gbef8MYvG-TWD5JRIWzFg7qV5xKhp9QXx9zEx5c,94
|
|
4
|
-
dyngle/command/run_command.py,sha256=0E8Bg7TRa8qXdfZNyC4Wygb1cEG4-ejRJ5ZfYz_L1_Y,1718
|
|
5
|
-
dyngle/error.py,sha256=CGcTa8L4O1qsHEYnzp_JBbkvntJTv2Qz46wj_TI8NLk,39
|
|
6
|
-
dyngle/expression.py,sha256=x-2Ald34lmXfG8070H96Wszln5Rd1AWGyYuYtf7iZFA,2134
|
|
7
|
-
dyngle/safe_path.py,sha256=Hk2AhP6e3yKGh3kKrLLwhvAlMNx-j2jObBYJL-_doAU,3339
|
|
8
|
-
dyngle/template.py,sha256=jE-rJiR2kZBHrjy_VJyiXCr4uu1JH8JCafShrMK9lVM,937
|
|
9
|
-
dyngle-0.4.2.dist-info/METADATA,sha256=RnHf4ODiubIgBsF5Q1B8Rk1QRHgjMWshS-7bN7KMl9w,3594
|
|
10
|
-
dyngle-0.4.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
11
|
-
dyngle-0.4.2.dist-info/entry_points.txt,sha256=rekiGhtweiHKm9g1jdGb3FhzqDrk1kigJDeSNollZSA,48
|
|
12
|
-
dyngle-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|