parsing: Escape str interpolation in dict unpacking.

Use subprocess.list2cmdline (windows) / shlex.quote

Closes #8203
Closes #8370
This commit is contained in:
David de la Iglesia Castro
2022-11-18 20:00:16 +01:00
parent ba2e8bdac7
commit a33bbc97d5
2 changed files with 26 additions and 9 deletions
+13 -2
View File
@@ -1,3 +1,4 @@
import os
import re
import typing
from collections.abc import Iterable, Mapping
@@ -70,6 +71,16 @@ def embrace(s: str):
return BRACE_OPEN + s + BRACE_CLOSE
def escape_str(value):
if os.name == "nt":
from subprocess import list2cmdline
return list2cmdline([value])
from shlex import quote
return quote(value)
@singledispatch
def to_str(obj) -> str:
return str(obj)
@@ -97,12 +108,12 @@ def _(obj: dict):
result += f"--no-{k} "
elif isinstance(v, str):
result += f"--{k} '{v}' "
result += f"--{k} {escape_str(v)} "
elif isinstance(v, Iterable):
for n, i in enumerate(v):
if isinstance(i, str):
i = f"'{i}'"
i = escape_str(i)
elif isinstance(i, Iterable):
raise ParseError(
f"Cannot interpolate nested iterable in '{k}'"
+13 -7
View File
@@ -6,6 +6,7 @@ import pytest
from dvc.dependency import _merge_params
from dvc.parsing import DEFAULT_PARAMS_FILE, DataResolver
from dvc.parsing.context import recurse_not_a_node
from dvc.parsing.interpolate import escape_str
from . import (
CONTEXT_DATA,
@@ -272,14 +273,17 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
if list_config:
conf["parsing"]["list"] = list_config
string = "spaced string"
mixed_quote_string = "quote\"'d"
data = {
"dict": {
"foo": "foo",
"bar": 2,
"string": "spaced string",
"string": string,
"mixed_quote_string": mixed_quote_string,
"bool": True,
"bool-false": False,
"list": [1, 2, "foo"],
"list": [1, 2, "foo", mixed_quote_string],
"nested": {"foo": "foo"},
}
}
@@ -296,9 +300,10 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
bool_resolved = " --bool --no-bool-false"
if list_config is None or list_config == "nargs":
list_resolved = " --list 1 2 'foo'"
list_resolved = f" --list 1 2 foo {escape_str(mixed_quote_string)}"
else:
list_resolved = " --list 1 --list 2 --list 'foo'"
list_resolved = " --list 1 --list 2 --list foo"
list_resolved += f" --list {escape_str(mixed_quote_string)}"
assert_stage_equal(
resolver.resolve(),
@@ -306,11 +311,12 @@ def test_cmd_dict(tmp_dir, dvc, bool_config, list_config):
"stages": {
"stage1": {
"cmd": "python script.py"
" --foo 'foo' --bar 2"
" --string 'spaced string'"
" --foo foo --bar 2"
f" --string {escape_str(string)}"
f" --mixed_quote_string {escape_str(mixed_quote_string)}"
f"{bool_resolved}"
f"{list_resolved}"
" --nested.foo 'foo'"
" --nested.foo foo"
}
}
},