pth 0.3.0
Simple and brief path traversal and filesystem access library.
License: BSD

==========================
        python-pth
==========================

.. image:: http://img.shields.io/travis/ionelmc/python-pth/master.png
    :alt: Travis-CI Build Status
    :target: https://travis-ci.org/ionelmc/python-pth

.. image:: https://ci.appveyor.com/api/projects/status/49hd684jo3y461oo/branch/master
    :alt: AppVeyor Build Status
    :target: https://ci.appveyor.com/project/ionelmc/python-pth

.. image:: http://img.shields.io/coveralls/ionelmc/python-pth/master.png
    :alt: Coverage Status
    :target: https://coveralls.io/r/ionelmc/python-pth

.. image:: http://img.shields.io/pypi/v/pth.png
    :alt: PYPI Package
    :target: https://pypi.python.org/pypi/pth

.. image:: http://img.shields.io/pypi/dm/pth.png
    :alt: PYPI Package
    :target: https://pypi.python.org/pypi/pth

**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 <https://pypi.python.org/pypi/Unipath/>`_'s
str-subclassing approach and and it has seamless zip support (like Twisted's `ZipPath
<http://twistedmatrix.com/trac/browser/trunk/twisted/python/zippath.py>`_).

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.