pytest-dev/pytest · warning · ValueError

cannot delete key in keywords dict

Error message

cannot delete key in keywords dict

What it means

A ValueError raised by NodeKeywords.__delitem__ whenever code attempts `del node.keywords[key]`. The keywords mapping is a read-only union of a node's own markers and its parent's keywords; pytest deliberately makes deletion impossible to keep the keyword set consistent with the collected node tree. Any del operation, regardless of key, hits this guard.

Solutions

  1. Do not delete keywords; instead, deselect the item via pytest_collection_modifyitems (config.hook.pytest_deselected).
  2. If filtering by keyword, build a new list of selected items rather than mutating keywords.
  3. Use a custom marker on the test rather than relying on dynamically removing keywords.

Example fix

# before
del item.keywords['smoke']
# after
def pytest_collection_modifyitems(items, config):
    remaining = [i for i in items if 'smoke' not in i.keywords]
    config.hook.pytest_deselected(items=[i for i in items if i not in remaining])
    items[:] = remaining
Defensive patterns

Strategy: validation

Validate before calling

def remove_keyword(items, key):
    return [i for i in items if key not in i.keywords]
# never call del item.keywords[key]

Prevention

When it happens

Trigger: Calling `del item.keywords['smoke']`, `item.keywords.pop('x')` (if it routes through __delitem__), or any third-party code that mutates the keywords mapping. The method unconditionally raises.

Common situations: Plugins or conftest hooks that try to remove a keyword to influence selection; tests asserting on keywords and trying to clean up; porting code from a dict-like API.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/42adf7853bcf6af7. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/mark/structures.py:682

    # 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: 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]:
        # Doesn't need to be fast.
        yield from self._markers
        if self.parent is not None:
            for keyword in self.parent.keywords:
                # self._marks and self.parent.keywords can have duplicates.
                if keyword not in self._markers:
                    yield keyword

    def __len__(self) -> int:
        # Doesn't need to be fast.
        return sum(1 for keyword in self)

    def __repr__(self) -> str:
        return f"<NodeKeywords for node {self.node}>"

View on GitHub (pinned to 0d6fbdeffa)