stash: make Stash and StashKey public
It's had enough time to bake - let's allow external plugins to use it.
This commit is contained in:
parent
5f39e31736
commit
c6bdeb8491
|
@ -0,0 +1,2 @@
|
||||||
|
Added :class:`pytest.Stash`, a facility for plugins to store their data on :class:`~pytest.Config` and :class:`~_pytest.nodes.Node`\s in a type-safe and conflict-free manner.
|
||||||
|
See :ref:`plugin-stash` for details.
|
|
@ -311,3 +311,42 @@ declaring the hook functions directly in your plugin module, for example:
|
||||||
|
|
||||||
This has the added benefit of allowing you to conditionally install hooks
|
This has the added benefit of allowing you to conditionally install hooks
|
||||||
depending on which plugins are installed.
|
depending on which plugins are installed.
|
||||||
|
|
||||||
|
.. _plugin-stash:
|
||||||
|
|
||||||
|
Storing data on items across hook functions
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
Plugins often need to store data on :class:`~pytest.Item`\s in one hook
|
||||||
|
implementation, and access it in another. One common solution is to just
|
||||||
|
assign some private attribute directly on the item, but type-checkers like
|
||||||
|
mypy frown upon this, and it may also cause conflicts with other plugins.
|
||||||
|
So pytest offers a better way to do this, :attr:`_pytest.nodes.Node.stash <item.stash>`.
|
||||||
|
|
||||||
|
To use the "stash" in your plugins, first create "stash keys" somewhere at the
|
||||||
|
top level of your plugin:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
been_there_key: pytest.StashKey[bool]()
|
||||||
|
done_that_key: pytest.StashKey[str]()
|
||||||
|
|
||||||
|
then use the keys to stash your data at some point:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def pytest_runtest_setup(item: pytest.Item) -> None:
|
||||||
|
item.stash[been_there_key] = True
|
||||||
|
item.stash[done_that_key] = "no"
|
||||||
|
|
||||||
|
and retrieve them at another point:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(item: pytest.Item) -> None:
|
||||||
|
if not item.stash[been_there_key]:
|
||||||
|
print("Oh?")
|
||||||
|
item.stash[done_that_key] = "yes!"
|
||||||
|
|
||||||
|
Stashes are available on all node types (like :class:`~pytest.Class`,
|
||||||
|
:class:`~pytest.Session`) and also on :class:`~pytest.Config`, if needed.
|
||||||
|
|
|
@ -962,6 +962,18 @@ Result used within :ref:`hook wrappers <hookwrapper>`.
|
||||||
.. automethod:: pluggy.callers._Result.get_result
|
.. automethod:: pluggy.callers._Result.get_result
|
||||||
.. automethod:: pluggy.callers._Result.force_result
|
.. automethod:: pluggy.callers._Result.force_result
|
||||||
|
|
||||||
|
Stash
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: pytest.Stash
|
||||||
|
:special-members: __setitem__, __getitem__, __delitem__, __contains__, __len__
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: pytest.StashKey
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Global Variables
|
Global Variables
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -923,6 +923,15 @@ class Config:
|
||||||
:type: PytestPluginManager
|
:type: PytestPluginManager
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.stash = Stash()
|
||||||
|
"""A place where plugins can store information on the config for their
|
||||||
|
own use.
|
||||||
|
|
||||||
|
:type: Stash
|
||||||
|
"""
|
||||||
|
# Deprecated alias. Was never public. Can be removed in a few releases.
|
||||||
|
self._store = self.stash
|
||||||
|
|
||||||
from .compat import PathAwareHookProxy
|
from .compat import PathAwareHookProxy
|
||||||
|
|
||||||
self.trace = self.pluginmanager.trace.root.get("config")
|
self.trace = self.pluginmanager.trace.root.get("config")
|
||||||
|
@ -931,9 +940,6 @@ class Config:
|
||||||
self._override_ini: Sequence[str] = ()
|
self._override_ini: Sequence[str] = ()
|
||||||
self._opt2dest: Dict[str, str] = {}
|
self._opt2dest: Dict[str, str] = {}
|
||||||
self._cleanup: List[Callable[[], None]] = []
|
self._cleanup: List[Callable[[], None]] = []
|
||||||
# A place where plugins can store information on the config for their
|
|
||||||
# own use. Currently only intended for internal plugins.
|
|
||||||
self._store = Stash()
|
|
||||||
self.pluginmanager.register(self, "pytestconfig")
|
self.pluginmanager.register(self, "pytestconfig")
|
||||||
self._configured = False
|
self._configured = False
|
||||||
self.hook.pytest_addoption.call_historic(
|
self.hook.pytest_addoption.call_historic(
|
||||||
|
|
|
@ -218,9 +218,13 @@ class Node(metaclass=NodeMeta):
|
||||||
if self.name != "()":
|
if self.name != "()":
|
||||||
self._nodeid += "::" + self.name
|
self._nodeid += "::" + self.name
|
||||||
|
|
||||||
# A place where plugins can store information on the node for their
|
#: A place where plugins can store information on the node for their
|
||||||
# own use. Currently only intended for internal plugins.
|
#: own use.
|
||||||
self._store = Stash()
|
#:
|
||||||
|
#: :type: Stash
|
||||||
|
self.stash = Stash()
|
||||||
|
# Deprecated alias. Was never public. Can be removed in a few releases.
|
||||||
|
self._store = self.stash
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fspath(self) -> LEGACY_PATH:
|
def fspath(self) -> LEGACY_PATH:
|
||||||
|
|
|
@ -14,7 +14,7 @@ D = TypeVar("D")
|
||||||
|
|
||||||
|
|
||||||
class StashKey(Generic[T]):
|
class StashKey(Generic[T]):
|
||||||
"""``StashKey`` is an object used as a key to a ``Stash``.
|
"""``StashKey`` is an object used as a key to a :class:`Stash`.
|
||||||
|
|
||||||
A ``StashKey`` is associated with the type ``T`` of the value of the key.
|
A ``StashKey`` is associated with the type ``T`` of the value of the key.
|
||||||
|
|
||||||
|
@ -29,17 +29,19 @@ class Stash:
|
||||||
allows keys and value types to be defined separately from
|
allows keys and value types to be defined separately from
|
||||||
where it (the ``Stash``) is created.
|
where it (the ``Stash``) is created.
|
||||||
|
|
||||||
Usually you will be given an object which has a ``Stash``:
|
Usually you will be given an object which has a ``Stash``, for example
|
||||||
|
:class:`~pytest.Config` or a :class:`~_pytest.nodes.Node`:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
stash: Stash = some_object.stash
|
stash: Stash = some_object.stash
|
||||||
|
|
||||||
If a module wants to store data in this Stash, it creates ``StashKey``\s
|
If a module or plugin wants to store data in this ``Stash``, it creates
|
||||||
for its keys (at the module level):
|
:class:`StashKey`\s for its keys (at the module level):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# At the top-level of the module
|
||||||
some_str_key = StashKey[str]()
|
some_str_key = StashKey[str]()
|
||||||
some_bool_key = StashKey[bool]()
|
some_bool_key = StashKey[bool]()
|
||||||
|
|
||||||
|
@ -59,25 +61,6 @@ class Stash:
|
||||||
some_str = stash[some_str_key]
|
some_str = stash[some_str_key]
|
||||||
# The static type of some_bool is bool.
|
# The static type of some_bool is bool.
|
||||||
some_bool = stash[some_bool_key]
|
some_bool = stash[some_bool_key]
|
||||||
|
|
||||||
Why use this?
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Problem: module Internal defines an object. Module External, which
|
|
||||||
module Internal doesn't know about, receives the object and wants to
|
|
||||||
attach information to it, to be retrieved later given the object.
|
|
||||||
|
|
||||||
Bad solution 1: Module External assigns private attributes directly on
|
|
||||||
the object. This doesn't work well because the type checker doesn't
|
|
||||||
know about these attributes and it complains about undefined attributes.
|
|
||||||
|
|
||||||
Bad solution 2: module Internal adds a ``Dict[str, Any]`` attribute to
|
|
||||||
the object. Module External stores its data in private keys of this dict.
|
|
||||||
This doesn't work well because retrieved values are untyped.
|
|
||||||
|
|
||||||
Good solution: module Internal adds a ``Stash`` to the object. Module
|
|
||||||
External mints StashKeys for its own keys. Module External stores and
|
|
||||||
retrieves its data using these keys.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_storage",)
|
__slots__ = ("_storage",)
|
||||||
|
|
|
@ -55,6 +55,8 @@ from _pytest.recwarn import deprecated_call
|
||||||
from _pytest.recwarn import WarningsRecorder
|
from _pytest.recwarn import WarningsRecorder
|
||||||
from _pytest.recwarn import warns
|
from _pytest.recwarn import warns
|
||||||
from _pytest.runner import CallInfo
|
from _pytest.runner import CallInfo
|
||||||
|
from _pytest.stash import Stash
|
||||||
|
from _pytest.stash import StashKey
|
||||||
from _pytest.tmpdir import TempdirFactory
|
from _pytest.tmpdir import TempdirFactory
|
||||||
from _pytest.tmpdir import TempPathFactory
|
from _pytest.tmpdir import TempPathFactory
|
||||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||||
|
@ -131,6 +133,8 @@ __all__ = [
|
||||||
"Session",
|
"Session",
|
||||||
"set_trace",
|
"set_trace",
|
||||||
"skip",
|
"skip",
|
||||||
|
"Stash",
|
||||||
|
"StashKey",
|
||||||
"version_tuple",
|
"version_tuple",
|
||||||
"TempPathFactory",
|
"TempPathFactory",
|
||||||
"Testdir",
|
"Testdir",
|
||||||
|
|
Loading…
Reference in New Issue