Skip to content

feat(mark): expose pytest.ParameterSet in public API (#13431) - #15193

Open
paneendrakumar0 wants to merge 3 commits into
pytest-dev:mainfrom
paneendrakumar0:expose-parameterset-13431
Open

paneendrakumar0 wants to merge 3 commits into
pytest-dev:mainfrom
paneendrakumar0:expose-parameterset-13431

Conversation

@paneendrakumar0

Copy link
Copy Markdown

Description

This PR addresses #13431 (part of the public API typing roadmap #7469) by exposing ParameterSet in the public pytest namespace as the return type of pytest.param().

Currently, developers using pytest.param in typed helper functions that return parameter sets receive private import warnings (such as Pyright's reportPrivateImportUsage) when importing ParameterSet from _pytest.mark.structures.

Summary of Changes

  • Public export: Exposed pytest.ParameterSet in src/pytest/__init__.py and included it in __all__.
  • Module declaration: Set __module__ = "pytest" on ParameterSet.
  • Private constructor: Refactored ParameterSet as a NamedTuple subclass with a __new__ constructor that calls check_ispytest(_ispytest) so direct instantiation issues a PytestDeprecationWarning, while internal factories (pytest.param(), extract_from(), etc.) instantiate with _ispytest=True.
  • Factory methods: Marked internal classmethods (param, extract_from) with :meta private: so Sphinx autodoc does not surface them under pytest.ParameterSet.
  • Documentation: Added pytest.ParameterSet to doc/en/reference/reference.rst under Objects, and removed the obsolete entry from nitpick_ignore in doc/en/conf.py.
  • Changelog: Added changelog/13431.improvement.rst.
  • Tests: Added comprehensive unit tests in testing/test_mark.py verifying public export, isinstance checking, deprecation warning on unflagged initialization, and pickle serialization.
  • Authors: Added myself to AUTHORS.

Closes #13431.

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Oct 16, 2026
@paneendrakumar0
paneendrakumar0 force-pushed the expose-parameterset-13431 branch from ab3cb09 to f03863b Compare October 16, 2026 09:20

@RonnyPfannschmidt RonnyPfannschmidt 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.

looks good, please add the extra documentation - the NOTSET followup is something we need to tinker a bit

Comment thread src/_pytest/mark/structures.py Outdated

class ParameterSet(NamedTuple):
"""A set of values for a set of parameters along with associated marks and
class _ParameterSetBase(NamedTuple):

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.

lets document hte minimal python version that makes this unnecessary for future cleanups

Comment thread src/_pytest/mark/structures.py Outdated
parameters.append(
ParameterSet(
values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET"
values=(NOTSET,) * len(argnames),

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.

followup note - we might want to rethink this NOTSET hack - most recent python added sentinels and it muddles the types,

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

i jsut noted that i was wrong on a detail, i'm reassessing a bit more

@RonnyPfannschmidt RonnyPfannschmidt 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.

🤖 Written by Claude Opus 5.5 via Claude Code for the pytest maintainers; I prompted it, it did the work, I read it.

Correcting my earlier review: my initial assessment was wrong. I'd like ParameterSet to be a completely public structure, not a private-constructor type.

Requested changes:

  • Drop _ispytest, the __new__/__reduce__ override and _ParameterSetBase. Go back to a plain class ParameterSet(NamedTuple). Direct construction has always worked (it was a plain NamedTuple), so the deprecation warning would be new breakage for existing users. Pickling works natively once __module__ points at pytest.
  • Type values as Sequence[object]. object | NotSetType collapses to object anyway, so the NotSetType part was only decorative.
  • Drop test_parameterset_direct_init_deprecated and keep the public-API and pickle tests.
  • That also resolves my line-85 comment: no Python version lifts the NamedTuple __new__/super() restriction (still a TypeError on 3.15), so there's no version to document once the split is gone.

I checked this locally on the PR head: mypy 2.4.0 and pyright 1.1.414 pass, and the full test suite passes.

One catch: mypy rejects __module__ = "pytest" inside a NamedTuple class body ("NamedTuple field name cannot start with an underscore"), so it has to be set after the class:

class ParameterSet(NamedTuple):
    ...

ParameterSet.__module__ = "pytest"

@bluetech, a detail I'd like your take on before we settle this:

  1. Set __module__ after the class definition, as above. This is the minimal change, but it's the first public class done this way; everything else sets __module__ = "pytest" in the class body (approx, config, warning_types).
  2. Put it in a public submodule of pytest so __module__ is naturally right. pytest currently has no public submodules, so this has its own rattail: a new import path we'd have to support (and that pickles would reference forever), docs and Sphinx paths, and import-cycle risk, since _pytest would import from pytest.* while pytest/__init__ imports _pytest.

My lean is option 1, since the rattail of option 2 seems larger than this type justifies. If we expect more NamedTuple/dataclass-style public types, though, it may be worth deciding the submodule question once.

The NOTSET placeholder for empty parameter sets stays as it is for now; I'll track that as a separate followup.


Generated by Claude Code

)

Expose ParameterSet publicly from pytest as the return type of
pytest.param() for type annotations and isinstance checks.

- Re-export ParameterSet in src/pytest/__init__.py and include in __all__
- Keep ParameterSet as a plain NamedTuple with values: Sequence[object]
- Set ParameterSet.__module__ = "pytest" after class definition
- Mark internal factory methods with :meta private: for Sphinx autodoc
- Document pytest.ParameterSet in doc/en/reference/reference.rst under Objects
- Add changelog entry and unit tests for public API and pickle roundtrip
@paneendrakumar0
paneendrakumar0 force-pushed the expose-parameterset-13431 branch from f03863b to 9ae02e6 Compare October 17, 2026 09:37
@paneendrakumar0

Copy link
Copy Markdown
Author

Thanks @RonnyPfannschmidt! Updated as requested:

  • Reverted ParameterSet to a plain NamedTuple without _ispytest or __new__/__reduce__ overrides.
  • Typed values: Sequence[object].
  • Set ParameterSet.__module__ = "pytest" after the class definition.
  • Dropped test_parameterset_direct_init_deprecated and verified direct instantiation alongside public API export and native pickling.
  • Full test suite and mypy pass cleanly.

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

Thanks im waiting for @bluetech 's assessment now

This branch has not been deployed

No deployments
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.

ParameterSet is private

2 participants