From 233e407b527e7827a8ef8fee660d8e6309865ffb Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Sun, 14 Jul 2024 22:00:24 +0200 Subject: [PATCH] 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> --- docs/tutorial.rst | 18 +++++++-------- news/rename_xthread.rst | 24 ++++++++++++++++++++ tests/procs/test_specs.py | 42 +++++++++++++++++------------------ tests/test_integrations.py | 4 ++-- xonsh/aliases.py | 38 ++++++++++++++++---------------- xonsh/procs/pipelines.py | 4 ++-- xonsh/procs/specs.py | 45 ++++++++++++++++++-------------------- 7 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 news/rename_xthread.rst diff --git a/docs/tutorial.rst b/docs/tutorial.rst index fcad90235..954b0e257 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -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.") diff --git a/news/rename_xthread.rst b/news/rename_xthread.rst new file mode 100644 index 000000000..1fe42949e --- /dev/null +++ b/news/rename_xthread.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* Rename: ``SpecModifier``, ``xthread``, ``xunthread`` renamed to ``DecoratorAlias``, ``@thread``, ``@unthread`` to support idea that + spec modifier is like a Python decorator. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/procs/test_specs.py b/tests/procs/test_specs.py index b307fc070..5759230a3 100644 --- a/tests/procs/test_specs.py +++ b/tests/procs/test_specs.py @@ -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" diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 3fa30ca47..e8883ef93 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -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" diff --git a/xonsh/aliases.py b/xonsh/aliases.py index c068b6c49..c20ce79a2 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -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.", ), diff --git a/xonsh/procs/pipelines.py b/xonsh/procs/pipelines.py index 80f22f87b..eb136c64a 100644 --- a/xonsh/procs/pipelines.py +++ b/xonsh/procs/pipelines.py @@ -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: diff --git a/xonsh/procs/specs.py b/xonsh/procs/specs.py index 4f504682f..1dc28bcf3 100644 --- a/xonsh/procs/specs.py +++ b/xonsh/procs/specs.py @@ -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"""