[DONTMERGE] refactor: clean up node constructors — move logic to from_parent#14227
Conversation
… Item.from_parent The _check_item_and_collector_diamond_inheritance validation is not data storage — it's a one-time class-level check. Moving it to from_parent keeps __init__ focused on storing values. Also converts the method to a classmethod since it only inspects the class, not the instance. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
…m_parent Package.__init__ only did `session = parent.session` which FSCollector already handles. Removing the __init__ entirely and adding an explicit from_parent with a typed signature for clarity. Also removes the now-unused LEGACY_PATH import from python.py. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
…smethods and from_parent Extracts _derive_name and _derive_nodeid as classmethods on FSCollector. The from_parent method now pre-computes name and nodeid before calling super, so __init__ receives fully resolved values in the normal path. The __init__ retains fallback derivation for backward compatibility with non-cooperative constructors (plugins using positional args). Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
…to from_parent DoctestItem.__init__ now only stores runner, dtest, and obj. The fixture resolution (getfixtureinfo) and _initrequest() are moved to from_parent, which is the only production entry point for creating DoctestItem instances. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
…nit__ to from_parent Function.__init__ now only stores _obj, _instance, originalname, and callspec. The marker extension, keyword updates, fixture resolution (getfixtureinfo), and _initrequest() are extracted into a new _setup_markers_and_fixtures method called from from_parent after construction. This keeps __init__ focused on storing values while from_parent handles all post-construction initialization logic. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
1. FSCollector: fix double _imply_path call that would produce duplicate deprecation warnings when fspath is used. Now __init__ skips the _imply_path call when path is already resolved (by from_parent), using _check_path for consistency validation instead. 2. Function: remove dead keywords/fixtureinfo parameters from __init__ since from_parent now pops them before calling super. Replace with **kw for forward-compatibility with additional kwargs. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude <claude@anthropic.com>
|
moved to rfr to trigger copilot review - this one is likely to need more iteration |
There was a problem hiding this comment.
Pull request overview
Refactors pytest node construction so that initialization/derivation logic happens in from_parent, keeping __init__ methods closer to “attribute stores” and reducing side effects during object construction.
Changes:
- Updated
FSCollectorto pre-computepath,name, andnodeidinfrom_parent, and avoid duplicatefspathdeprecation warnings. - Moved
Item’s diamond-inheritance warning toItem.from_parent. - Moved fixture/marker setup out of
Function/DoctestItem.__init__intofrom_parent, and replacedPackage.__init__with an explicitPackage.from_parent.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/_pytest/python.py |
Refactors Package construction and moves Function fixture/marker setup into from_parent. |
src/_pytest/nodes.py |
Refactors FSCollector name/nodeid derivation into helpers and from_parent; moves Item diamond check into from_parent. |
src/_pytest/doctest.py |
Moves doctest fixture initialization from __init__ into DoctestItem.from_parent. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @classmethod | ||
| def from_parent( # type: ignore[override] | ||
| cls, | ||
| parent: nodes.Collector, | ||
| # NOTE: following args are unused: | ||
| config=None, | ||
| session=None, | ||
| nodeid=None, | ||
| path: Path | None = None, | ||
| ) -> None: | ||
| # NOTE: Could be just the following, but kept as-is for compat. | ||
| # super().__init__(self, fspath, parent=parent) | ||
| session = parent.session | ||
| super().__init__( | ||
| fspath=fspath, | ||
| path=path, | ||
| parent=parent, | ||
| config=config, | ||
| session=session, | ||
| nodeid=nodeid, | ||
| ) | ||
| *, | ||
| path: Path, | ||
| ) -> Self: | ||
| """The public constructor. | ||
|
|
||
| :param parent: The parent collector of this Package. | ||
| :param path: The package directory's path. | ||
| :type path: pathlib.Path | ||
| """ | ||
| return super().from_parent(parent=parent, path=path) |
There was a problem hiding this comment.
Package.from_parent overrides Directory/FSCollector.from_parent but drops the fspath keyword entirely. This is a runtime API break: external code/plugins that still call Package.from_parent(..., fspath=...) (even if deprecated) will now get TypeError: got an unexpected keyword argument 'fspath'. Consider keeping fspath: LEGACY_PATH | None = None (and optionally path: Path | None = None) in the signature and delegating to super().from_parent(...) after resolving/validating, so deprecated callers continue to work while steering new code to path.
| If given, the object which will be called when the Function is invoked, | ||
| otherwise the callobj will be obtained from ``parent`` using ``originalname``. | ||
| :param keywords: | ||
| Keywords bound to the function object for "-k" matching. | ||
| :param session: | ||
| The pytest Session object. | ||
| :param fixtureinfo: | ||
| Fixture information already resolved at this fixture node.. | ||
| :param originalname: | ||
| The attribute name to use for accessing the underlying function object. | ||
| Defaults to ``name``. Set this if name is different from the original name, | ||
| for example when it contains decorations like those added by parametrization | ||
| (``my_func[my_param]``). | ||
| """ | ||
|
|
||
| # Disable since functions handle it themselves. | ||
| _ALLOW_MARKERS = False | ||
|
|
||
| def __init__( | ||
| self, | ||
| name: str, | ||
| parent, | ||
| config: Config | None = None, | ||
| callspec: CallSpec2 | None = None, | ||
| callobj=NOTSET, | ||
| keywords: Mapping[str, Any] | None = None, | ||
| session: Session | None = None, | ||
| fixtureinfo: FuncFixtureInfo | None = None, | ||
| originalname: str | None = None, | ||
| **kw, | ||
| ) -> None: |
There was a problem hiding this comment.
The Function class docstring still documents keywords and fixtureinfo as constructor parameters, but __init__ no longer accepts them explicitly (they’re now handled in from_parent). This makes the public API docs misleading. Please update the docstring (or add a note) to reflect that keywords/fixtureinfo are from_parent kwargs and not __init__ parameters anymore.
Summary
Moves initialization logic out of node
__init__methods and intofrom_parent, completing the refactoring thatNodeMetawas designed to enable (seesrc/_pytest/nodes.pydocstring onNodeMeta).After this change,
__init__methods are "dumb stores" focused on assigning attributes, whilefrom_parentmethods handle derivation, validation, and post-construction setup.Changes by node class
Item: Diamond inheritance check (_check_item_and_collector_diamond_inheritance) moved from__init__tofrom_parent; converted to classmethodPackage: Removed redundant__init__(only didsession = parent.session, already handled byFSCollector); added explicit typedfrom_parentFSCollector: Extracted_derive_name/_derive_nodeidas classmethods;from_parentpre-computes name and nodeid;__init__retains fallback for non-cooperative constructorsDoctestItem: Fixture resolution (getfixtureinfo,_initrequest) moved from__init__tofrom_parentFunction: Marker extension, keyword updates, fixture resolution, and_initrequestextracted into_setup_markers_and_fixtures, called fromfrom_parent_imply_pathcall that would produce duplicate deprecation warnings; cleaned up dead__init__parametersWhat was NOT changed
Node.__init__config/session/path/nodeid derivation was left in place — pushing these intofrom_parentas kwargs breaks backward compat for plugins with non-cooperative constructors (theNodeMeta._createfallback filters unknown kwargs, causing TypeErrors). The derivation is triviallyparent.config/parent.session, andfrom_parentalready enforces callers can't override them.Test plan
test_nodes.py,test_collection.py,deprecated_test.py,python/collect.py,python/metafunc.py,test_doctest.py,test_runner.py,test_skipping.py,test_unittest.py,test_mark.py,test_junitxml.pyNote
This PR was AI-generated using Cursor with Claude (Anthropic). All commits include
Co-authored-byattribution for both Cursor AI and Anthropic Claude.Made with Cursor