2008-08-16 23:26:59 +08:00
|
|
|
=======
|
|
|
|
py.path
|
|
|
|
=======
|
|
|
|
|
|
|
|
The 'py' lib provides a uniform high-level api to deal with filesystems
|
|
|
|
and filesystem-like interfaces: :api:`py.path`. It aims to offer a central
|
|
|
|
object to fs-like object trees (reading from and writing to files, adding
|
|
|
|
files/directories, examining the types and structure, etc.), and out-of-the-box
|
|
|
|
provides a number of implementations of this API.
|
|
|
|
|
|
|
|
Path implementations provided by :api:`py.path`
|
|
|
|
===============================================
|
|
|
|
|
2009-04-13 21:00:00 +08:00
|
|
|
.. _`local`:
|
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
:api:`py.path.local`
|
|
|
|
--------------------
|
|
|
|
|
|
|
|
The first and most obvious of the implementations is a wrapper around a local
|
|
|
|
filesystem. It's just a bit nicer in usage than the regular Python APIs, and
|
|
|
|
of course all the functionality is bundled together rather than spread over a
|
|
|
|
number of modules.
|
|
|
|
|
|
|
|
Example usage, here we use the :api:`py.test.ensuretemp()` function to create
|
2009-04-13 20:54:58 +08:00
|
|
|
a :api:`py.path.local` object for us (which wraps a directory):
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
>>> import py
|
|
|
|
>>> temppath = py.test.ensuretemp('py.path_documentation')
|
|
|
|
>>> foopath = temppath.join('foo') # get child 'foo' (lazily)
|
|
|
|
>>> foopath.check() # check if child 'foo' exists
|
|
|
|
False
|
|
|
|
>>> foopath.write('bar') # write some data to it
|
|
|
|
>>> foopath.check()
|
|
|
|
True
|
|
|
|
>>> foopath.read()
|
|
|
|
'bar'
|
|
|
|
>>> foofile = foopath.open() # return a 'real' file object
|
|
|
|
>>> foofile.read(1)
|
|
|
|
'b'
|
|
|
|
|
|
|
|
:api:`py.path.svnurl` and :api:`py.path.svnwc`
|
|
|
|
----------------------------------------------
|
|
|
|
|
|
|
|
Two other :api:`py.path` implementations that the py lib provides wrap the
|
|
|
|
popular `Subversion`_ revision control system: the first (called 'svnurl')
|
|
|
|
by interfacing with a remote server, the second by wrapping a local checkout.
|
|
|
|
Both allow you to access relatively advanced features such as metadata and
|
|
|
|
versioning, and both in a way more user-friendly manner than existing other
|
|
|
|
solutions.
|
|
|
|
|
2009-04-13 20:54:58 +08:00
|
|
|
Some example usage of :api:`py.path.svnurl`:
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
.. >>> import py
|
2009-02-27 18:18:27 +08:00
|
|
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
2008-08-16 23:26:59 +08:00
|
|
|
>>> url = py.path.svnurl('http://codespeak.net/svn/py')
|
|
|
|
>>> info = url.info()
|
|
|
|
>>> info.kind
|
|
|
|
'dir'
|
|
|
|
>>> firstentry = url.log()[-1]
|
|
|
|
>>> import time
|
|
|
|
>>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date))
|
|
|
|
'2004-10-02'
|
|
|
|
|
2009-04-13 20:54:58 +08:00
|
|
|
Example usage of :api:`py.path.svnwc`:
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
2008-08-16 23:26:59 +08:00
|
|
|
>>> temp = py.test.ensuretemp('py.path_documentation')
|
|
|
|
>>> wc = py.path.svnwc(temp.join('svnwc'))
|
|
|
|
>>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
|
|
|
|
>>> wc.join('local.py').check()
|
|
|
|
True
|
|
|
|
|
|
|
|
.. _`Subversion`: http://subversion.tigris.org/
|
|
|
|
|
|
|
|
Common vs. specific API
|
|
|
|
=======================
|
|
|
|
|
|
|
|
All Path objects support a common set of operations, suitable
|
|
|
|
for many use cases and allowing to transparently switch the
|
|
|
|
path object within an application (e.g. from "local" to "svnwc").
|
|
|
|
The common set includes functions such as `path.read()` to read all data
|
|
|
|
from a file, `path.write()` to write data, `path.listdir()` to get a list
|
|
|
|
of directory entries, `path.check()` to check if a node exists
|
|
|
|
and is of a particular type, `path.join()` to get
|
|
|
|
to a (grand)child, `path.visit()` to recursively walk through a node's
|
|
|
|
children, etc. Only things that are not common on 'normal' filesystems (yet),
|
|
|
|
such as handling metadata (e.g. the Subversion "properties") require
|
|
|
|
using specific APIs.
|
|
|
|
|
|
|
|
Examples
|
|
|
|
---------------------------------
|
|
|
|
|
|
|
|
A quick 'cookbook' of small examples that will be useful 'in real life',
|
|
|
|
which also presents parts of the 'common' API, and shows some non-common
|
|
|
|
methods:
|
|
|
|
|
|
|
|
Searching `.txt` files
|
2009-04-02 15:10:16 +08:00
|
|
|
..........................
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
Search for a particular string inside all files with a .txt extension in a
|
|
|
|
specific directory.
|
|
|
|
|
2009-04-13 20:54:58 +08:00
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
>>> dirpath = temppath.ensure('testdir', dir=True)
|
|
|
|
>>> dirpath.join('textfile1.txt').write('foo bar baz')
|
|
|
|
>>> dirpath.join('textfile2.txt').write('frob bar spam eggs')
|
|
|
|
>>> subdir = dirpath.ensure('subdir', dir=True)
|
|
|
|
>>> subdir.join('textfile1.txt').write('foo baz')
|
|
|
|
>>> subdir.join('textfile2.txt').write('spam eggs spam foo bar spam')
|
|
|
|
>>> results = []
|
|
|
|
>>> for fpath in dirpath.visit('*.txt'):
|
|
|
|
... if 'bar' in fpath.read():
|
|
|
|
... results.append(fpath.basename)
|
|
|
|
>>> results.sort()
|
|
|
|
>>> results
|
|
|
|
['textfile1.txt', 'textfile2.txt', 'textfile2.txt']
|
|
|
|
|
|
|
|
Working with Paths
|
2009-04-02 15:10:16 +08:00
|
|
|
.......................
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
This example shows the :api:`py.path` features to deal with
|
|
|
|
filesystem paths Note that the filesystem is never touched,
|
|
|
|
all operations are performed on a string level (so the paths
|
2009-04-13 20:54:58 +08:00
|
|
|
don't have to exist, either):
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
>>> p1 = py.path.local('/foo/bar')
|
|
|
|
>>> p2 = p1.join('baz/qux')
|
|
|
|
>>> p2 == py.path.local('/foo/bar/baz/qux')
|
|
|
|
True
|
|
|
|
>>> sep = py.path.local.sep
|
|
|
|
>>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
|
|
|
|
'baz/qux'
|
2009-03-19 04:49:38 +08:00
|
|
|
>>> p2.bestrelpath(p1).replace(sep, '/')
|
2009-02-27 18:18:27 +08:00
|
|
|
'../..'
|
|
|
|
>>> p2.join(p2.bestrelpath(p1)) == p1
|
|
|
|
True
|
2008-08-16 23:26:59 +08:00
|
|
|
>>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
|
|
|
|
>>> p2 == p3
|
|
|
|
True
|
|
|
|
>>> p4 = p1 + ".py"
|
|
|
|
>>> p4.basename == "bar.py"
|
|
|
|
True
|
|
|
|
>>> p4.ext == ".py"
|
|
|
|
True
|
|
|
|
>>> p4.purebasename == "bar"
|
|
|
|
True
|
|
|
|
|
|
|
|
This should be possible on every implementation of :api:`py.path`, so
|
|
|
|
regardless of whether the implementation wraps a UNIX filesystem, a Windows
|
|
|
|
one, or a database or object tree, these functions should be available (each
|
|
|
|
with their own notion of path seperators and dealing with conversions, etc.).
|
|
|
|
|
|
|
|
Checking path types
|
2009-04-02 15:10:16 +08:00
|
|
|
.......................
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
Now we will show a bit about the powerful 'check()' method on paths, which
|
2009-04-13 20:54:58 +08:00
|
|
|
allows you to check whether a file exists, what type it is, etc.:
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
>>> file1 = temppath.join('file1')
|
|
|
|
>>> file1.check() # does it exist?
|
|
|
|
False
|
|
|
|
>>> file1 = file1.ensure(file=True) # 'touch' the file
|
|
|
|
>>> file1.check()
|
|
|
|
True
|
|
|
|
>>> file1.check(dir=True) # is it a dir?
|
|
|
|
False
|
|
|
|
>>> file1.check(file=True) # or a file?
|
|
|
|
True
|
|
|
|
>>> file1.check(ext='.txt') # check the extension
|
|
|
|
False
|
|
|
|
>>> textfile = temppath.ensure('text.txt', file=True)
|
|
|
|
>>> textfile.check(ext='.txt')
|
|
|
|
True
|
|
|
|
>>> file1.check(basename='file1') # we can use all the path's properties here
|
|
|
|
True
|
|
|
|
|
|
|
|
Setting svn-properties
|
2009-04-02 15:10:16 +08:00
|
|
|
.......................
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
As an example of 'uncommon' methods, we'll show how to read and write
|
2009-04-13 20:54:58 +08:00
|
|
|
properties in an :api:`py.path.svnwc` instance:
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
2008-08-16 23:26:59 +08:00
|
|
|
>>> wc.propget('foo')
|
|
|
|
''
|
|
|
|
>>> wc.propset('foo', 'bar')
|
|
|
|
>>> wc.propget('foo')
|
|
|
|
'bar'
|
|
|
|
>>> len(wc.status().prop_modified) # our own props
|
|
|
|
1
|
|
|
|
>>> msg = wc.revert() # roll back our changes
|
|
|
|
>>> len(wc.status().prop_modified)
|
|
|
|
0
|
|
|
|
|
|
|
|
SVN authentication
|
2009-04-02 15:10:16 +08:00
|
|
|
.......................
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
Some uncommon functionality can also be provided as extensions, such as SVN
|
2009-04-13 20:54:58 +08:00
|
|
|
authentication:
|
|
|
|
|
|
|
|
.. sourcecode:: pycon
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
2008-08-16 23:26:59 +08:00
|
|
|
>>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
|
|
|
|
... interactive=False)
|
|
|
|
>>> wc.auth = auth
|
|
|
|
>>> wc.update() # this should work
|
|
|
|
>>> path = wc.ensure('thisshouldnotexist.txt')
|
|
|
|
>>> try:
|
|
|
|
... path.commit('testing')
|
|
|
|
... except py.process.cmdexec.Error, e:
|
|
|
|
... pass
|
|
|
|
>>> 'authorization failed' in str(e)
|
|
|
|
True
|
|
|
|
|
|
|
|
Known problems / limitations
|
|
|
|
===================================
|
|
|
|
|
|
|
|
* The SVN path objects require the "svn" command line,
|
|
|
|
there is currently no support for python bindings.
|
|
|
|
Parsing the svn output can lead to problems, particularly
|
|
|
|
regarding if you have a non-english "locales" setting.
|
|
|
|
|
|
|
|
* While the path objects basically work on windows,
|
|
|
|
there is no attention yet on making unicode paths
|
|
|
|
work or deal with the famous "8.3" filename issues.
|
|
|
|
|
|
|
|
Future plans
|
|
|
|
============
|
|
|
|
|
|
|
|
The Subversion path implementations are based
|
|
|
|
on the `svn` command line, not on the bindings.
|
|
|
|
It makes sense now to directly use the bindings.
|
|
|
|
|
|
|
|
Moreover, it would be good, also considering
|
|
|
|
`py.execnet`_ distribution of programs, to
|
|
|
|
be able to manipulate Windows Paths on Linux
|
|
|
|
and vice versa. So we'd like to consider
|
|
|
|
refactoring the path implementations
|
|
|
|
to provide this choice (and getting rid
|
|
|
|
of platform-dependencies as much as possible).
|
|
|
|
|
|
|
|
There is some experimental small approach
|
|
|
|
(:source:`py/path/gateway/`) aiming at having
|
2009-08-27 01:09:52 +08:00
|
|
|
a convenient Remote Path implementation.
|
2008-08-16 23:26:59 +08:00
|
|
|
|
|
|
|
There are various hacks out there to have
|
|
|
|
Memory-Filesystems and even path objects
|
|
|
|
being directly mountable under Linux (via `fuse`).
|
|
|
|
However, the Path object implementations
|
|
|
|
do not internally have a clean abstraction
|
|
|
|
of going to the filesystem - so with some
|
|
|
|
refactoring it should become easier to
|
|
|
|
have very custom Path objects, still offering
|
|
|
|
the quite full interface without requiring
|
|
|
|
to know about all details of the full path
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
.. _`py.execnet`: execnet.html
|
|
|
|
|