Skip to main content

Simple and brief path traversal and filesystem access library.

Project description

Travis-CI Build Status AppVeyor Build Status Coverage Status PYPI Package PYPI Package

Note: This is in very alpha state.

Simple and brief path traversal and filesystem access library. This library is a bit different that other path manipulation libraries:

  • Path are subclasses of strings. You can use them anyhere you would use a string.

  • Almost everything from os.path is available as a property with the same name except:

    • os.path.relpath is a method

    • os.path.getsize becomes a property named size

    • os.path.getatime becomes a property named atime

    • os.path.getctime becomes a property named ctime

    • os.path.getmtime becomes a property named mtime

    • os.path.split becomes a method name splitpath as split is already a string method

    • os.path.join becomes a method name joinpath as join is already a string method

    • os.path.commonprefix is not implemented

    • os.path.basename becomes a property named name

    • os.path.dirname becomes a property named dir

    • os.listdir becomes a property named list

    • os.walk becomes a property named tree

  • Calling a Path object calls open() on the path. Takes any argument open would take (except the filename ofcourse).

  • Transparent support for files in .zip files.

Basically it is designed for extreme brevity. It shares Unipath’s str-subclassing approach and and it has seamless zip support (like Twisted’s ZipPath).

Usage

Getting started:

>>> import pth
>>> pth  # the module is a function!
<function pth at ...>
>>> p = pth("a.txt")
>>> p
<Path 'a.txt'>
>>> p
<Path 'a.txt'>

API

>>> p = pth('tests')
>>> p
<Path 'tests'>

Joining paths:

>>> p/"a"/"b"/"c"/"d"
<Path 'tests/a/b/c/d'>

>>> p/"/root"
<Path '/root'>

Properties:

>>> p.abspath
<Path '/.../tests'>

>>> p2 = p/'b.txt'
>>> p2
<Path 'tests/b.txt'>

>>> p.exists
True

>>> p2.isfile
True

>>> p2()
<...'tests/b.txt'...mode...'r'...>

>>> pth('bogus-doesnt-exist')()
Traceback (most recent call last):
  ...
pth.PathMustBeFile: [Errno 2] No such file or directory: ...

Looping over children, including files in .zip files:

>>> for i in sorted([i for i in p.tree]): print(i)
tests/a
tests/a/a.txt
tests/b.txt
tests/test.zip
tests/test.zip/1
tests/test.zip/1/1.txt
tests/test.zip/B.TXT
tests/test.zip/a.txt

>>> for i in sorted([i for i in p.files]): print(i)
tests/b.txt

>>> for i in sorted([i for i in p.dirs]): print(i)
tests/a
tests/test.zip

>>> for i in sorted([i for i in p.list]): print(i)
tests/a
tests/b.txt
tests/test.zip

>>> list(pth('bogus-doesnt-exist').tree)
Traceback (most recent call last):
  ...
pth.PathMustBeDirectory: <Path 'bogus-doesnt-exist'> is not a directory nor a zip !

Trying to access inexisting property:

>>> p.bogus
Traceback (most recent call last):
...
AttributeError: 'Path' object has no attribute 'bogus'

Automatic wrapping of zips:

>>> p/'test.zip'
<ZipPath 'tests/test.zip' / ''>

Other properties:

>>> p.abspath
<Path '/.../tests'>

>>> p.abs
<Path '/.../tests'>

>>> p.basename
<Path 'tests'>

>>> p.abs.basename
<Path 'tests'>

>>> p.name
<Path 'tests'>

>>> p.dirname
<Path ''>

>>> p.dir
<Path ''>

>>> p.exists
True

>>> pth('~root').expanduser
<Path '/root'>

>>> pth('~/stuff').expanduser
<Path '/home/.../stuff'>

>>> p.expandvars
<Path 'tests'>

>>> type(p.atime)
<... 'float'>

>>> type(p.ctime)
<... 'float'>

>>> type(p.size)
<... 'int'>

>>> p.isabs
False

>>> p.abs.isabs
True

>>> p.isdir
True

>>> p.isfile
False

>>> p.islink
False

>>> p.ismount
False

>>> p.lexists
True

>>> p.normcase
<Path 'tests'>

>>> p.normpath
<Path 'tests'>

>>> p.realpath
<Path '/.../tests'>

>>> p.splitpath
(<Path ''>, <Path 'tests'>)

>>> pth('a/b/c/d').splitpath
(<Path 'a/b/c'>, <Path 'd'>)

>>> pth('a/b/c/d').parts
[<Path 'a'>, <Path 'b'>, <Path 'c'>, <Path 'd'>]

>>> pth('/a/b/c/d').parts
[<Path '/'>, <Path 'a'>, <Path 'b'>, <Path 'c'>, <Path 'd'>]

>>> pth(*pth('/a/b/c/d').parts)
<Path '/a/b/c/d'>

>>> p.splitdrive
('', <Path 'tests'>)

>>> p.drive
''

>>> [i for i in (p/'xxx').tree]
Traceback (most recent call last):
...
pth.PathMustBeDirectory: <Path 'tests/xxx'> is not a directory nor a zip !

>>> (p/'xxx').isfile
False

>>> (p/'xxx')()
Traceback (most recent call last):
...
pth.PathMustBeFile: ... 2...

>>> p()
Traceback (most recent call last):
...
pth.PathMustBeFile: <Path 'tests'> is not a file !

>>> pth('a.txt').splitext
(<Path 'a'>, '.txt')

>>> pth('a.txt').ext
'.txt'

Zip stuff:

>>> z = pth('tests/test.zip')
>>> z
<ZipPath 'tests/test.zip' / ''>

>>> z.abspath
<ZipPath '/.../tests/test.zip' / ''>

>>> z.abs
<ZipPath '/.../tests/test.zip' / ''>

>>> z.basename # transforms in normal path cauze zip is not accessible in current dir
<Path 'test.zip'>

>>> z.abs.basename # transforms in normal path cauze zip is not accessible in current dir
<Path 'test.zip'>

>>> import os
>>> os.chdir('tests')
>>> z.basename
<ZipPath 'test.zip' / ''>
>>> z.name
<ZipPath 'test.zip' / ''>
>>> os.chdir('..')

>>> z.dirname
<Path 'tests'>

>>> z.abs.dirname
<Path '/.../tests'>

>>> z.dir
<Path 'tests'>

>>> z.exists
True

>>> pth('~root').expanduser
<Path '/root'>

>>> pth('~/stuff').expanduser
<Path '/home/.../stuff'>

>>> z.expandvars
<ZipPath 'tests/test.zip' / ''>

>>> type(z.atime)
Traceback (most recent call last):
...
AttributeError: Not available here.

>>> type(z.ctime)
<... 'float'>

>>> type(z.size)
<... 'int'>

>>> z.isabs
False

>>> z.abs.isabs
True

>>> z.isdir
True

>>> z.isfile
False

>>> z.islink
False

>>> z.ismount
False

>>> z.lexists
Traceback (most recent call last):
...
AttributeError: Not available here.

>>> for i in z.tree: print((str(i), repr(i)))
('tests/test.zip/1',...... "<ZipPath 'tests/test.zip' / '1/'>")
('tests/test.zip/1/1.txt', "<ZipPath 'tests/test.zip' / '1/1.txt'>")
('tests/test.zip/B.TXT',..."<ZipPath 'tests/test.zip' / 'B.TXT'>")
('tests/test.zip/a.txt',..."<ZipPath 'tests/test.zip' / 'a.txt'>")

>>> for i in z.files: print((str(i), repr(i)))
('tests/test.zip/B.TXT',..."<ZipPath 'tests/test.zip' / 'B.TXT'>")
('tests/test.zip/a.txt',..."<ZipPath 'tests/test.zip' / 'a.txt'>")

>>> for i in z.dirs: print((str(i), repr(i)))
('tests/test.zip/1',...... "<ZipPath 'tests/test.zip' / '1/'>")

>>> for i in z.list: print((str(i), repr(i)))
('tests/test.zip/1',...... "<ZipPath 'tests/test.zip' / '1/'>")
('tests/test.zip/B.TXT',..."<ZipPath 'tests/test.zip' / 'B.TXT'>")
('tests/test.zip/a.txt',..."<ZipPath 'tests/test.zip' / 'a.txt'>")

>>> (z/'B.TXT')
<ZipPath 'tests/test.zip' / 'B.TXT'>

>>> str(z/'B.TXT')
'tests/test.zip/B.TXT'

>>> (z/'B.TXT').dirname
<ZipPath 'tests/test.zip' / ''>

>>> (z/'B.TXT').rel(z)
<Path 'B.TXT'>

>>> z.rel(z/'B.TXT')
<Path '..'>

>>> (z/'B.TXT').exists
True

>>> (z/'B.TXT').normcase
<ZipPath 'tests/test.zip' / 'B.TXT'>

>>> (z/'B.TXT').normpath
<ZipPath 'tests/test.zip' / 'B.TXT'>

>>> (z/'B.TXT').name
<Path 'B.TXT'>

>>> (z/'B.TXT').name
<Path 'B.TXT'>

>>> z.normcase
<ZipPath 'tests/test.zip' / ''>

>>> z.normpath
<ZipPath 'tests/test.zip' / ''>

>>> z.realpath
<ZipPath '/.../tests/test.zip' / ''>

>>> z.splitpath
(<Path 'tests'>, <Path 'test.zip'>)

>>> z.splitdrive
('', <ZipPath 'tests/test.zip' / ''>)

>>> z.drive
''

>>> pth('a.txt').splitext
(<Path 'a'>, '.txt')

>>> pth('a.txt').ext
'.txt'

Working with files in a .zip:

>>> p = z/'B.TXT'
>>> p.abspath
<ZipPath '/.../tests/test.zip' / 'B.TXT'>

>>> p.abs
<ZipPath '/.../tests/test.zip' / 'B.TXT'>

>>> p.basename
<Path 'B.TXT'>

>>> p.abs.basename
<Path 'B.TXT'>

>>> p.name
<Path 'B.TXT'>

>>> p.dirname
<ZipPath 'tests/test.zip' / ''>

>>> p.dir
<ZipPath 'tests/test.zip' / ''>

>>> p.exists
True

>>> type(p.atime)
Traceback (most recent call last):
...
AttributeError: Not available here.

>>> type(p.ctime)
<... 'float'>

>>> type(p.size)
<... 'int'>

>>> p.isabs
False

>>> p.abs.isabs
True

>>> p.isdir
False

>>> p.isfile
True

>>> p.islink
False

>>> p.ismount
False

>>> p.lexists
Traceback (most recent call last):
...
AttributeError: Not available here.

>>> p.normcase
<ZipPath 'tests/test.zip' / 'B.TXT'>

>>> p.normpath
<ZipPath 'tests/test.zip' / 'B.TXT'>

>>> p.realpath
<ZipPath '/.../tests/test.zip' / 'B.TXT'>

>>> p.splitpath
(<ZipPath 'tests/test.zip' / ''>, <Path 'B.TXT'>)

>>> pth.ZipPath.from_string('tests/test.zip/1/1.txt')
<ZipPath 'tests/test.zip' / '1/1.txt'>

>>> p.splitdrive
('', <ZipPath 'tests/test.zip' / 'B.TXT'>)

>>> p.drive
''

>>> p.splitext
(<ZipPath 'tests/test.zip' / 'B'>, '.TXT')

>>> p.ext
'.TXT'

>>> p.joinpath('tete')
<ZipPath 'tests/test.zip' / 'B.TXT/tete'>

>>> p.joinpath('tete').exists
False

>>> p.joinpath('tete').isdir
False

>>> p.joinpath('tete').isfile
False

>>> p.joinpath('tete').ctime
Traceback (most recent call last):
...
pth.PathDoesNotExist: "There is no item named 'B.TXT/tete' in the archive"

>>> p.joinpath('tete').size
Traceback (most recent call last):
...
pth.PathDoesNotExist: "There is no item named 'B.TXT/tete' in the archive"

>>> p.relpath('tests')
<Path 'test.zip/B.TXT'>

>>> p.joinpath('tete')('rb')
Traceback (most recent call last):
...
pth.PathMustBeFile: <ZipPath 'tests/test.zip' / 'B.TXT/tete'> is not a file !

>>> p('r')
<zipfile.ZipExtFile ...>

>>> [i for i in p.tree]
Traceback (most recent call last):
...
pth.PathMustBeDirectory: <ZipPath 'tests/test.zip' / 'B.TXT'> is not a directory !

>>> z('rb')
Traceback (most recent call last):
...
pth.PathMustBeFile: <ZipPath 'tests/test.zip' / ''> is not a file !

Iterating though the contents of the zip:

>>> [i for i in z.tree]
[<ZipPath 'tests/test.zip' / '1/'>, <ZipPath 'tests/test.zip' / '1/1.txt'>, <ZipPath 'tests/test.zip' / 'B.TXT'>, <ZipPath 'tests/test.zip' / 'a.txt'>]

>>> [i for i in z.files]
[<ZipPath 'tests/test.zip' / 'B.TXT'>, <ZipPath 'tests/test.zip' / 'a.txt'>]

>>> [i for i in z.dirs]
[<ZipPath 'tests/test.zip' / '1/'>]

Note that there’s this inconsistency with joining absolute paths:

>>> z/pth('/root')
<Path '/root'>

Vs:

>>> z/'/root'
<ZipPath 'tests/test.zip' / '/root'>

TODO: Make this nicer.

>>> pth.ZipPath('tests', '', '')
<Path 'tests'>

>>> pth.ZipPath.from_string('/bogus/path/to/stuff/bla/bla/bla')
<Path '/bogus/path/to/stuff/bla/bla/bla'>

>>> pth.ZipPath.from_string('bogus')
<Path 'bogus'>

>>> pth.ZipPath.from_string('tests/test.zip/bogus/path/to/stuff/bla/bla/bla')
<ZipPath 'tests/test.zip' / 'bogus/path/to/stuff/bla/bla/bla'>

>>> pth.ZipPath.from_string('tests/1/bogus/path/to/stuff/bla/bla/bla')
<Path 'tests/1/bogus/path/to/stuff/bla/bla/bla'>

>>> pth.ZipPath.from_string('tests')
<Path 'tests'>

>>> pth.ZipPath.from_string('tests/bogus')
<Path 'tests/bogus'>

And there’s a temporary path:

>>> t = pth.TempPath()
>>> t
<TempPath '/tmp/...'>

>>> with t:
...     with (t/"booo.txt")('w+') as f:
...         _ = f.write("test")
...     print([i for i in t.tree])
[<Path '/tmp/.../booo.txt'>]

>>> t.exists
False

Changelog

0.1.0 (2014-06-10)

  • First release on PyPI.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page