Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Allow users to explicitly specify path module (e.g. posixpath) #9

merged 2 commits into from

2 participants


This change allows users to specify an alternate path module instead of os.path on a per-instance basis, as we discussed in issue #8.


I'm sorry, I didn't know that a pull request would open its own, duplicate issue. Is there a way I can fix this mess?


Don't worry about it. Apparently, it's possible to convert an issue into a pull request using their API (, but some say they don't even recommend doing that. Having the issue and pull request tracked separately has its advantages.


One comment - when the new paths are created, should they be created using the same 'module'? i.e.:

def abspath(self):       return self.__class__(self.module.abspath(self), self.module)

Surely, we need a test to ensure that the following produces paths with consistent, predictable path separators:

path('foo', posixpath) / 'bar' / 'baz'
path('foo', ntpath) / 'bar' / 'baz'

At this point, I start to wonder if it makes sense instead to use a class attribute, i.e.:

class path(unicode):
    module = os.path

Then a subclass could override the module:

class WinPath(path):
    module = ntpath

or an application that doesn't care about affecting all path instances could simply replace the class attribute for all path instances:

# default to posixpath for all instances
path.module = posixpath

It also means that the constructor signature doesn't change, which is a huge boon in terms of avoiding conflicts in the future.

It does complicate the basic usage, however, where one wants to construct an explicit path using an explicit module. For that, I see a couple of options. One option, a class constructor that simply sets the module attribute:

def using_module(cls, source, module=None):
    path_ob = cls(source)
    if module: path_ob.module = module

Then, one could construct a simple path with customized module as so:

my_path = path.using_module('foo', module=posixpath)

However, that path's methods would end up returning paths of the default module. Perhaps instead, using_module should create a new class whose module is customized:

def using_module(cls, module):
    cls_name = cls.__name__ + '_' + module.__name__
    bases = (path,)
    ns = {'module': module}
    return type(cls_name, bases, ns)

Then, one could construct a path with a customized module as so:

my_path = path.using_module(posixpath)('foo')

We could even create handy aliases for the two common use-cases:

posix_path = path.using_module(posixpath)
nt_path = path.using_module(ntpath)

Although this last implementation may seem the most complicated at first blush, I like it the most because it's the least intrusive and the most flexible.


Oops, I dropped the ball there. I should have included the code that passes module reference in every method that creates a new path.

I guess that just underlines what you said: adding a constructor argument complicates things more than having it in the class.

I didn't think of creating classes dynamically. I'll try the approach you described at the end.


I like it. I think I'd like to omit the value argument to using_module, as the caller can just as easily construct an instance with a value from the class that's returned, and it's not obvious which method would be preferable. All in all, great work!

@jaraco jaraco merged commit d36450e into jaraco:master

1 check passed

Details default The Travis build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 13, 2012
  1. Allow users to explicitly specify an alternate path module (e.g. posi…

    Vojislav Stojkovic authored
This page is out of date. Refresh to see the latest.
Showing with 75 additions and 47 deletions.
  1. +61 −47
  2. +14 −0
@@ -77,10 +77,27 @@ class path(unicode):
counterparts in os.path.
+ # Use an alternate path module (e.g. posixpath)
+ module = os.path
+ _subclass_for_module = {}
+ @classmethod
+ def using_module(cls, module, value=None):
+ if module in cls._subclass_for_module:
+ subclass = cls._subclass_for_module[module]
+ else:
+ subclass_name = cls.__name__ + '_' + module.__name__
+ bases = (cls,)
+ ns = {'module': module}
+ subclass = type(subclass_name, bases, ns)
+ cls._subclass_for_module[module] = subclass
+ if value is None:
+ return subclass
+ return subclass(value)
# --- Special Python methods.
def __repr__(self):
- return 'path(%s)' % super(path, self).__repr__()
+ return '%s(%s)' % (type(self).__name__, super(path, self).__repr__())
# Adding a path and a string yields a path.
def __add__(self, more):
@@ -101,7 +118,7 @@ def __div__(self, rel):
Join two path components, adding a separator character if
- return self.__class__(os.path.join(self, rel))
+ return self.__class__(self.module.join(self, rel))
# Make the / operator work even when true division is enabled.
__truediv__ = __div__
@@ -121,14 +138,14 @@ def getcwd(cls):
# --- Operations on path strings.
- def abspath(self): return self.__class__(os.path.abspath(self))
- def normcase(self): return self.__class__(os.path.normcase(self))
- def normpath(self): return self.__class__(os.path.normpath(self))
- def realpath(self): return self.__class__(os.path.realpath(self))
- def expanduser(self): return self.__class__(os.path.expanduser(self))
- def expandvars(self): return self.__class__(os.path.expandvars(self))
- def dirname(self): return self.__class__(os.path.dirname(self))
- def basename(self): return self.__class__(os.path.basename(self))
+ def abspath(self): return self.__class__(self.module.abspath(self))
+ def normcase(self): return self.__class__(self.module.normcase(self))
+ def normpath(self): return self.__class__(self.module.normpath(self))
+ def realpath(self): return self.__class__(self.module.realpath(self))
+ def expanduser(self): return self.__class__(self.module.expanduser(self))
+ def expandvars(self): return self.__class__(self.module.expandvars(self))
+ def dirname(self): return self.__class__(self.module.dirname(self))
+ def basename(self): return self.__class__(self.module.basename(self))
def expand(self):
""" Clean up a filename by calling expandvars(),
@@ -140,15 +157,15 @@ def expand(self):
return self.expandvars().expanduser().normpath()
def _get_namebase(self):
- base, ext = os.path.splitext(
+ base, ext = self.module.splitext(
return base
def _get_ext(self):
- f, ext = os.path.splitext(self)
+ f, ext = self.module.splitext(self)
return ext
def _get_drive(self):
- drive, r = os.path.splitdrive(self)
+ drive, r = self.module.splitdrive(self)
return self.__class__(drive)
parent = property(
@@ -185,7 +202,7 @@ def _get_drive(self):
def splitpath(self):
""" p.splitpath() -> Return (p.parent, """
- parent, child = os.path.split(self)
+ parent, child = self.module.split(self)
return self.__class__(parent), child
def splitdrive(self):
@@ -195,7 +212,7 @@ def splitdrive(self):
no drive specifier, is empty, so the return value
is simply (path(''), p). This is always the case on Unix.
- drive, rel = os.path.splitdrive(self)
+ drive, rel = self.module.splitdrive(self)
return self.__class__(drive), rel
def splitext(self):
@@ -208,7 +225,7 @@ def splitext(self):
last path segment. This has the property that if
(a, b) == p.splitext(), then a + b == p.
- filename, ext = os.path.splitext(self)
+ filename, ext = self.module.splitext(self)
return self.__class__(filename), ext
def stripext(self):
@@ -219,26 +236,25 @@ def stripext(self):
return self.splitext()[0]
- if hasattr(os.path, 'splitunc'):
- def splitunc(self):
- unc, rest = os.path.splitunc(self)
- return self.__class__(unc), rest
+ def splitunc(self):
+ unc, rest = self.module.splitunc(self)
+ return self.__class__(unc), rest
- def _get_uncshare(self):
- unc, r = os.path.splitunc(self)
- return self.__class__(unc)
+ def _get_uncshare(self):
+ unc, r = self.module.splitunc(self)
+ return self.__class__(unc)
- uncshare = property(
- _get_uncshare, None, None,
- """ The UNC mount point for this path.
- This is empty for paths on local drives. """)
+ uncshare = property(
+ _get_uncshare, None, None,
+ """ The UNC mount point for this path.
+ This is empty for paths on local drives. """)
def joinpath(self, *args):
""" Join two or more path components, adding a separator
character (os.sep) if needed. Returns a new path
- return self.__class__(os.path.join(self, *args))
+ return self.__class__(self.module.join(self, *args))
def splitall(self):
r""" Return a list of the path components in this path.
@@ -283,14 +299,14 @@ def relpathto(self, dest):
# Don't normcase dest! We want to preserve the case.
dest_list = dest.splitall()
- if orig_list[0] != os.path.normcase(dest_list[0]):
+ if orig_list[0] != self.module.normcase(dest_list[0]):
# Can't get here from there.
return dest
# Find the location where the two paths start to differ.
i = 0
for start_seg, dest_seg in zip(orig_list, dest_list):
- if start_seg != os.path.normcase(dest_seg):
+ if start_seg != self.module.normcase(dest_seg):
i += 1
@@ -304,7 +320,7 @@ def relpathto(self, dest):
# If they happen to be identical, use os.curdir.
relpath = os.curdir
- relpath = os.path.join(*segments)
+ relpath = self.module.join(*segments)
return self.__class__(relpath)
# --- Listing, searching, walking, and matching
@@ -796,33 +812,31 @@ def read_hexhash(self, hash_name):
# (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
# bound. Playing it safe and wrapping them all in method calls.
- def isabs(self): return os.path.isabs(self)
- def exists(self): return os.path.exists(self)
- def isdir(self): return os.path.isdir(self)
- def isfile(self): return os.path.isfile(self)
- def islink(self): return os.path.islink(self)
- def ismount(self): return os.path.ismount(self)
+ def isabs(self): return self.module.isabs(self)
+ def exists(self): return self.module.exists(self)
+ def isdir(self): return self.module.isdir(self)
+ def isfile(self): return self.module.isfile(self)
+ def islink(self): return self.module.islink(self)
+ def ismount(self): return self.module.ismount(self)
- if hasattr(os.path, 'samefile'):
- def samefile(self): return os.path.samefile(self)
+ def samefile(self): return self.module.samefile(self)
- def getatime(self): return os.path.getatime(self)
+ def getatime(self): return self.module.getatime(self)
atime = property(
getatime, None, None,
""" Last access time of the file. """)
- def getmtime(self): return os.path.getmtime(self)
+ def getmtime(self): return self.module.getmtime(self)
mtime = property(
getmtime, None, None,
""" Last-modified time of the file. """)
- if hasattr(os.path, 'getctime'):
- def getctime(self): return os.path.getctime(self)
- ctime = property(
- getctime, None, None,
- """ Creation time of the file. """)
+ def getctime(self): return self.module.getctime(self)
+ ctime = property(
+ getctime, None, None,
+ """ Creation time of the file. """)
- def getsize(self): return os.path.getsize(self)
+ def getsize(self): return self.module.getsize(self)
size = property(
getsize, None, None,
""" Size of the file, in bytes. """)
@@ -27,6 +27,8 @@
import shutil
import tempfile
import time
+import ntpath
+import posixpath
from path import path, __version__ as path_version
@@ -127,6 +129,18 @@ def testUNC(self):
self.assert_(p.uncshare == r'\\python1\share1')
self.assert_(p.splitunc() == os.path.splitunc(str(p)))
+ def testExplicitModule(self):
+ nt_ok = path.using_module(ntpath, r'foo\bar\baz')
+ posix_ok = path.using_module(posixpath, r'foo/bar/baz')
+ posix_wrong = path.using_module(posixpath, r'foo\bar\baz')
+ self.assertEqual(nt_ok.dirname(), r'foo\bar')
+ self.assertEqual(posix_ok.dirname(), r'foo/bar')
+ self.assertEqual(posix_wrong.dirname(), '')
+ self.assertEqual(nt_ok / 'quux', r'foo\bar\baz\quux')
+ self.assertEqual(posix_ok / 'quux', r'foo/bar/baz/quux')
class TempDirTestCase(unittest.TestCase):
def setUp(self):
# Create a temporary directory.
Something went wrong with that request. Please try again.