Skip to content

Clarify the relation between marks and keywords - #15011

Open
RonnyPfannschmidt wants to merge 7 commits into
pytest-dev:mainfrom
RonnyPfannschmidt:marks-keywords-4569
Open

RonnyPfannschmidt wants to merge 7 commits into
pytest-dev:mainfrom
RonnyPfannschmidt:marks-keywords-4569

Conversation

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

I prompted an agent (Claude Opus 5 in Claude Code) to work through #4569. The
investigation, the commits and this description are its work; I read it and I
am posting it.


#4569 asks to revisit the relation between marks and keywords. Mapping what
that relation actually is came first, and it changes the shape of the issue:

-k does not read item.keywords, and has not since 2.4.0 (#306).
KeywordMatcher.from_item builds its own name set from the node chain,
extra_keyword_matches, the test function's __dict__ and iter_markers().
node.keywords is a separate mapping, fed by different code, read only by
request.keywords, TestReport.keywords and the terminal's skip folding.

So the two mark-to-keyword hacks from #4564 and #4649 are independent of one
another, and the # todo: this is a hell of a hack comment pointing at #4569
sits on the one that does not influence selection at all. That makes most of
#4569 a documentation and coverage problem rather than a refactoring one.

One commit per item:

Behaviour

  • -k no longer matches attributes that land in a test function's __dict__
    without anyone meaning them as keywords. -k wrapped selected every
    functools.wraps-decorated test and -k pytestmark every directly marked
    one. Private and dunder names are skipped, plus a named set. The legacy
    test_func.slow = True idiom keeps working.
  • add_marker stores a Mark in node.keywords instead of a
    MarkDecorator, matching what collection-time marks store.

Documentation

  • The -k help text described the pre-5.4 implementation ("a Python evaluable
    expression"), limited matching to test names and parent classes, and never
    mentioned markers — the thing The-Compiler's comment on revisit relation between marks and keywords #4569 asks for. It
    now lists the real keyword sources and contrasts with -m.
  • markers.rst framed -k as name-only and corrected itself only in a
    trailing paragraph; usage.rst, which the option reference links to, still
    described eval semantics.
  • KeywordMatcher, Function's keywords parameter, TestReport.keywords,
    Node.keywords and FixtureRequest.keywords docstrings were stale or wrong.
  • The --runslow and incremental examples use get_closest_marker rather than
    "name" in item.keywords.

Tests

Pins -k matching marks from module/class/base classes, -k ignoring marker
arguments, -k seeing a mark added by a conftest collection hook,
extra_keyword_matches on an item, and writing into item.keywords having no
effect on -k or -m — despite the 2.3.4 changelog claiming it integrates
with -m. Also enables the -k half of test_mark_expressions_no_smear,
commented out since the marker transfer was removed, and pins that -m is
case sensitive.

Deliberately left open

Whether the function.__dict__ scan should exist at all is the remaining
design question in #4569. Dropping it means deprecating attribute-marks and
finding another discriminator for the skip folding in terminal.py, which
keys off "pytestmark" in report.keywords. Unifying KeywordMatcher with
node.keywords depends on that answer, so neither is attempted here.

🤖 Generated with Claude Code

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Sep 13, 2026

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see anything outrageous but I'm not sure I'm knowledgable enough to do this review.

@bluetech bluetech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, look like good changes to me.

Comment thread doc/en/example/markers.rst Outdated
Comment thread doc/en/example/markers.rst Outdated
Comment thread changelog/4569.doc.rst Outdated
Comment thread changelog/4569.bugfix.rst Outdated
Comment thread doc/en/how-to/usage.rst Outdated
Comment thread doc/en/reference/reference.rst Outdated
Comment thread src/_pytest/mark/__init__.py Outdated
Comment thread src/_pytest/mark/__init__.py
Comment thread src/_pytest/fixtures.py Outdated
Comment thread src/_pytest/reports.py Outdated
Comment thread doc/en/example/markers.rst Outdated
RonnyPfannschmidt and others added 2 commits September 16, 2026 08:17
The -k matcher scans a test function's __dict__ to support the legacy
`test_fn.foo = True` keyword idiom, but that scan also picked up
attributes nobody meant as keywords: pytest's own `pytestmark` storage,
and the bookkeeping decorators leave behind (`__wrapped__` and the rest
of what functools.wraps copies, lru_cache's `cache_parameters`).

So `-k wrapped` selected every wraps-decorated test and `-k pytestmark`
selected every directly marked one.

Skip private and dunder names, plus a named set of known attributes.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
Marks applied during collection are stored in node.keywords as Mark
objects; add_marker stored the MarkDecorator instead, so the value type
depended on how the mark got there.

Nothing inside pytest reads keyword values, and both types expose
name/args/kwargs, so downstream `item.keywords["x"].args` keeps working.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
RonnyPfannschmidt and others added 5 commits September 16, 2026 09:31
The -k help text still described the pre-5.4 implementation ("a Python
evaluable expression"), claimed matching is limited to test names and
their parent classes, and never mentioned that marker names are
keywords - which is the behaviour people trip over in pytest-dev#4569.

Replace it with the real list of keyword sources, and state the
substring/case-insensitive contrast with -m in both option blocks.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
KeywordMatcher claimed it only matches Class and Function names, which
stopped being true in 2.4.0; Function's `keywords` parameter claimed it
feeds "-k" matching, which it does not; TestReport.keywords promised a
name -> value mapping while the values are all 1, so annotate it
`Literal[1]` and let the type checker hold that promise.

`FixtureRequest.applymarker` spoke of "a single test function
invocation" without saying what that is in contrast to; say that it is
the request's node, and that decorating the function marks every
invocation instead.

Also state what Node.keywords is for, and that -k does not read it.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
The markers.rst section title and intro presented -k as matching test
names, in contrast to -m matching markers, and only corrected itself in
a trailing paragraph after three console dumps. usage.rst, the page the
option reference links to, still described the pre-5.4 eval semantics
and listed only filenames, classes and functions.

Keep the keyword list where it was, after the examples -- the reader
needs to see -k work before being told what else it looks at -- but make
it a list, and give each entry a name from the example module rather
than a description in the abstract. The example module already carries
the point: test_send_http is marked webtest, so `-k webtest` selects a
test whose name contains no "webtest", and `-k device` selects both
device tests where the -m expression above tells their arguments apart.

Label the section so the other pages can point at it. usage.rst is a
getting-started page, so it keeps one sentence and the link; the option
reference keeps the exhaustive list and links here for the examples.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
The --runslow and incremental examples tested for a marker with
`"name" in item.keywords`, which is also true when a parent node happens
to be named that, or when the test function carries an attribute of that
name. These snippets are widely copied, so they are where the idiom of
treating keywords as a mark lookup keeps coming from.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
Several long-standing behaviours had no test: -k matching marks from the
module, class and base classes; -k ignoring marker arguments; -k seeing
a mark added by a conftest collection hook; extra_keyword_matches on an
item rather than a collector; and writing into item.keywords having no
effect on either -k or -m, despite the 2.3.4 changelog claiming it
"integrates with the -m option".

Also enable the -k half of test_mark_expressions_no_smear, commented out
since the marker transfer that made marks smear onto a shared base class
was removed, and pin that -m is case sensitive.

Part of pytest-dev#4569.

Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants