Skip to content

Commit

Permalink
test: nested list and dict in TemplateList
Browse files Browse the repository at this point in the history
fix: miscellaneous naming
  • Loading branch information
fblanchetNaN committed Apr 18, 2022
1 parent cb67157 commit 6bf129a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 49 deletions.
42 changes: 21 additions & 21 deletions incipyt/_internal/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
logger = logging.getLogger(__name__)


class HasFormat(metaclass=ABCMeta):
class Formattable(metaclass=ABCMeta):
@abstractmethod
def format(self): # noqa: A003
raise NotImplementedError


class StringTemplate(HasFormat):
class StringTemplate(Formattable):
"""This class acts like a wrapper around a format string.
When an instance is called, it renders the underlying format string using
Expand Down Expand Up @@ -111,11 +111,11 @@ def __repr__(self):
)

@classmethod
def _get_string_template(cls, value):
return value if isinstance(value, HasFormat) else cls(value)
def wrap(cls, value):
return value if isinstance(value, Formattable) else cls(value)


class MultiStringTemplate(HasFormat):
class ChoiceTemplate(Formattable):
"""Class to hold multiple values for a single key.
When an instance is called, the user will be asked to pick a value using
Expand All @@ -131,29 +131,29 @@ def __init__(self, head, tail):
:param head: Entry to put a the head of the stack.
:type tail: :class:str
:param tail: Tail of the stack.
:type tail: :class:`incipyt._intternal.templates.MultiStringTemplate` or any bare value
:type tail: :class:`incipyt._intternal.templates.ChoiceTemplate` or any bare value
"""
self._values = (
{StringTemplate._get_string_template(head)} | tail._values
if isinstance(tail, MultiStringTemplate)
{StringTemplate.wrap(head)} | tail._values
if isinstance(tail, ChoiceTemplate)
else {
StringTemplate._get_string_template(head),
StringTemplate._get_string_template(tail),
StringTemplate.wrap(head),
StringTemplate.wrap(tail),
}
)

def format(self): # noqa: A003
"""Ask the user to pick a value using the command line interface.
If it is :class:`incipyt._internal.templates.HasFormat`, it will be formatted.
If it is :class:`incipyt._internal.templates.Formattable`, it will be formatted.
:return: The user-choosen value.
"""
return click.prompt(
"Conflicting configuration, choose between",
type=click.Choice(
[
value.format() if isinstance(value, HasFormat) else value
value.format() if isinstance(value, Formattable) else value
for value in self._values
]
),
Expand All @@ -174,10 +174,10 @@ def from_items(cls, *args):
:param \*args: Entries to wrap.
:return: New class instance
:rtype: :class:`incipyt._intternal.templates.MultiStringTemplate`
:rtype: :class:`incipyt._intternal.templates.ChoiceTemplate`
"""
instance = cls.__new__(cls)
instance._values = {StringTemplate._get_string_template(arg) for arg in args}
instance._values = {StringTemplate.wrap(arg) for arg in args}
return instance


Expand All @@ -201,7 +201,7 @@ class TemplateDict(abc.MutableMapping):
>>> print(cfg)
TemplateDict(data={'key': StringTemplate(format_string={VARIABLE_NAME})})
:class:`incipyt._internal.templates.HasFromat` will be kept as-is:
:class:`incipyt._internal.templates.Formattable` will be kept as-is:
>>> cfg["key"] = a_formattable
>>> print(cfg)
Expand All @@ -227,14 +227,14 @@ class TemplateDict(abc.MutableMapping):
:Multiple values:
Instances of :class:`incipyt._internal.templates.MultiStringTemplate` will be
Instances of :class:`incipyt._internal.templates.ChoiceTemplate` will be
created in case of value overrides. For instance, if `previous_value` is a
:class:`incipyt._internal.templates.HasFormat`:
:class:`incipyt._internal.templates.Formattable`:
>>> cfg = TemplateDict({"key": previous_value})
>>> cfg["key"] = "{VARIABLE_NAME}"
>>> print(cfg)
TemplateDict(data={'key': MultiStringTemplate({StringTemplate(format_string={VARIABLE_NAME}), previous_value})})
TemplateDict(data={'key': ChoiceTemplate({StringTemplate(format_string={VARIABLE_NAME}), previous_value})})
If `previous_list` is a mutable sequence, any value not already present in
it will be appended:
Expand Down Expand Up @@ -329,9 +329,9 @@ def __setitem__(self, keys, value):

else:
if keys in self.data:
self.data[keys] = MultiStringTemplate(value, self.data[keys])
self.data[keys] = ChoiceTemplate(value, self.data[keys])
else:
self.data[keys] = StringTemplate._get_string_template(value)
self.data[keys] = StringTemplate.wrap(value)

def __ior__(self, other):
self.update(other)
Expand Down Expand Up @@ -391,7 +391,7 @@ def insert(self, index, value):
self.data.insert(index, {})
TemplateDict(self.data[index]).update(value)
else:
new_value = StringTemplate._get_string_template(value)
new_value = StringTemplate.wrap(value)
if new_value not in self.data:
self.data.insert(index, new_value)

Expand Down
4 changes: 2 additions & 2 deletions incipyt/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from incipyt import project

from incipyt._internal.templates import HasFormat
from incipyt._internal.templates import Formattable
from incipyt._internal.utils import EnvValue

logger = logging.getLogger(__name__)
Expand All @@ -20,7 +20,7 @@ def run(args, **kwargs):
:return: Represents a process that has finished
:rtype: :class:`subprocess.CompletedProcess`
"""
formatted = [arg.format() if isinstance(arg, HasFormat) else arg for arg in args]
formatted = [arg.format() if isinstance(arg, Formattable) else arg for arg in args]
logger.info(" ".join(formatted))
result = subprocess.run(formatted, capture_output=True, check=True, **kwargs)
logger.info(result.stdout.decode())
Expand Down
6 changes: 3 additions & 3 deletions incipyt/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def mkdir(self, workon):
def _visit(template):
"""Visit the `template` nested-dictionary structure.
All :class:`incipyt._internal.templates.HasFromat` values of the template dictionary will be
All :class:`incipyt._internal.templates.Formattable` values of the template dictionary will be
evaluated and replaced by their results. All nested structures will be recursively
visited and processed too.
Expand All @@ -196,7 +196,7 @@ def _visit(template):
"""
if is_nonstring_sequence(template):
for index, value in enumerate(template):
if isinstance(value, templates.HasFormat):
if isinstance(value, templates.Formattable):
template[index] = value.format()
else:
_Structure._visit(value)
Expand All @@ -210,7 +210,7 @@ def _visit(template):
for key, value in template.items():
logger.debug("Visit %s to process environ variables.", key)

if isinstance(value, templates.HasFormat):
if isinstance(value, templates.Formattable):
template[key] = value.format()
else:
_Structure._visit(value)
Expand Down
82 changes: 59 additions & 23 deletions tests/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pytest import fixture, mark, raises

from incipyt._internal.templates import (
MultiStringTemplate,
ChoiceTemplate,
StringTemplate,
TemplateDict,
)
Expand Down Expand Up @@ -72,43 +72,42 @@ def test_format_null(self, st, stdin, reset_environ, request, monkeypatch):
assert st.format() is None


class TestMultiStringTemplate:
class TestChoiceTemplate:
@fixture
def simple_mst(self):
return MultiStringTemplate("a", "b")
return ChoiceTemplate("a", "b")

@fixture
def has_format_mst(self):
return MultiStringTemplate(StringTemplate("a"), StringTemplate("b"))
def formattable_mst(self):
return ChoiceTemplate(StringTemplate("a"), StringTemplate("b"))

@fixture
def reset_environ(self):
project.environ.clear()

def test_mst_tail(self, simple_mst):
mst = MultiStringTemplate("x", simple_mst)
print(mst)
mst = ChoiceTemplate("x", simple_mst)
assert mst._values == {
StringTemplate("x"),
StringTemplate("a"),
StringTemplate("b"),
}

@mark.parametrize("mst", ("simple_mst", "has_format_mst"))
@mark.parametrize("mst", ("simple_mst", "formattable_mst"))
def test_call(self, mst, reset_environ, monkeypatch, request):
mock_stdin(monkeypatch, "a")
mst = request.getfixturevalue(mst)
assert mst.format() == "a"

@mark.parametrize("mst", ("simple_mst", "has_format_mst"))
@mark.parametrize("mst", ("simple_mst", "formattable_mst"))
def test_call_invalid(self, mst, reset_environ, monkeypatch, request):
mock_stdin(monkeypatch, "x")
mst = request.getfixturevalue(mst)
with raises(click.exceptions.Abort):
mst.format()


class TestTemplateDict:
class TestTemplateCollection:
@fixture
def empty_td(self):
return TemplateDict({})
Expand All @@ -122,8 +121,8 @@ def nested_td(self):
return TemplateDict({"1": {"2": {"3": StringTemplate("a")}}})

@fixture
def multiple_td(self):
return TemplateDict({"1": MultiStringTemplate("a", "b")})
def choice_td(self):
return TemplateDict({"1": ChoiceTemplate("a", "b")})

@fixture
def sequence_td(self):
Expand All @@ -135,11 +134,11 @@ def sequence_td(self):
("empty_td", TemplateDict({"1": StringTemplate("x")})),
(
"simple_td",
TemplateDict({"1": MultiStringTemplate("x", "a")}),
TemplateDict({"1": ChoiceTemplate("x", "a")}),
),
(
"multiple_td",
TemplateDict({"1": MultiStringTemplate.from_items("x", "a", "b")}),
"choice_td",
TemplateDict({"1": ChoiceTemplate.from_items("x", "a", "b")}),
),
),
)
Expand All @@ -154,7 +153,11 @@ def test_setitem(self, td, res, request):
("empty_td", TemplateDict({"1": StringTemplate("x")})),
(
"simple_td",
TemplateDict({"1": MultiStringTemplate("x", "a")}),
TemplateDict({"1": ChoiceTemplate("x", "a")}),
),
(
"choice_td",
TemplateDict({"1": ChoiceTemplate.from_items("x", "a", "b")}),
),
),
)
Expand All @@ -169,7 +172,7 @@ def test_setitem_callable(self, td, res, request):
("empty_td", TemplateDict({"1": {"2": {"3": StringTemplate("x")}}})),
(
"nested_td",
TemplateDict({"1": {"2": {"3": MultiStringTemplate("x", "a")}}}),
TemplateDict({"1": {"2": {"3": ChoiceTemplate("x", "a")}}}),
),
),
)
Expand Down Expand Up @@ -202,8 +205,41 @@ def test_chained_setitem(self, td, res, request):
def test_sequence_setitem(self, td, res, request):
td = request.getfixturevalue(td)
td["1"] = ["a", "x"]
print(td["1"])
print(res["1"])
assert td == res

@mark.parametrize(
"td, res",
(
(
"empty_td",
TemplateDict(
{
"1": [
StringTemplate("a"),
[StringTemplate("x"), StringTemplate("y")],
{"2": StringTemplate("z")},
]
}
),
),
(
"sequence_td",
TemplateDict(
{
"1": [
StringTemplate("a"),
StringTemplate("b"),
[StringTemplate("x"), StringTemplate("y")],
{"2": StringTemplate("z")},
]
}
),
),
),
)
def test_nested_sequence_setitem(self, td, res, request):
td = request.getfixturevalue(td)
td["1"] = ["a", ["x", "y"], {"2": "z"}]
assert td == res

@mark.parametrize(
Expand All @@ -212,7 +248,7 @@ def test_sequence_setitem(self, td, res, request):
("empty_td", TemplateDict({"1": {"2": {"3": StringTemplate("x")}}})),
(
"nested_td",
TemplateDict({"1": {"2": {"3": MultiStringTemplate("x", "a")}}}),
TemplateDict({"1": {"2": {"3": ChoiceTemplate("x", "a")}}}),
),
),
)
Expand Down Expand Up @@ -240,8 +276,8 @@ def nested_td(self):
return {"1": {"2": {"3": StringTemplate("{ONE}")}}}

@fixture
def multiple_td(self):
return {"1": MultiStringTemplate("{ONE}", "b")}
def choice_td(self):
return {"1": ChoiceTemplate("{ONE}", "b")}

@fixture
def sequence_td(self):
Expand All @@ -263,7 +299,7 @@ def single_td(self):
("sequence_td", {"1": [{2: "b"}]}, [""]),
("single_td", {"1": ["a"]}, ["a"]),
("single_td", {}, [""]),
("multiple_td", {"1": "a"}, ["a", "a"]),
("choice_td", {"1": "a"}, ["a", "a"]),
),
)
def test_call(self, td, res, reset_environ, input_values, monkeypatch, request):
Expand Down

0 comments on commit 6bf129a

Please sign in to comment.