pytest-dev/pytest · error · AttributeError
Marker name must NOT start with underscore
Error message
Marker name must NOT start with underscore
What it means
An AttributeError raised by MarkGenerator.__getattr__ when a user accesses an attribute starting with an underscore, e.g. `pytest.mark._foo`. pytest forbids underscore-prefixed marker names to avoid collisions with internal/dunder attributes and to signal that the name is private. The guard checks `name[0] == '_'` before any marker resolution.
Solutions
- Rename the marker without a leading underscore.
- If you intended a pytest-internal marker, use the public name instead.
- Register custom markers in pytest.ini/markers to get name validation and autocompletion without underscores.
Example fix
# before @pytest.mark._slow def test_x(): ... # after @pytest.mark.slow def test_x(): ...
Defensive patterns
Strategy: validation
Validate before calling
def is_public_marker_name(name: str) -> bool:
return isinstance(name, str) and bool(name) and name[0] != '_' Type guard
def is_valid_marker_name(name: str) -> bool:
return isinstance(name, str) and len(name) > 0 and not name.startswith('_') Prevention
- Name markers without leading underscores.
- Register markers in pytest.ini for validation.
- Audit dynamic marker access via getattr.
When it happens
Trigger: Writing `@pytest.mark._custom`, `pytest.mark.__something`, or programmatically `getattr(pytest.mark, '_x')`. Also triggered by typos that prepend an underscore or by copy-paste from internal pytest code.
Common situations: Naming a custom marker with a leading underscore for 'private' style, autocompletion inserting a dunder, or iterating attribute names that include private ones.
Related errors
- got instead of Mark
- pytest.param cannot add pytest.mark.usefixtures; see…
- has no attribute
- object at has no attribute
- absolute tolerance can't be NaN.
AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11).
Data as JSON: /api/errors/3688cf92b72ddc22.
Report an issue: GitHub.
Appendix: source
Thrown at src/_pytest/mark/structures.py:600
# See TYPE_CHECKING above.
if TYPE_CHECKING:
skip: _SkipMarkDecorator
skipif: _SkipifMarkDecorator
xfail: _XfailMarkDecorator
parametrize: _ParametrizeMarkDecorator
usefixtures: _UsefixturesMarkDecorator
filterwarnings: _FilterwarningsMarkDecorator
def __init__(self, *, _ispytest: bool = False) -> None:
check_ispytest(_ispytest)
self._config: Config | None = None
self._markers: set[str] = set()
def __getattr__(self, name: str) -> MarkDecorator:
"""Generate a new :class:`MarkDecorator` with the given name."""
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
if self._config is not None:
# We store a set of markers as a performance optimisation - if a mark
# name is in the set we definitely know it, but a mark may be known and
# not in the set. We therefore start by updating the set!
if name not in self._markers:
for line in self._config.getini("markers"):
# example lines: "skipif(condition): skip the given test if..."
# or "hypothesis: tests which use Hypothesis", so to get the
# marker name we split on both `:` and `(`.
marker = line.split(":")[0].split("(")[0].strip()
self._markers.add(marker)
# If the name is not in the set of known marks after updating,
# then it really is time to issue a warning or an error.
if name not in self._markers:
# Raise a specific error for common misspellings of "parametrize".
if name in ["parameterize", "parametrise", "parameterise"]:View on GitHub (pinned to 0d6fbdeffa)