pytest-dev/pytest · error · TypeError

{relpath!r}: not a string or path object

Error message

{relpath!r}: not a string or path object

What it means

LocalPath.relto returns the relative portion of this path with respect to a base path. It only accepts a str or another LocalPath as the base; any other type raises TypeError naming the bad argument. The restriction exists because relto does string-prefix matching on normalized path strings, so non-string-coercible objects would fail or produce wrong results.

Source

Thrown at src/_pytest/_py/path.py:436

            ?       matches any single character
            [seq]   matches any character in seq
            [!seq]  matches any char not in seq

        If the pattern contains a path-separator then the full path
        is used for pattern matching and a '*' is prepended to the
        pattern.

        if the pattern doesn't contain a path-separator the pattern
        is only matched against the basename.
        """
        return FNMatcher(pattern)(self)

    def relto(self, relpath):
        """Return a string which is the relative part of the path
        to the given 'relpath'.
        """
        if not isinstance(relpath, str | LocalPath):
            raise TypeError(f"{relpath!r}: not a string or path object")
        strrelpath = str(relpath)
        if strrelpath and strrelpath[-1] != self.sep:
            strrelpath += self.sep
        # assert strrelpath[-1] == self.sep
        # assert strrelpath[-2] != self.sep
        strself = self.strpath
        if sys.platform == "win32" or getattr(os, "_name", None) == "nt":
            if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)):
                return strself[len(strrelpath) :]
        elif strself.startswith(strrelpath):
            return strself[len(strrelpath) :]
        return ""

    def ensure_dir(self, *args):
        """Ensure the path joined with args is a directory."""
        return self.ensure(*args, dir=True)

    def bestrelpath(self, dest):

View on GitHub (pinned to 0d6fbdeffa)

Solutions

  1. Convert the base to str: `path.relto(str(base_path))`.
  2. Convert the base to LocalPath: `path.relto(LocalPath(base_path))`.
  3. Migrate fully to pathlib and use Path.relative_to / os.path.relpath instead.

Example fix

# before
path.relto(pathlib.Path('/base'))
# after
path.relto(str(pathlib.Path('/base')))
Defensive patterns

Strategy: type-guard

Validate before calling

def is_relto_base(v) -> bool:
    return isinstance(v, (str, LocalPath))
# usage
base = str(base) if not is_relto_base(base) else base
rel = path.relto(base)

Type guard

def is_str_or_localpath(v) -> bool:
    # NOTE: pathlib.Path is intentionally NOT accepted by relto
    return isinstance(v, str) or isinstance(v, LocalPath)

Prevention

When it happens

Trigger: Calling path.relto(pathlib.Path('/base')) (pathlib.Path is not accepted!), path.relto(123), path.relto(None), path.relto(some_object). Triggered at LocalPath.relto (src/_pytest/_py/path.py:435-436). Note: this rejects pathlib.Path even though it is path-like, because the API predates pathlib.

Common situations: Mixing pathlib.Path and legacy py.path.local in the same codebase; passing a Path object where a string was assumed; refactoring from one API to the other.

Related errors


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