[RFC] structured NamedTuples for code locations#8056
Conversation
nicoddemus
left a comment
There was a problem hiding this comment.
Looks good so far!
So the idea is to not change Item.location's return value or any other exposed value, just use CodeLocation internally?
| lineno = function.__code__.co_firstlineno | ||
|
|
||
| # TODO: this cycle indicates a larger issue | ||
| from .pathlib import bestrelpath |
There was a problem hiding this comment.
Hmm indeed, perhaps getlocation can be moved to pathlib then? Makes sense there, is all about obtaining the "path" location to a function.
| tw.write(" [%s scope]" % fixturedef.scope, cyan=True) | ||
| if verbose > 0: | ||
| tw.write(" -- %s" % bestrel, yellow=True) | ||
| tw.write(" -- %s" % str(bestrel), yellow=True) |
There was a problem hiding this comment.
since namedtuple isinstance tuple, %-formatting will try and unpack it. if this converts to f-string you won't need str(...)
There was a problem hiding this comment.
Good execuse to switch to an fstring :)
| tw.write(" [%s scope]" % fixturedef.scope, cyan=True) | ||
| if verbose > 0: | ||
| tw.write(" -- %s" % bestrel, yellow=True) | ||
| tw.write(" -- %s" % str(bestrel), yellow=True) |
There was a problem hiding this comment.
since namedtuple isinstance tuple, %-formatting will try and unpack it. if this converts to f-string you won't need str(...)
|
|
||
| import py | ||
|
|
||
| from _pytest.compat import assert_never |
There was a problem hiding this comment.
What is the reason for this change?
There was a problem hiding this comment.
import cycle maangement
There was a problem hiding this comment.
I thought
from _pytest.compat import assert_neverand
import _pytest.compatare equivalent from an import-cycle perspective. Maybe not...
There was a problem hiding this comment.
the latter delays the access of the module, allowing ~some cycles to occur (as long as there's no attribute access the cycle will import a "partially initialized module" which gets finalized later)
There was a problem hiding this comment.
Interesting, didn't know about this partially initialized state.
| tw = _pytest.config.create_terminal_writer(config) | ||
| verbose = config.getvalue("verbose") | ||
|
|
||
| def get_best_relpath(func): |
There was a problem hiding this comment.
IMO we can just inline this function now.
| tw.write(" [%s scope]" % fixturedef.scope, cyan=True) | ||
| if verbose > 0: | ||
| tw.write(" -- %s" % bestrel, yellow=True) | ||
| tw.write(" -- %s" % str(bestrel), yellow=True) |
There was a problem hiding this comment.
Good execuse to switch to an fstring :)
|
@nicoddemus for practical reasons we have to deprecate node.location and reportinfo() - its a hard to fix api adding a qualified name to the code location is no longer needed/sensible on modern python, where the qualified name is available pytest itself computes it by a listchain from hell i pushed my current state as im currently error blind on location determination wrt allowing/disallowing escape, the testsuite should be made to pass |
|
|
||
| import py | ||
|
|
||
| from _pytest.compat import assert_never |
There was a problem hiding this comment.
Interesting, didn't know about this partially initialized state.
356cbba to
4056e07
Compare
304995d to
02ad46a
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
02ad46a to
7b826c9
Compare
…0-based - CodeLocation: rename field `lineno` to `lineindex` (0-based stored), add `.lineno` property (1-based) and `__str__` using 1-based display. `getlocation()` now stores `co_firstlineno - 1` as lineindex. - ItemLocation: new NamedTuple (path, lineindex, testname) that replaces the bare `tuple[str, int | None, str]` returned by `Item.location`. Has `.lineno` property (1-based) and `__str__` for display. - Item.location returns ItemLocation instead of a plain tuple. Runtime-compatible: ItemLocation is a tuple subclass. - TestReport/BaseReport: location typed as ItemLocation, with coercion in __init__ for backwards compat. JSON deserialization in _report_kwargs_from_json reconstructs ItemLocation. - write_item in fixtures.py now uses item.location instead of getlocation(item.function, ...), eliminating the item.function assumption that broke for DoctestItem and custom Item subclasses. - Fixes off-by-one in getlocation display (was co_firstlineno + 1, now correctly co_firstlineno via 0-based storage + 1-based property). Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
pytest-xdist (<= 3.x) sends the location parameter from pytest_runtest_logstart/logfinish directly over execnet, which cannot serialize NamedTuple subclasses. Convert to plain tuple at the hook call site and in _report_to_json serialization. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
- Remove unused allow_escape parameter from getlocation() (added in this branch, never called with True) - Add ItemLocation.__str__ coverage tests for None lineindex - Add changelog entry for the feature Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
be17a37 to
0705197
Compare
Introduces
CodeLocationandItemLocationNamedTuples to replace the unstructured strings and bare tuples currently used for code locations. Both types store a 0-basedlineindexfield and expose a 1-based.linenoproperty, making the convention explicit rather than something each call site has to remember.CodeLocation(replacesstrfromgetlocation())Two fields:
path: Path,lineindex: int. Used for fixture locations in error messages and--fixtures/--fixtures-per-testoutput.__str__producespath:lineno(1-based).getlocation()previously storedco_firstlineno + 1— an off-by-one sinceco_firstlinenois already 1-based. Now storesco_firstlineno - 1(0-based) and lets the.linenoproperty add +1 back correctly.ItemLocation(replacestuple[str, int | None, str]fromItem.location)Three fields:
path: str,lineindex: int | None,testname: str. Returned byItem.location, stored onTestReport. Tuple subclass — all existing indexing, unpacking, and equality comparisons continue to work unchanged.Other changes
write_iteminfixtures.pynow usesitem.locationinstead ofgetlocation(item.function, ...), removing theitem.functionassumption that fails forDoctestItemand customItemsubclasses.BaseReport.location/TestReport.locationtyped asItemLocation.TestReport.__init__coerces plain tuples for backwards compatibility._report_kwargs_from_jsonreconstructsItemLocationafter JSON round-trip.reportinfo()contract, and JSON wire format are all unchanged.