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
|
||||
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.force_result
|
||||
|
||||
Stash
|
||||
~~~~~
|
||||
|
||||
.. autoclass:: pytest.Stash
|
||||
:special-members: __setitem__, __getitem__, __delitem__, __contains__, __len__
|
||||
:members:
|
||||
|
||||
.. autoclass:: pytest.StashKey
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
|
||||
Global Variables
|
||||
----------------
|
||||
|
||||
|
|
|
@ -923,6 +923,15 @@ class Config:
|
|||
: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
|
||||
|
||||
self.trace = self.pluginmanager.trace.root.get("config")
|
||||
|
@ -931,9 +940,6 @@ class Config:
|
|||
self._override_ini: Sequence[str] = ()
|
||||
self._opt2dest: Dict[str, str] = {}
|
||||
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._configured = False
|
||||
self.hook.pytest_addoption.call_historic(
|
||||
|
|
|
@ -218,9 +218,13 @@ class Node(metaclass=NodeMeta):
|
|||
if self.name != "()":
|
||||
self._nodeid += "::" + self.name
|
||||
|
||||
# A place where plugins can store information on the node for their
|
||||
# own use. Currently only intended for internal plugins.
|
||||
self._store = Stash()
|
||||
#: A place where plugins can store information on the node for their
|
||||
#: own use.
|
||||
#:
|
||||
#: :type: Stash
|
||||
self.stash = Stash()
|
||||
# Deprecated alias. Was never public. Can be removed in a few releases.
|
||||
self._store = self.stash
|
||||
|
||||
@property
|
||||
def fspath(self) -> LEGACY_PATH:
|
||||
|
|
|
@ -14,7 +14,7 @@ D = TypeVar("D")
|
|||
|
||||
|
||||
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.
|
||||
|
||||
|
@ -29,17 +29,19 @@ class Stash:
|
|||
allows keys and value types to be defined separately from
|
||||
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
|
||||
|
||||
stash: Stash = some_object.stash
|
||||
|
||||
If a module wants to store data in this Stash, it creates ``StashKey``\s
|
||||
for its keys (at the module level):
|
||||
If a module or plugin wants to store data in this ``Stash``, it creates
|
||||
:class:`StashKey`\s for its keys (at the module level):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# At the top-level of the module
|
||||
some_str_key = StashKey[str]()
|
||||
some_bool_key = StashKey[bool]()
|
||||
|
||||
|
@ -59,25 +61,6 @@ class Stash:
|
|||
some_str = stash[some_str_key]
|
||||
# The static type of some_bool is bool.
|
||||
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",)
|
||||
|
|
|
@ -55,6 +55,8 @@ from _pytest.recwarn import deprecated_call
|
|||
from _pytest.recwarn import WarningsRecorder
|
||||
from _pytest.recwarn import warns
|
||||
from _pytest.runner import CallInfo
|
||||
from _pytest.stash import Stash
|
||||
from _pytest.stash import StashKey
|
||||
from _pytest.tmpdir import TempdirFactory
|
||||
from _pytest.tmpdir import TempPathFactory
|
||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||
|
@ -131,6 +133,8 @@ __all__ = [
|
|||
"Session",
|
||||
"set_trace",
|
||||
"skip",
|
||||
"Stash",
|
||||
"StashKey",
|
||||
"version_tuple",
|
||||
"TempPathFactory",
|
||||
"Testdir",
|
||||
|
|
Loading…
Reference in New Issue