semantic path names and much more
contributed by return42
are you tired in juggling with …
parent_dir = os.path.abspath(
os.path.join(
os.path.dirname(__file__)
, os.path.pardir))
and all that bloody stuff? do you think this ..
parent_dir = FSPath(__file__).DIRNAME.ABSPATH / '..'
is much more readable .. than continue.
from PyPI
$ pip install [--user] fspath
or a bleeding edge installation from GitHub
$ pip install --user git+http://github.com/return42/fspath.git
>>> from fspath import FSPath
>>> tmp = FSPath('~/tmp')
>>> tmp
'/home/user/tmp'
>>> tmp.EXISTS
False
no additional import, no juggling with os.join(...)
simply slash /
and foo.<method>
calls
>>> [(tmp/x).makedirs() for x in ('foo', 'bar')]
True, True
>>> for n in tmp.listdir():
... print(tmp / n)
...
/home/user/tmp/foo
/home/user/tmp/bar
confused by makedirs
‘Changed in ..’?
>>> foo = tmp / 'foo'
>>> import os
>>> os.makedirs(foo) &&
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/os.py", line 241, in makedirs
mkdir(name, mode)
FileExistsError: [Errno 17] File exists:'/home/user/tmp/foo'
aargh, creates intermediate but raise if exists?!
>>> foo.makedirs()
False
FSPath behaves as expected :)
tired in meaningless foo
, foo2
and fooN
functions?
def copyfile(self, dest, preserve=False):
if preserve:
shutil.copy2(self, dest)
else:
shutil.copy(self, dest)
you think delete means delete!
def delete(self):
if self.ISDIR:
self.rmtree()
else:
os.remove(self)
just read my entire text file
readme = FSPath('README.txt').readFile()
open a path name with its associated desktop application
>>> FSPath('index.html').startFile() # opens HTML-browser showing
>>>
>>> FSPath('.').startFile() # opens file-explorer at CWD
M$-Win has nativ support in Python. On Darwin and FreeBSD the open command is used. On other OS (e.g. Linux) the xdg-open is used.
FSPath
gives us prototypes with meaningful defaults
def openTextFile(self
, mode='rt', encoding='utf-8'
, errors='strict', buffering=1
, newline=None):
and without meaningless arguments
def openBinaryFile(self
, mode='rb', errors='strict'
, buffering=None):
if you have time, compare this with open
just download and extract
>>> arch = foo / 'fspath.zip'
>>> url = 'https://github.com/return42/fspath/archive/master.zip'
.download
– super easy download + segmentation + ticker
>>> arch.download(url, chunksize=1024, ticker=True)
/home/user/tmp/foo/fspath.zip: [87.9 KB][============== ] 83%
.extract
– extract in one step, no matter ZIP or TAR
>>> arch.ISTAR, arch.ISZIP
(False, True)
>>> arch.extract(foo)
['fspath-master/', 'fspath-master/.gitignore' ...
.glob
– shell like pattern in a single folder
>>> folder = foo / 'fspath-master'
>>> g_iter = folder.glob('*.py')
>>> type(g_iter), len(list(g_iter))
(<class 'generator'>, 1)
.reMatchFind
– search files recursively by regexp
>>> rst_files = folder.reMatchFind(r'.*\.rst$')
example: change suffix of all ‘.rst’ files in your tree
>>> moved_files = [f.move(f.suffix('.txt')) for f in rst_files]
.relpath
– strip relative pathnames
>>> folder
'/home/user/tmp/foo/fspath-master'
>>> folder.relpath(tmp)
'foo/fspath-master'
>>> py_iter = folder.reMatchFind(r'.*\.py$', relpath=True)
>>> list(py_iter)
['setup.py', 'fspath/_which.py', 'fspath/win.py', ...]
.filesize
– (human) readable file size
>>> arch.filesize() # size in bytes (int)
91502
>>> arch.filesize(precision=3) # switch to human readable output
'89.357 KB'
>>> foo.filesize(precision=0) # switch to human readable output
'12 MB'
run executable without any rocket since
>>> proc = FSPath('arp').Popen('-a',)
>>> stdout, stderr = proc.communicate()
>>> retVal = proc.returncode
callEXE
– synchronous call and capture all in one
>>> from fspath import callEXE
>>> out, err, rc = callEXE('arp', '-a', '192.168.1.120')
>>> print("out:'%s...' | err='%s' | exit code=%d"
% (out[:24], err, rc))
out:'storage (192.168.1.120) ...' | err='' | exit code=0
>>> callEXE('arp', '-a', 'xyz')
('', 'xyz: Unknown host\n', 255)
.chdir
– change current working dir to self
.walk
– generate filenames of tree (see os.walk)
.delete
– delete! .. no matter if file or folder
.rmtree
– remove entire folder
.rmfile
– remove single file
.copyfile
– copy file (opt. with permission bits)
.copytree
– recursively copy the entire tree
.filesize
– Filesize in bytes or with precision
.suffix
– return path name with new suffix
To be complete with path names.
>>> FSPath.getHOME()
'/home/user'
>>> FSPath.getCWD()
'/share/fspath/local'
FSPath.OS
– shortcut to common OS properties
>>> pprint(FSPath.OS)
{'altsep' : None , 'curdir' : '.' ,
'extsep' : '.' , 'linesep' : '\n' ,
'pathsep' : ':' , 'sep' : '/' ,
'devnull' : '/dev/null', 'defpath' : ':/bin:/usr/bin'
}
>>> filename = FSPath('../path/to/folder/filename.ext')
dot is a part of the suffix
>>> filename.SUFFIX
'.ext'
change suffix in place
>>> filename.suffix('.rst')
'../path/to/folder/filename.rst'
or even throw it away
>>> filename.SKIPSUFFIX
'../path/to/folder/filename'
>>> filename.DIRNAME
'../path/to/folder'
>>> filename.BASENAME
'filename.ext'
>>> filename.FILENAME
'filename'
>>> filename.ABSPATH
'/share/fspath/local/path/to/folder/filename.ext'
>>> filename.REALPATH
'/share/fspath/path/to/folder/filename.ext'
>>> filename.NTPATH
'..\\path\\to\\folder\\filename.ext'
>>> filename.POSIXPATH
'../path/to/folder/filename.ext'
known from shell
>>> home = FSPath("$HOME")
>>> home
'$HOME'
>>> home.EXPANDVARS
'/home/user'
>>> home = FSPath("~/tmp")
>>> home.EXPANDUSERS
'/home/user'
.EXISTS
– True if file/path name exist
.SIZE
– Size in bytes
.READABLE
– True if file/path is readable
.WRITEABLE
– True if file/path is writeable
.EXECUTABLE
– True if file is executable
.ISDIR
– True if path is a folder
.ISFILE
– True if path is a file
.ISABSPATH
– True if path is absolute
.ISLINK
– True if path is a symbolic link
.ISMOUNT
– True if path is a mountpoint
.MTIME
– last modification time
.ATIME
– last access time
.CTIME
– last change time
.ISZIP
– True if path is a ZIP file
.ISTAR
– True if path is a TAR archive file
inheritance of unicode
in Py2 and str
in Py3
class FSPath(six.text_type):
...
constructor normalize without asking
>>> FSPath('goes/up/../and/../down')
'goes/down'
works with anyone who accept strings
>>> os.stat(FSPath.getHOME())
os.stat_result(st_mode=16877, st_ino=1966082, ...
FSPath
member call returns FSPath instances
>>> type(folder.splitpath()[-1])
<class 'fspath.fspath.FSPath'>
call of inherited string member returns string types
>>> type(folder.split(home.OS.sep)[-1])
<class 'str'>
environment variables are attributes
>>> from fspath import OS_ENV
>>> OS_ENV.SHELL
'/bin/bash'
you can get or set
>>> OS_ENV.TMP = '/tmp/xyz'
>>> FSPath('$TMP').EXPANDVARS
'/tmp/xyz'
unknown environment request raises KeyError
>>> OS_ENV.XYZ
Traceback (most recent call last):
...
KeyError: 'XYZ'
use .get
to avoid exceptions
>>> OS_ENV.get('XYZ', 'not defined')
'not defined'
>>> OS_ENV.get('XYZ')
>>>
# -*- coding: utf-8; mode: python -*-
# file: foobar/main.py
"""foobar CLI"""
import sys
from fspath import CLI
def main():
cli = CLI(description=__doc__)
# define CLI
...
# run CLI
cli()
in projects setup.py
add entry point for main()
setup(name = "foobar"
...
, entry_points = {
'console_scripts': [
'foobar = foobar.main:main' ]}
...
)
implement a cli
wrapper for each subcommand
def cli_hello(cliArgs):
"""another 'hello world'"""
print('hello world')
cliArgs.folder
we will be of type FSPath
def cli_listdir(cliArgs):
"""list directory"""
for f in cliArgs.folder.listdir():
l = f.upper() if cliArgs.upper else f
f = cliArgs.folder / f
if cliArgs.verbose:
l = '[%10s] ' % (f.filesize(precision=0)) + l
print(l, end = ('\n' if cliArgs.verbose else ', '))
CLI is an argparse implementation.
def main():
...
# define CLI
hello = cli.addCMDParser(cli_hello, cmdName='hello')
listdir = cli.addCMDParser(cli_listdir, cmdName='dir')
listdir.add_argument("folder", type = FSPath
, nargs = "?", default = FSPath(".")
, help = "path of the folder")
listdir.add_argument("--upper", action = 'store_true'
, help = "convert to upper letters")
using type=FSPath
for file and path name arguments gives us the power of
FSPath
(see cli_listdir(...)
)
the over all help
$ foobar --help
usage: foobar [-h] [--debug] [--verbose] [--quiet] \
{hello, dir} ...
optional arguments:
-h, --help show this help message and exit
--debug run in debug mode (default: False)
--verbose run in verbose mode (default: False)
--quiet run in quiet mode (default: False)
commands:
{hello,dir}
hello another 'hello world'
dir list directory
the subcommand --help
$ foobar dir --help
usage: foobar dir [-h] [--upper] [folder]
positional arguments:
folder path of the folder (default: .)
optional arguments:
-h, --help show this help message and exit
--upper convert to upper letters (default: False)
list directory
run subcommand dir
$ foobar dir /
initrd.img.old, initrd.img, var, vmlinuz, home ...
and with global option verbose
$ foobar.py --verbose dir /
[ 40 MB] initrd.img.old
[ 40 MB] initrd.img
[ 4 KB] var
[ 7 MB] vmlinuz
[ 4 KB] home
...
As long as every new release is fully downward compatible a serial versioning is enough.
Version numbers follow scheme
YYYYMMDD
there is much more to show .. in the meantime take a look at the
This slide show was build with the help of ..