{"record":{"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/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/mark/structures.py#L582-L618","documentation":"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.","triggerScenarios":"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.","commonSituations":"Naming a custom marker with a leading underscore for 'private' style, autocompletion inserting a dunder, or iterating attribute names that include private ones.","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."],"exampleFix":"# before\n@pytest.mark._slow\ndef test_x(): ...\n# after\n@pytest.mark.slow\ndef test_x(): ...","handlingStrategy":"validation","validationCode":"def is_public_marker_name(name: str) -> bool:\n    return isinstance(name, str) and bool(name) and name[0] != '_'","typeGuard":"def is_valid_marker_name(name: str) -> bool:\n    return isinstance(name, str) and len(name) > 0 and not name.startswith('_')","tryCatchPattern":null,"preventionTips":["Name markers without leading underscores.","Register markers in pytest.ini for validation.","Audit dynamic marker access via getattr."],"tags":["pytest","marks","attribute-error","naming","markergenerator"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}