Alois
Alois

Reputation: 197

pathlib.path allowing other types for joining apart from string and Path

How can I change pathlib.Path._parse_args, so that I cannot only have other types (like LocalPath) as arguments to Path but also use other types as parameter for / based joining, etc..?

from pathlib import Path
Path(tmplib) / 25 / True

With tmplib a LocalPath from py._path.local, and the others automatically converted to their str() representations?

I tried sub-classing and monkey-patching as shown in this (pathlib Path and py.test LocalPath) question but that did not work.

Path looks very resistant to extending.

Upvotes: 4

Views: 4241

Answers (2)

Anthon
Anthon

Reputation: 76692

pathlib (and pathlib2 for Python versions < 3.4) primarily consists of four classes directly related to paths Path, PosixPath, WindowsPath and PurePath (BasePath in pathlib2). If you subclass each of these and copy and adapt the code for Path.__new__() and PurePath._parse_args() in the following way:

import os
import sys
if sys.version_info < (3, 4):
    import pathlib2 as pathlib
else:
    import pathlib


class PurePath(pathlib.Path):
    __slots__ = ()
    types_to_stringify = [int]

    @classmethod
    def _parse_args(cls, args):
        # This is useful when you don't want to create an instance, just
        # canonicalize some constructor arguments.
        parts = []
        for a in args:
            if isinstance(a, pathlib.PurePath):
                parts += a._parts
            elif sys.version_info < (3,) and isinstance(a, basestring):
                # Force-cast str subclasses to str (issue #21127)
                parts.append(str(a))
            elif sys.version_info >= (3, 4) and isinstance(a, str):
                # Force-cast str subclasses to str (issue #21127)
                parts.append(str(a))
            elif isinstance(a, tuple(PurePath.types_to_stringify)):
                parts.append(str(a))
            else:
                try:
                    parts.append(a)
                except:
                    raise TypeError(
                        "argument should be a path or str object, not %r"
                        % type(a))
        return cls._flavour.parse_parts(parts)


class WindowsPath(PurePath, pathlib.PureWindowsPath):
    __slots__ = ()


class PosixPath(PurePath, pathlib.PurePosixPath):
    __slots__ = ()


class Path(pathlib.Path):
    __slots__ = ()

    def __new__(cls, *args, **kwargs):
        if cls is Path:
            cls = WindowsPath if os.name == 'nt' else PosixPath
        self = cls._from_parts(args, init=False)
        if not self._flavour.is_supported:
            raise NotImplementedError("cannot instantiate %r on your system"
                                      % (cls.__name__,))
        self._init()
        return self

you will have a Path that already understand int and can be used to do:

from py._path.local import LocalPath

# extend the types to be converted to string on the fly
PurePath.types_to_stringify.extend([LocalPath, bool])

tmpdir = LocalPath('/var/tmp/abc')

p = Path(tmpdir) / 14 / False / 'testfile.yaml'
print(p)

to get:

/var/tmp/abc/14/False/testfile.yaml

(you'll need to install package pathlib2 for versions < 3.4 for this to work on those).

The above Path can be used as open(p) in Python 3.6.

Adapting _parse_args gives you automatic support for / (__truediv__) as well as methods like joinpath(), relative_to() etc.

Upvotes: 1

eigil
eigil

Reputation: 465

You can do something like this if you want to keep the platform independence magic:

from py._path.local import LocalPath
import os
import pathlib


class Path(pathlib.Path):

    def __new__(cls, *args, **kwargs):
        if cls is Path:
            cls = WindowsPath if os.name == 'nt' else PosixPath
        return cls._from_parts(map(str, args))

    def __truediv__(self, other):
        return super().__truediv__(str(other))

class WindowsPath(Path, pathlib.WindowsPath):
    pass
class PosixPath(Path, pathlib.PosixPath):
    pass

p = Path(LocalPath())
print(p / 25 / True)

Or just this if it's ok to be platform specific:

from py._path.local import LocalPath
import pathlib


class Path(pathlib.PosixPath):

    def __new__(cls, *args, **kwargs):
        return cls._from_parts(map(str, args))

    def __truediv__(self, other):
        return super().__truediv__(str(other))


p = Path(LocalPath())
print(p / 25 / True)

Upvotes: 1

Related Questions