Introduce default_args
context manager (#39964)
This adds a rather trivial context manager that lets you deduplicate repeated arguments in directives, e.g. ```python depends_on("py-x@1", when="@1", type=("build", "run")) depends_on("py-x@2", when="@2", type=("build", "run")) depends_on("py-x@3", when="@3", type=("build", "run")) depends_on("py-x@4", when="@4", type=("build", "run")) ``` can be condensed to ```python with default_args(type=("build", "run")): depends_on("py-x@1", when="@1") depends_on("py-x@2", when="@2") depends_on("py-x@3", when="@3") depends_on("py-x@4", when="@4") ``` The advantage is it's clear for humans, the downside it's less clear for type checkers due to type erasure.
This commit is contained in:
parent
b5538960c3
commit
1235084c20
5 changed files with 94 additions and 18 deletions
|
@ -3503,6 +3503,56 @@ is equivalent to:
|
||||||
Constraints from nested context managers are also combined together, but they are rarely
|
Constraints from nested context managers are also combined together, but they are rarely
|
||||||
needed or recommended.
|
needed or recommended.
|
||||||
|
|
||||||
|
.. _default_args:
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
Common default arguments
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Similarly, if directives have a common set of default arguments, you can
|
||||||
|
group them together in a ``with default_args()`` block:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class PyExample(PythonPackage):
|
||||||
|
|
||||||
|
with default_args(type=("build", "run")):
|
||||||
|
depends_on("py-foo")
|
||||||
|
depends_on("py-foo@2:", when="@2:")
|
||||||
|
depends_on("py-bar")
|
||||||
|
depends_on("py-bz")
|
||||||
|
|
||||||
|
The above is short for:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class PyExample(PythonPackage):
|
||||||
|
|
||||||
|
depends_on("py-foo", type=("build", "run"))
|
||||||
|
depends_on("py-foo@2:", when="@2:", type=("build", "run"))
|
||||||
|
depends_on("py-bar", type=("build", "run"))
|
||||||
|
depends_on("py-bz", type=("build", "run"))
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``with when()`` context manager is composable, while ``with default_args()``
|
||||||
|
merely overrides the default. For example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with default_args(when="+feature"):
|
||||||
|
depends_on("foo")
|
||||||
|
depends_on("bar")
|
||||||
|
depends_on("baz", when="+baz")
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
depends_on("foo", when="+feature")
|
||||||
|
depends_on("bar", when="+feature")
|
||||||
|
depends_on("baz", when="+baz") # Note: not when="+feature+baz"
|
||||||
|
|
||||||
.. _install-method:
|
.. _install-method:
|
||||||
|
|
||||||
------------------
|
------------------
|
||||||
|
|
|
@ -137,6 +137,7 @@ class DirectiveMeta(type):
|
||||||
_directive_dict_names: Set[str] = set()
|
_directive_dict_names: Set[str] = set()
|
||||||
_directives_to_be_executed: List[str] = []
|
_directives_to_be_executed: List[str] = []
|
||||||
_when_constraints_from_context: List[str] = []
|
_when_constraints_from_context: List[str] = []
|
||||||
|
_default_args: List[dict] = []
|
||||||
|
|
||||||
def __new__(cls, name, bases, attr_dict):
|
def __new__(cls, name, bases, attr_dict):
|
||||||
# Initialize the attribute containing the list of directives
|
# Initialize the attribute containing the list of directives
|
||||||
|
@ -199,6 +200,16 @@ def pop_from_context():
|
||||||
"""Pop the last constraint from the context"""
|
"""Pop the last constraint from the context"""
|
||||||
return DirectiveMeta._when_constraints_from_context.pop()
|
return DirectiveMeta._when_constraints_from_context.pop()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def push_default_args(default_args):
|
||||||
|
"""Push default arguments"""
|
||||||
|
DirectiveMeta._default_args.append(default_args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pop_default_args():
|
||||||
|
"""Pop default arguments"""
|
||||||
|
return DirectiveMeta._default_args.pop()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def directive(dicts=None):
|
def directive(dicts=None):
|
||||||
"""Decorator for Spack directives.
|
"""Decorator for Spack directives.
|
||||||
|
@ -259,7 +270,13 @@ def _decorator(decorated_function):
|
||||||
directive_names.append(decorated_function.__name__)
|
directive_names.append(decorated_function.__name__)
|
||||||
|
|
||||||
@functools.wraps(decorated_function)
|
@functools.wraps(decorated_function)
|
||||||
def _wrapper(*args, **kwargs):
|
def _wrapper(*args, **_kwargs):
|
||||||
|
# First merge default args with kwargs
|
||||||
|
kwargs = dict()
|
||||||
|
for default_args in DirectiveMeta._default_args:
|
||||||
|
kwargs.update(default_args)
|
||||||
|
kwargs.update(_kwargs)
|
||||||
|
|
||||||
# Inject when arguments from the context
|
# Inject when arguments from the context
|
||||||
if DirectiveMeta._when_constraints_from_context:
|
if DirectiveMeta._when_constraints_from_context:
|
||||||
# Check that directives not yet supporting the when= argument
|
# Check that directives not yet supporting the when= argument
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"""
|
"""
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from llnl.util.lang import caller_locals
|
from llnl.util.lang import caller_locals
|
||||||
|
|
||||||
|
@ -271,6 +272,13 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
spack.directives.DirectiveMeta.pop_from_context()
|
spack.directives.DirectiveMeta.pop_from_context()
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def default_args(**kwargs):
|
||||||
|
spack.directives.DirectiveMeta.push_default_args(kwargs)
|
||||||
|
yield
|
||||||
|
spack.directives.DirectiveMeta.pop_default_args()
|
||||||
|
|
||||||
|
|
||||||
class MultiMethodError(spack.error.SpackError):
|
class MultiMethodError(spack.error.SpackError):
|
||||||
"""Superclass for multimethod dispatch errors"""
|
"""Superclass for multimethod dispatch errors"""
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
UpstreamPackageError,
|
UpstreamPackageError,
|
||||||
)
|
)
|
||||||
from spack.mixins import filter_compiler_wrappers
|
from spack.mixins import filter_compiler_wrappers
|
||||||
from spack.multimethod import when
|
from spack.multimethod import default_args, when
|
||||||
from spack.package_base import (
|
from spack.package_base import (
|
||||||
DependencyConflictError,
|
DependencyConflictError,
|
||||||
build_system_flags,
|
build_system_flags,
|
||||||
|
|
|
@ -37,23 +37,24 @@ class PyBlack(PythonPackage):
|
||||||
depends_on("py-hatchling@1.8:", when="@22.10:", type="build")
|
depends_on("py-hatchling@1.8:", when="@22.10:", type="build")
|
||||||
depends_on("py-hatch-vcs", when="@22.10:", type="build")
|
depends_on("py-hatch-vcs", when="@22.10:", type="build")
|
||||||
depends_on("py-hatch-fancy-pypi-readme", when="@22.10:", type="build")
|
depends_on("py-hatch-fancy-pypi-readme", when="@22.10:", type="build")
|
||||||
depends_on("python@3.8:", when="@23.7:", type=("build", "run"))
|
|
||||||
# Needed to ensure that Spack can bootstrap black with Python 3.6
|
|
||||||
depends_on("python@3.7:", when="@22.10:", type=("build", "run"))
|
|
||||||
depends_on("py-click@8:", type=("build", "run"))
|
|
||||||
depends_on("py-mypy-extensions@0.4.3:", type=("build", "run"))
|
|
||||||
depends_on("py-packaging@22:", when="@23.1:", type=("build", "run"))
|
|
||||||
depends_on("py-pathspec@0.9:", type=("build", "run"))
|
|
||||||
depends_on("py-platformdirs@2:", type=("build", "run"))
|
|
||||||
depends_on("py-tomli@1.1:", when="@22.8: ^python@:3.10", type=("build", "run"))
|
|
||||||
depends_on("py-tomli@1.1:", when="@21.7:22.6", type=("build", "run"))
|
|
||||||
depends_on("py-typing-extensions@3.10:", when="^python@:3.9", type=("build", "run"))
|
|
||||||
|
|
||||||
depends_on("py-colorama@0.4.3:", when="+colorama", type=("build", "run"))
|
with default_args(type=("build", "run")):
|
||||||
depends_on("py-uvloop@0.15.2:", when="+uvloop", type=("build", "run"))
|
depends_on("python@3.8:", when="@23.7:")
|
||||||
depends_on("py-aiohttp@3.7.4:", when="+d", type=("build", "run"))
|
depends_on("python@3.7:", when="@22.10:")
|
||||||
depends_on("py-ipython@7.8:", when="+jupyter", type=("build", "run"))
|
depends_on("py-click@8:")
|
||||||
depends_on("py-tokenize-rt@3.2:", when="+jupyter", type=("build", "run"))
|
depends_on("py-mypy-extensions@0.4.3:")
|
||||||
|
depends_on("py-packaging@22:", when="@23.1:")
|
||||||
|
depends_on("py-pathspec@0.9:")
|
||||||
|
depends_on("py-platformdirs@2:")
|
||||||
|
depends_on("py-tomli@1.1:", when="@22.8: ^python@:3.10")
|
||||||
|
depends_on("py-tomli@1.1:", when="@21.7:22.6")
|
||||||
|
depends_on("py-typing-extensions@3.10:", when="^python@:3.9")
|
||||||
|
|
||||||
|
depends_on("py-colorama@0.4.3:", when="+colorama")
|
||||||
|
depends_on("py-uvloop@0.15.2:", when="+uvloop")
|
||||||
|
depends_on("py-aiohttp@3.7.4:", when="+d")
|
||||||
|
depends_on("py-ipython@7.8:", when="+jupyter")
|
||||||
|
depends_on("py-tokenize-rt@3.2:", when="+jupyter")
|
||||||
|
|
||||||
# Historical dependencies
|
# Historical dependencies
|
||||||
depends_on("py-setuptools@45:", when="@:22.8", type=("build", "run"))
|
depends_on("py-setuptools@45:", when="@:22.8", type=("build", "run"))
|
||||||
|
|
Loading…
Reference in a new issue