{"id":"3688cf92b72ddc22","repo":"pytest-dev/pytest","slug":"marker-name-must-not-start-with-underscore","errorCode":null,"errorMessage":"Marker name must NOT start with underscore","messagePattern":"Marker name must NOT start with underscore","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/mark/structures.py","lineNumber":600,"sourceCode":"\n    # See TYPE_CHECKING above.\n    if TYPE_CHECKING:\n        skip: _SkipMarkDecorator\n        skipif: _SkipifMarkDecorator\n        xfail: _XfailMarkDecorator\n        parametrize: _ParametrizeMarkDecorator\n        usefixtures: _UsefixturesMarkDecorator\n        filterwarnings: _FilterwarningsMarkDecorator\n\n    def __init__(self, *, _ispytest: bool = False) -> None:\n        check_ispytest(_ispytest)\n        self._config: Config | None = None\n        self._markers: set[str] = set()\n\n    def __getattr__(self, name: str) -> MarkDecorator:\n        \"\"\"Generate a new :class:`MarkDecorator` with the given name.\"\"\"\n        if name[0] == \"_\":\n            raise AttributeError(\"Marker name must NOT start with underscore\")\n\n        if self._config is not None:\n            # We store a set of markers as a performance optimisation - if a mark\n            # name is in the set we definitely know it, but a mark may be known and\n            # not in the set.  We therefore start by updating the set!\n            if name not in self._markers:\n                for line in self._config.getini(\"markers\"):\n                    # example lines: \"skipif(condition): skip the given test if...\"\n                    # or \"hypothesis: tests which use Hypothesis\", so to get the\n                    # marker name we split on both `:` and `(`.\n                    marker = line.split(\":\")[0].split(\"(\")[0].strip()\n                    self._markers.add(marker)\n\n            # If the name is not in the set of known marks after updating,\n            # then it really is time to issue a warning or an error.\n            if name not in self._markers:\n                # Raise a specific error for common misspellings of \"parametrize\".\n                if name in [\"parameterize\", \"parametrise\", \"parameterise\"]:","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/mark/structures.py#L582-L618","documentation":"`MarkGenerator.__getattr__` is what makes `pytest.mark.<name>` work. It refuses names beginning with `_` to prevent collision with internal/dunder attributes (and accidental access to private generator state like `_config`). It raises AttributeError with this message.","triggerScenarios":"`pytest.mark._foo`, `pytest.mark.__something`, or any access `getattr(pytest.mark, '_x')`.","commonSituations":"Programmatically constructing marker names from input that may start with `_`; copying internal-style names.","solutions":["Rename the marker so it does not start with an underscore.","When generating marker names from external input, strip or reject leading underscores."],"exampleFix":"# before\n@pytest.mark._internal\ndef test_x(): ...\n# after\n@pytest.mark.internal\ndef test_x(): ...","handlingStrategy":"validation","validationCode":"def marker_name_ok(name: str) -> bool:\n    return not name.startswith('_')","typeGuard":"def is_public_marker_name(name: object) -> bool:\n    return isinstance(name, str) and not name.startswith('_')","tryCatchPattern":null,"preventionTips":["Register custom markers without leading underscores.","Sanitize generated marker names from external input."],"tags":["pytest","marks","attribute-error","naming"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}