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 obj

View on GitHub (pinned to 0d6fbdeffa)

Solutions

  1. Pass only 'basename', or only 'purebasename'/'ext' — never both at once.
  2. If you need to change the extension, use new(purebasename=..., ext=...) and drop 'basename'.
  3. If you need to set the full filename, use new(basename=...) alone.
  4. 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

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


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/e4dda3549e97df0b. Report an issue: GitHub.