spec modifier: rename to decorator alias (#5579)

* rename

* rename

* rename

* rename

* rename

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* rename

* rename

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* rename

* rename

* rename

* rename

* rename

* rename

* rename

* rename

* rename

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix

* fix

---------

Co-authored-by: a <1@1.1>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Andy Kipp 2024-07-14 22:00:24 +02:00 committed by GitHub
parent 96c2c0af14
commit 233e407b52
Failed to generate hash of commit
7 changed files with 98 additions and 77 deletions

View file

@ -1632,26 +1632,26 @@ best used in conjunction with the ``unthreadable`` decorator. For example:
Note that ``@()`` is required to pass the python list ``args`` to a subprocess
command.
Specification Modifier Aliases
------------------------------
Decorator Aliases
-----------------
Using ``SpecAttrModifierAlias`` and callable ``output_format`` you can
Using ``DecoratorAlias`` and ``SpecAttrDecoratorAlias`` and callable ``output_format`` you can
convert subprocess command output into Python object:
.. code-block:: xonshcon
import json, pathlib, yaml
from xonsh.procs.specs import SpecAttrModifierAlias
from xonsh.procs.specs import SpecAttrDecoratorAlias
aliases['@lines'] = SpecAttrModifierAlias({"output_format": 'list_lines'},
aliases['@lines'] = SpecAttrDecoratorAlias({"output_format": 'list_lines'},
"Set `list_lines` output format.")
aliases['@json'] = SpecAttrModifierAlias({"output_format": lambda lines: json.loads('\n'.join(lines))},
aliases['@json'] = SpecAttrDecoratorAlias({"output_format": lambda lines: json.loads('\n'.join(lines))},
"Set `json` output format.")
aliases['@path'] = SpecAttrModifierAlias({"output_format": lambda lines: pathlib.Path(':'.join(lines))},
aliases['@path'] = SpecAttrDecoratorAlias({"output_format": lambda lines: pathlib.Path(':'.join(lines))},
"Set `path` output format.")
aliases['@yaml'] = SpecAttrModifierAlias({"output_format": lambda lines: yaml.safe_load('\n'.join(lines))},
aliases['@yaml'] = SpecAttrDecoratorAlias({"output_format": lambda lines: yaml.safe_load('\n'.join(lines))},
"Set `yaml` output format.")
aliases['@noerr'] = SpecAttrModifierAlias({"raise_subproc_error": False},
aliases['@noerr'] = SpecAttrDecoratorAlias({"raise_subproc_error": False},
"Set `raise_subproc_error` to False.")

24
news/rename_xthread.rst Normal file
View file

@ -0,0 +1,24 @@
**Added:**
* <news item>
**Changed:**
* Rename: ``SpecModifier``, ``xthread``, ``xunthread`` renamed to ``DecoratorAlias``, ``@thread``, ``@unthread`` to support idea that
spec modifier is like a Python decorator.
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -10,8 +10,8 @@ import pytest
from xonsh.procs.posix import PopenThread
from xonsh.procs.proxies import STDOUT_DISPATCHER, ProcProxy, ProcProxyThread
from xonsh.procs.specs import (
SpecAttrModifierAlias,
SpecModifierAlias,
DecoratorAlias,
SpecAttrDecoratorAlias,
SubprocSpec,
_run_command_pipeline,
cmds_to_specs,
@ -288,8 +288,8 @@ def test_run_subproc_background(captured, exp_is_none):
assert (return_val is None) == exp_is_none
def test_spec_modifier_alias_alone(xession):
xession.aliases["xunthread"] = SpecAttrModifierAlias(
def test_spec_decorator_alias_alone(xession):
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
@ -300,8 +300,8 @@ def test_spec_modifier_alias_alone(xession):
assert spec.alias_name == "xunthread"
def test_spec_modifier_alias(xession):
xession.aliases["xunthread"] = SpecAttrModifierAlias(
def test_spec_decorator_alias(xession):
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
@ -313,11 +313,11 @@ def test_spec_modifier_alias(xession):
assert spec.force_threadable is False
def test_spec_modifier_alias_tree(xession):
xession.aliases["xthread"] = SpecAttrModifierAlias(
def test_spec_decorator_alias_tree(xession):
xession.aliases["xthread"] = SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True}
)
xession.aliases["xunthread"] = SpecAttrModifierAlias(
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
@ -337,11 +337,11 @@ def test_spec_modifier_alias_tree(xession):
assert spec.force_threadable is False
def test_spec_modifier_alias_multiple(xession):
xession.aliases["@unthread"] = SpecAttrModifierAlias(
def test_spec_decorator_alias_multiple(xession):
xession.aliases["@unthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
xession.aliases["@dict"] = SpecAttrModifierAlias({"output_format": "list_lines"})
xession.aliases["@dict"] = SpecAttrDecoratorAlias({"output_format": "list_lines"})
cmds = [
["@unthread", "@dict", "echo", "1"],
@ -356,12 +356,12 @@ def test_spec_modifier_alias_multiple(xession):
@skip_if_on_windows
def test_spec_modifier_alias_output_format(xession):
class SpecModifierOutputLinesAlias(SpecModifierAlias):
def on_modifer_added(self, spec):
def test_spec_decorator_alias_output_format(xession):
class OutputLinesDecoratorAlias(DecoratorAlias):
def decorate_spec(self, spec):
spec.output_format = "list_lines"
xession.aliases["xlines"] = SpecModifierOutputLinesAlias()
xession.aliases["xlines"] = OutputLinesDecoratorAlias()
cmds = [["xlines", "echo", "1\n2\n3"]]
specs = cmds_to_specs(cmds, captured="stdout")
@ -531,10 +531,10 @@ def test_alias_return_command_chain(xession):
assert spec.alias_name == "foreground"
def test_alias_return_command_chain_spec_modifiers(xession):
def test_alias_return_command_chain_decorators(xession):
xession.aliases["foreground"] = "midground f0 f1"
xession.aliases["xunthread"] = SpecAttrModifierAlias(
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
@ -556,16 +556,16 @@ def test_alias_return_command_chain_spec_modifiers(xession):
def test_alias_return_command_eval_inside(xession):
xession.aliases["xthread"] = SpecAttrModifierAlias(
xession.aliases["xthread"] = SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True}
)
@xession.aliases.register("xsudo")
@xession.aliases.return_command
def _midground(args, spec_modifiers=None):
def _midground(args, decorators=None):
return [
"sudo",
*xession.aliases.eval_alias(args, spec_modifiers=spec_modifiers),
*xession.aliases.eval_alias(args, decorators=decorators),
]
xession.aliases["cmd"] = "xthread echo 1"

View file

@ -1365,10 +1365,10 @@ def test_alias_stability():
@skip_if_on_windows
@pytest.mark.flaky(reruns=3, reruns_delay=1)
def test_spec_modifier_alias():
def test_spec_decorator_alias():
"""Testing spec modifier alias with `@` in the alias name."""
stdin_cmd = (
"from xonsh.procs.specs import SpecAttrModifierAlias as mod\n"
"from xonsh.procs.specs import SpecAttrDecoratorAlias as mod\n"
'aliases["@dict"] = mod({"output_format": lambda lines: eval("\\n".join(lines))})\n'
"d = $(@dict echo '{\"a\":42}')\n"
"print('Answer =', d['a'])\n"

View file

@ -35,7 +35,7 @@ from xonsh.platform import (
)
from xonsh.procs.executables import locate_file
from xonsh.procs.jobs import bg, clean_jobs, disown, fg, jobs
from xonsh.procs.specs import SpecAttrModifierAlias, SpecModifierAlias
from xonsh.procs.specs import DecoratorAlias, SpecAttrDecoratorAlias
from xonsh.timings import timeit_alias
from xonsh.tools import (
ALIAS_KWARG_NAMES,
@ -90,7 +90,7 @@ class FuncAlias:
stderr=None,
spec=None,
stack=None,
spec_modifiers=None,
decorators=None,
):
return run_alias_by_params(
self.func,
@ -101,7 +101,7 @@ class FuncAlias:
"stderr": stderr,
"spec": spec,
"stack": stack,
"spec_modifiers": spec_modifiers,
"decorators": decorators,
},
)
@ -161,7 +161,7 @@ class Aliases(cabc.MutableMapping):
value,
seen_tokens=frozenset(),
acc_args=(),
spec_modifiers=None,
decorators=None,
):
"""
"Evaluates" the alias ``value``, by recursively looking up the leftmost
@ -173,7 +173,7 @@ class Aliases(cabc.MutableMapping):
callable. The resulting callable will be "partially applied" with
``["-al", "arg"]``.
"""
spec_modifiers = spec_modifiers if spec_modifiers is not None else []
decorators = decorators if decorators is not None else []
# Beware of mutability: default values for keyword args are evaluated
# only once.
if (
@ -183,8 +183,8 @@ class Aliases(cabc.MutableMapping):
):
i = 0
for v in value:
if isinstance(mod := self._raw.get(str(v)), SpecModifierAlias):
spec_modifiers.append(mod)
if isinstance(mod := self._raw.get(str(v)), DecoratorAlias):
decorators.append(mod)
i += 1
else:
break
@ -192,7 +192,7 @@ class Aliases(cabc.MutableMapping):
if callable(value) and getattr(value, "return_what", "result") == "command":
try:
value = value(acc_args, spec_modifiers=spec_modifiers)
value = value(acc_args, decorators=decorators)
acc_args = []
except Exception as e:
print_exception(f"Exception inside alias {value}: {e}")
@ -220,14 +220,14 @@ class Aliases(cabc.MutableMapping):
self._raw[token],
seen_tokens,
acc_args,
spec_modifiers=spec_modifiers,
decorators=decorators,
)
def get(
self,
key,
default=None,
spec_modifiers=None,
decorators=None,
):
"""
Returns list that represent command with resolved aliases.
@ -235,14 +235,14 @@ class Aliases(cabc.MutableMapping):
In the first position will be the resolved command name or callable alias.
If the key is not present, then `default` is returned.
``spec_modifiers`` is the list of SpecModifier objects that found during
``decorators`` is the list of `DecoratorAlias` objects that found during
resolving aliases (#5443).
Note! The return value is always list because during resolving
we can find return_command alias that can completely replace
command and add new arguments.
"""
spec_modifiers = spec_modifiers if spec_modifiers is not None else []
decorators = decorators if decorators is not None else []
args = []
if isinstance(key, list):
args = key[1:]
@ -250,7 +250,7 @@ class Aliases(cabc.MutableMapping):
val = self._raw.get(key)
if callable(val) and getattr(val, "return_what", "result") == "command":
try:
val = val(args, spec_modifiers=spec_modifiers)
val = val(args, decorators=decorators)
args = []
except Exception as e:
print_exception(f"Exception inside alias {key!r}: {e}")
@ -264,7 +264,7 @@ class Aliases(cabc.MutableMapping):
return self.eval_alias(
val,
seen_tokens={key},
spec_modifiers=spec_modifiers,
decorators=decorators,
acc_args=args,
)
else:
@ -483,10 +483,10 @@ class PartialEvalAlias7(PartialEvalAliasBase):
stderr=None,
spec=None,
stack=None,
spec_modifiers=None,
decorators=None,
):
args = list(self.acc_args) + args
return self.f(args, stdin, stdout, stderr, spec, stack, spec_modifiers)
return self.f(args, stdin, stdout, stderr, spec, stack, decorators)
PARTIAL_EVAL_ALIASES = (
@ -538,7 +538,7 @@ def run_alias_by_params(func: tp.Callable, params: dict[str, tp.Any]):
"stderr": None,
"spec": None,
"stack": None,
"spec_modifiers": None,
"decorators": None,
}
alias_params |= params
sign = inspect.signature(func)
@ -1064,11 +1064,11 @@ def make_default_aliases():
"completer": xca.completer_alias,
"xpip": detect_xpip_alias(),
"xonsh-reset": xonsh_reset,
"xthread": SpecAttrModifierAlias(
"@thread": SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True},
"Mark current command as threadable.",
),
"xunthread": SpecAttrModifierAlias(
"@unthread": SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False},
"Mark current command as unthreadable.",
),

View file

@ -167,8 +167,8 @@ class CommandPipeline:
# to stop.
pipeline_group = os.getpgid(0)
for i, spec in enumerate(specs):
for mod in spec.spec_modifiers:
mod.on_pre_run(self, spec, i)
for mod in spec.decorators:
mod.decorate_spec_pre_run(self, spec, i)
if self.starttime is None:
self.starttime = time.time()
try:

View file

@ -277,10 +277,10 @@ def no_pg_xonsh_preexec_fn():
signal.signal(signal.SIGTSTP, default_signal_pauser)
class SpecModifierAlias:
"""Spec modifier base class."""
class DecoratorAlias:
"""Decorator alias base class."""
descr = "Spec modifier base class."
descr = "DecoratorAlias base."
def __call__(
self,
@ -294,24 +294,24 @@ class SpecModifierAlias:
):
print(self.descr, file=stdout)
def on_modifer_added(self, spec):
def decorate_spec(self, spec):
"""Modify spec immediately after modifier added."""
pass
def on_pre_run(self, pipeline, spec, spec_num):
def decorate_spec_pre_run(self, pipeline, spec, spec_num):
"""Modify spec before run."""
pass
class SpecAttrModifierAlias(SpecModifierAlias):
"""Modifier for spec attributes."""
class SpecAttrDecoratorAlias(DecoratorAlias):
"""Decorator Alias for spec attributes."""
def __init__(self, set_attributes: dict, descr=""):
self.set_attributes = set_attributes
self.descr = descr
super().__init__()
def on_modifer_added(self, spec):
def decorate_spec(self, spec):
for a, v in self.set_attributes.items():
setattr(spec, a, v)
@ -419,7 +419,7 @@ class SubprocSpec:
self.captured_stdout = None
self.captured_stderr = None
self.stack = None
self.spec_modifiers = [] # List of SpecModifierAlias objects that applied to spec.
self.decorators = [] # List of DecoratorAlias objects that applied to spec.
self.output_format = XSH.env.get("XONSH_SUBPROC_OUTPUT_FORMAT", "stream_lines")
self.raise_subproc_error = None # Spec-based $RAISE_SUBPROC_ERROR.
@ -642,7 +642,7 @@ class SubprocSpec:
# modifications that do not alter cmds may come before creating instance
spec = kls(cmd, cls=cls, **kwargs)
# modifications that alter cmds must come after creating instance
spec.resolve_spec_modifiers() # keep this first
spec.resolve_decorators() # keep this first
spec.resolve_args_list()
spec.resolve_redirects()
spec.resolve_alias()
@ -653,21 +653,19 @@ class SubprocSpec:
spec.resolve_stack()
return spec
def add_spec_modifier(self, mod: SpecModifierAlias):
def add_decorator(self, mod: DecoratorAlias):
"""Add spec modifier to the specification."""
mod.on_modifer_added(self)
self.spec_modifiers.append(mod)
mod.decorate_spec(self)
self.decorators.append(mod)
def resolve_spec_modifiers(self):
"""Apply spec modifier."""
def resolve_decorators(self):
"""Apply decorators."""
if (ln := len(self.cmd)) == 1:
return
for i in range(ln):
c = self.cmd[i]
if c in XSH.aliases and isinstance(
mod := XSH.aliases[c], SpecModifierAlias
):
self.add_spec_modifier(mod)
if c in XSH.aliases and isinstance(mod := XSH.aliases[c], DecoratorAlias):
self.add_decorator(mod)
else:
break
self.cmd = self.cmd[i:]
@ -716,7 +714,7 @@ class SubprocSpec:
if callable(cmd0):
self.alias = cmd0
else:
found_spec_modifiers = []
decorators = []
if isinstance(XSH.aliases, dict):
# Windows tests
alias = XSH.aliases.get(cmd0, None)
@ -726,7 +724,7 @@ class SubprocSpec:
alias = XSH.aliases.get(
self.cmd,
None,
spec_modifiers=found_spec_modifiers,
decorators=decorators,
)
if alias is not None:
self.alias_name = cmd0
@ -738,9 +736,8 @@ class SubprocSpec:
# E.g. `alias == ['ls', '-la']`
self.alias = alias
if found_spec_modifiers:
for mod in found_spec_modifiers:
self.add_spec_modifier(mod)
for mod in decorators:
self.add_decorator(mod)
def resolve_binary_loc(self):
"""Sets the binary location"""