pytest-dev/pytest · error · ValueError
invalid specification {kw!r}
Error message
invalid specification {kw!r} What it means
Raised by legacy pytest.path.LocalPath.new() when the keyword arguments used to derive a new path are internally contradictory. Specifically, passing 'basename' together with either 'purebasename' or 'ext' is rejected because basename already fully encodes both the pure base name and the extension, so specifying them alongside is ambiguous. The ValueError surfaces from the spec-resolution branch that validates kwargs before constructing the new path object.
Source
Thrown at src/_pytest/_py/path.py:660
the following keyword arguments modify various path parts::
a:/some/path/to/a/file.ext
xx drive
xxxxxxxxxxxxxxxxx dirname
xxxxxxxx basename
xxxx purebasename
xxx ext
"""
obj = object.__new__(self.__class__)
if not kw:
obj.strpath = self.strpath
return obj
drive, dirname, _basename, purebasename, ext = self._getbyspec(
"drive,dirname,basename,purebasename,ext"
)
if "basename" in kw:
if "purebasename" in kw or "ext" in kw:
raise ValueError(f"invalid specification {kw!r}")
else:
pb = kw.setdefault("purebasename", purebasename)
try:
ext = kw["ext"]
except KeyError:
pass
else:
if ext and not ext.startswith("."):
ext = "." + ext
kw["basename"] = pb + ext
if "dirname" in kw and not kw["dirname"]:
kw["dirname"] = drive
else:
kw.setdefault("dirname", dirname)
kw.setdefault("sep", self.sep)
obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw))
return objView on GitHub (pinned to 0d6fbdeffa)
Solutions
- Pass only 'basename', or only 'purebasename'/'ext' — never both at once.
- If you need to change the extension, use new(purebasename=..., ext=...) and drop 'basename'.
- If you need to set the full filename, use new(basename=...) alone.
- Switch to the modern pathlib API (pathlib.Path.with_name / with_suffix) which has no such conflict.
Example fix
// before p.new(basename='report.txt', ext='.csv') // after p.new(basename='report.csv')
Defensive patterns
Strategy: validation
Validate before calling
def safe_new(path, **kw):
if 'basename' in kw and ('purebasename' in kw or 'ext' in kw):
raise ValueError("Pass 'basename' OR ('purebasename'/'ext'), not both")
return path.new(**kw) Type guard
def is_consistent_spec(kw: dict) -> bool:
return not ('basename' in kw and ('purebasename' in kw or 'ext' in kw)) Try / catch
try:
p = path.new(**kw)
except ValueError as e:
if 'invalid specification' in str(e):
# resolve conflict and retry with single component
... Prevention
- Decide upfront whether you compose from basename or from purebasename+ext.
- Prefer pathlib.Path.with_name / with_suffix in new code.
When it happens
Trigger: Calling path.new(basename='foo.txt', ext='.log') or path.new(basename='foo', purebasename='bar') — any call where kwargs simultaneously contains 'basename' and at least one of 'purebasename'/'ext'.
Common situations: Refactoring test helpers that built paths from components; migrating from older py.path.local APIs; copy-pasting a path construction that mixed basename-level and component-level overrides.
Related errors
- invalid part specification {name!r}
- no {name!r} checker available for {self.path!r}
- XXX win32
- can only pass None, Path instances or non-empty strings to L
- {relpath!r}: not a string or path object
AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11).
Data as JSON: /api/errors/e4dda3549e97df0b.
Report an issue: GitHub.