pytest-dev/pytest · error · SyntaxError

not a valid python identifier

Error message

not a valid python identifier {keyword_name.value}

What it means

A SyntaxError raised in single_kwarg when the kwarg name token (parsed as an IDENT) is not a valid Python identifier according to str.isidentifier(). Because the tokenizer accepts a broad character class, an identifier-like token such as `1foo`, `a-b`, or `a.b` can reach this check and be rejected. The column reported is the 1-based position of the offending name.

Solutions

  1. Rename the kwarg to a valid Python identifier: letters, digits, underscores, not starting with a digit.
  2. Check that you did not accidentally include a hyphen or dot from a CLI flag.
  3. Define the marker's kwargs with proper identifier names in conftest or plugin code.

Example fix

# before
pytest -m "foo(1x='y')"
# after
pytest -m "foo(x='y')"
Defensive patterns

Strategy: type-guard

Validate before calling

def valid_kwarg_name(name: str) -> bool:
    return name.isidentifier() and not name[0].isdigit()

Type guard

def is_valid_marker_kwarg_name(name: str) -> bool:
    return isinstance(name, str) and name.isidentifier()

Prevention

When it happens

Trigger: Marker expressions like `-m "foo(1x='y')"`, `-m "foo(a-b='y')"`, or `-m "foo(a.b='y')"` where the kwarg name fails Python identifier rules. Also triggered by tokens that the lexer grouped but which are not legal parameter names.

Common situations: Using numeric or symbol-laden kwarg names in marker filters, mistyping a kwarg name, or assuming Python attribute syntax (dashes, dots) works in marker expressions.

Related errors


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

Appendix: source

Thrown at src/_pytest/mark/expression.py:217

    if ident:
        name = ast.Name(IDENT_PREFIX + ident.value, ast.Load())
        if s.accept(TokenType.LPAREN):
            ret = ast.Call(func=name, args=[], keywords=all_kwargs(s))
            s.accept(TokenType.RPAREN, reject=True)
        else:
            ret = name
        return ret

    s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT))


BUILTIN_MATCHERS = {"True": True, "False": False, "None": None}


def single_kwarg(s: Scanner) -> ast.keyword:
    keyword_name = s.accept(TokenType.IDENT, reject=True)
    if not keyword_name.value.isidentifier():
        raise SyntaxError(
            f"not a valid python identifier {keyword_name.value}",
            (FILE_NAME, 1, keyword_name.pos + 1, s.input),
        )
    if keyword.iskeyword(keyword_name.value):
        raise SyntaxError(
            f"unexpected reserved python keyword `{keyword_name.value}`",
            (FILE_NAME, 1, keyword_name.pos + 1, s.input),
        )
    s.accept(TokenType.EQUAL, reject=True)

    if value_token := s.accept(TokenType.STRING):
        value: str | int | bool | None = value_token.value[1:-1]  # strip quotes
    else:
        value_token = s.accept(TokenType.IDENT, reject=True)
        if (number := value_token.value).isdigit() or (
            number.startswith("-") and number[1:].isdigit()
        ):
            value = int(number)

View on GitHub (pinned to 0d6fbdeffa)