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 Note that ``@()`` is required to pass the python list ``args`` to a subprocess
command. 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: convert subprocess command output into Python object:
.. code-block:: xonshcon .. code-block:: xonshcon
import json, pathlib, yaml 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.") "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.") "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.") "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.") "Set `yaml` output format.")
aliases['@noerr'] = SpecAttrModifierAlias({"raise_subproc_error": False}, aliases['@noerr'] = SpecAttrDecoratorAlias({"raise_subproc_error": False},
"Set `raise_subproc_error` to 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.posix import PopenThread
from xonsh.procs.proxies import STDOUT_DISPATCHER, ProcProxy, ProcProxyThread from xonsh.procs.proxies import STDOUT_DISPATCHER, ProcProxy, ProcProxyThread
from xonsh.procs.specs import ( from xonsh.procs.specs import (
SpecAttrModifierAlias, DecoratorAlias,
SpecModifierAlias, SpecAttrDecoratorAlias,
SubprocSpec, SubprocSpec,
_run_command_pipeline, _run_command_pipeline,
cmds_to_specs, cmds_to_specs,
@ -288,8 +288,8 @@ def test_run_subproc_background(captured, exp_is_none):
assert (return_val is None) == exp_is_none assert (return_val is None) == exp_is_none
def test_spec_modifier_alias_alone(xession): def test_spec_decorator_alias_alone(xession):
xession.aliases["xunthread"] = SpecAttrModifierAlias( xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False} {"threadable": False, "force_threadable": False}
) )
@ -300,8 +300,8 @@ def test_spec_modifier_alias_alone(xession):
assert spec.alias_name == "xunthread" assert spec.alias_name == "xunthread"
def test_spec_modifier_alias(xession): def test_spec_decorator_alias(xession):
xession.aliases["xunthread"] = SpecAttrModifierAlias( xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False} {"threadable": False, "force_threadable": False}
) )
@ -313,11 +313,11 @@ def test_spec_modifier_alias(xession):
assert spec.force_threadable is False assert spec.force_threadable is False
def test_spec_modifier_alias_tree(xession): def test_spec_decorator_alias_tree(xession):
xession.aliases["xthread"] = SpecAttrModifierAlias( xession.aliases["xthread"] = SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True} {"threadable": True, "force_threadable": True}
) )
xession.aliases["xunthread"] = SpecAttrModifierAlias( xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False} {"threadable": False, "force_threadable": False}
) )
@ -337,11 +337,11 @@ def test_spec_modifier_alias_tree(xession):
assert spec.force_threadable is False assert spec.force_threadable is False
def test_spec_modifier_alias_multiple(xession): def test_spec_decorator_alias_multiple(xession):
xession.aliases["@unthread"] = SpecAttrModifierAlias( xession.aliases["@unthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False} {"threadable": False, "force_threadable": False}
) )
xession.aliases["@dict"] = SpecAttrModifierAlias({"output_format": "list_lines"}) xession.aliases["@dict"] = SpecAttrDecoratorAlias({"output_format": "list_lines"})
cmds = [ cmds = [
["@unthread", "@dict", "echo", "1"], ["@unthread", "@dict", "echo", "1"],
@ -356,12 +356,12 @@ def test_spec_modifier_alias_multiple(xession):
@skip_if_on_windows @skip_if_on_windows
def test_spec_modifier_alias_output_format(xession): def test_spec_decorator_alias_output_format(xession):
class SpecModifierOutputLinesAlias(SpecModifierAlias): class OutputLinesDecoratorAlias(DecoratorAlias):
def on_modifer_added(self, spec): def decorate_spec(self, spec):
spec.output_format = "list_lines" spec.output_format = "list_lines"
xession.aliases["xlines"] = SpecModifierOutputLinesAlias() xession.aliases["xlines"] = OutputLinesDecoratorAlias()
cmds = [["xlines", "echo", "1\n2\n3"]] cmds = [["xlines", "echo", "1\n2\n3"]]
specs = cmds_to_specs(cmds, captured="stdout") specs = cmds_to_specs(cmds, captured="stdout")
@ -531,10 +531,10 @@ def test_alias_return_command_chain(xession):
assert spec.alias_name == "foreground" 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["foreground"] = "midground f0 f1"
xession.aliases["xunthread"] = SpecAttrModifierAlias( xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False} {"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): def test_alias_return_command_eval_inside(xession):
xession.aliases["xthread"] = SpecAttrModifierAlias( xession.aliases["xthread"] = SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True} {"threadable": True, "force_threadable": True}
) )
@xession.aliases.register("xsudo") @xession.aliases.register("xsudo")
@xession.aliases.return_command @xession.aliases.return_command
def _midground(args, spec_modifiers=None): def _midground(args, decorators=None):
return [ return [
"sudo", "sudo",
*xession.aliases.eval_alias(args, spec_modifiers=spec_modifiers), *xession.aliases.eval_alias(args, decorators=decorators),
] ]
xession.aliases["cmd"] = "xthread echo 1" xession.aliases["cmd"] = "xthread echo 1"

View file

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

View file

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

View file

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