diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index afe1aa6ef..7de8f9f54 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -539,6 +539,8 @@ MARK_GEN = MarkGenerator(_ispytest=True) @final class NodeKeywords(MutableMapping[str, Any]): + __slots__ = ("node", "parent", "_markers") + def __init__(self, node: "Node") -> None: self.node = node self.parent = node.parent @@ -555,21 +557,36 @@ class NodeKeywords(MutableMapping[str, Any]): def __setitem__(self, key: str, value: Any) -> None: self._markers[key] = value + # Note: we could've avoided explicitly implementing some of the methods + # below and use the collections.abc fallback, but that would be slow. + + def __contains__(self, key: object) -> bool: + return ( + key in self._markers + or self.parent is not None + and key in self.parent.keywords + ) + + def update( # type: ignore[override] + self, + other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), + **kwds: Any, + ) -> None: + self._markers.update(other) + self._markers.update(kwds) + def __delitem__(self, key: str) -> None: raise ValueError("cannot delete key in keywords dict") def __iter__(self) -> Iterator[str]: - seen = self._seen() - return iter(seen) - - def _seen(self) -> Set[str]: - seen = set(self._markers) + yield from self._markers if self.parent is not None: - seen.update(self.parent.keywords) - return seen + yield from self.parent.keywords def __len__(self) -> int: - return len(self._seen()) + return len(self._markers) + ( + len(self.parent.keywords) if self.parent is not None else 0 + ) def __repr__(self) -> str: return f""