Browse Source

Initial commit

Sven Slootweg 7 years ago
  1. 190
  2. 131


@ -0,0 +1,190 @@
# filething
Filesystem operations are one of those things in the Python standard library that just kind of suck.
`filething` is a thin light-weight wrapper library, to make filesystem operations in Python suck less. It's primarily meant for read-only stuff, and doesn't do anything to set file attributes and so on.
## License
[WTFPL]( or [CC0](, your choice.
## Platforms
Theoretically cross-platform. Then again, Windows will probably be Windows and thus it may break there. I have no idea, I don't use Windows. All code is pure Python, anyway.
## Installation
`pip install filething`
Done. You'll need `pip`, of course.
## Usage
First of all, import `filething`.
import filething
Then there's a bunch of stuff you can do. To start working with a directory or file, create a `Directory` or `File` object respectively.
To create a new `Directory` object:
some_dir = filething.Directory("/path/to/directory")
To create a new `File` object:
some_file = filething.File("/path/to/file")
Entering a non-existent (or inaccessible) path will result in a `filething.FilesystemException` being raised.
### Directory and File objects
The `Directory` and `File` classes have some things in common.
#### Attributes
The following attributes are automatically set on both `Directory` and `File` objects:
* `path`: The path of the file or directory.
* `name`: The name of the file or directory. This is generally the part after the last slash.
* `is_symlink`: Boolean. Whether the file/directory is a symlink or not.
#### Directory/File information
To learn more about a directory or file, you can use the `stat` or `symlink_stat` methods.
`stat` will give you the metadata for a file or directory, resolving a symlink if necessary. `symlink_stat` only applies to symbolic links, and gives you metadata about the symlink itself.
Trying to use `symlink_stat` on something that isn't a symlink, will raise a `filething.FilesystemException`. The `symlink_stat` function returns data in the same format as `stat`. The below applies to both.
metadata = some_file.stat()
By default, the `stat` data will be returned as a custom `Attributes` object, with more human-meaningful names than what Python provides. The below is a list of available attributes, with a description (and their original name in `os.stat` in parentheses). I'll assume that the metadata is stored in a `metadata` variable, as above.
As with Python's `os.stat`, the exact meaning and accuracy of `lastmodified`, `lastaccessed` and `ctime` differ across platforms and filesystems.
Cross-platform (more-or-less):
* **metadata.size** (*st_size*): size of file, in bytes
* **metadata.lastaccessed** (*st_atime*): time of most recent access
* **metadata.lastmodified** (*st_mtime*): time of most recent content modification
* **metadata.uid** (*st_uid*): user id of owner
* **metadata.gid** (*st_gid*): group id of owner
* **metadata.mode** (*st_mode*): protection bits
* **metadata.inode** (*st_ino*): inode number
* **metadata.device** (*st_dev*): device
* **metadata.links** (*st_nlink*): number of hard links
* **metadata.ctime** (*st_ctime*): platform dependent; time of most recent metadata change on Unix, or the time of creation on Windows
On some UNIX-like (eg. Linux):
* **metadata.blockcount** (*st_blocks*): number of 512-byte blocks allocated for file
* **metadata.blocksize** (*st_blksize*): filesystem blocksize for efficient file system I/O
* **metadata.devicetype** (*st_rdev*): type of device if an inode device
* **metadata.userflags** (*st_flags*): user defined flags for file
On some other UNIX-like (eg. FreeBSD):
* **metadata.filegen** (*st_gen*): file generation number
* **metadata.creation** (*st_birthtime*): time of file creation
On Mac OS:
* **metadata.rsize** (*st_rsize*): ?
* **metadata.creator** (*st_creator*): ?
* **metadata.type** (*st_type*): ?
* **metadata.filetype** (*st_ftype*): file type
* **metadata.attributes** (*st_attrs*): attributes
* **metadata.objecttype** (*st_objtype*): object type
You may access any of these attributes as either normal attributes, or as dictionary keys. The following are both valid:
filesize = metadata.size
filesize = metadata['size']
Optionally, you may pass `True` as a parameter to either `stat` or `symlink_stat`, to return the original data returned by `os.stat`, without changing the attribute names. This does, however, mean that dictionary key access no longer works. Example:
metadata = some_file.stat(True)
filesize = metadata.st_size # Valid
filesize = metadata['st_size'] # Won't work!
### Directory objects
There are some methods that are specific to directories, and only available on `Directory` objects.
#### Retrieving a child file/directory
You can use `get` to retrieve a `File` or `Directory` object for a child node. The type of node will automatically be detected, and either a `File` or `Directory` object will be returned as appropriate. The child doesn't have to be a direct child; it will simply join together the paths, so you can even retrieve nodes outside the path of the current `Directory`. A `FilesystemException` will be raised if the path does not exist.
child_dir = some_dir.get("assets")
deeper_child_file = some_dir.get("public/static/logo.png")
outside_dir = some_dir.get("../configuration")
#### Listing all child nodes
You may retrieve a list of `File` and `Directory` objects representing child nodes of the directory, by using `get_children`.
child_nodes = some_dir.get_children()
Alternatively, you may use `get_files` or `get_directories` to only retrieve child files and directories, respectively. All files and directories will be wrapped in `File` and `Directory` objects.
### File objects
You may use `File` objects as actual Python file objects. There are three ways to do this:
#### As a context manager in read-only mode
The easiest way. The file object will be opened in `rb` (binary reading) mode. It will be automatically closed.
some_file = filething.File("/some/file/on/my/system")
with some_file as f:
#### As a context manager in another mode
If you need to do more than just reading, you may define an explicit mode. The file will still be automatically closed.
some_file = filething.File("/some/file/on/my/system")
with some_file("wb") as f:
#### As a normal function
If context managers are not an option for some reason, you may retrieve the corresponding Python file object through a regular method. If you don't specify a mode, it will default to `rb`.
Note that when using this method, you need to manually close the file!
some_file = filething.File("/some/file/on/my/system")
f = some_file.get_file_object("wb")


@ -0,0 +1,131 @@
import sys, os, collections, contextlib
def map_attributes(obj, attr_map):
attrs = {}
for original, mapped in attr_map.iteritems():
attrs[mapped] = getattr(obj, original)
except AttributeError, e:
if len(attrs) == 0:
raise FilesystemException("No stat data received! This is probably a bug, please report it.")
return Attributes(attrs)
class FilesystemException(Exception):
class Attributes(object):
def __init__(self, data = {}): = data
self.__setattr__ = self._setattr # To prevent the previous line from causing havoc
def __getattr__(self, attr):
except KeyError, e:
raise AttributeError("No such attribute.")
def _setattr(self, attr, value):[attr] = value
def __getitem__(self, attr):
def __setitem__(self, attr, value):[attr] = value
class FilesystemObject(object):
def __init__(self, path):
if not os.path.exists(path):
raise FilesystemException("The specified path (%s) either does not exist, or you cannot access it." % path)
self.path = path = os.path.basename(path)
self.is_symlink = os.path.islink(self.path)
def _process_stat(self, data, original_names):
attr_map = {
"st_mode": "mode",
"st_ino": "inode",
"st_dev": "device",
"st_nlink": "links",
"st_uid": "uid",
"st_gid": "gid",
"st_size": "size",
"st_atime": "lastaccessed",
"st_mtime": "lastmodified",
"st_ctime": "ctime",
"st_blocks": "blockcount",
"st_blksize": "blocksize",
"st_rdev": "devicetype",
"st_flags": "userflags",
"st_gen": "filegen",
"st_birthtime": "creation",
"st_rsize": "rsize",
"st_creator": "creator",
"st_type": "type",
"st_ftype": "filetype",
"st_attrs": "attributes",
"st_objtype": "objecttype"
if original_names:
return data
return map_attributes(data, attr_map)
def stat(self, original_names = False):
return self._process_stat(os.stat(self.path), original_names)
def symlink_stat(self, original_names = False):
if self.is_symlink:
return self._process_stat(os.lstat(self.path), original_names)
raise FilesystemException("The specified path is not a symlink.")
class Directory(FilesystemObject):
def _get_items(self):
return os.listdir(self.path)
def _join(self, name):
return os.path.join(self.path, name)
def get(self, name):
child = self._join(name)
if os.path.isdir(child):
return Directory(child)
return File(child)
def get_children(self):
return [self.get(x) for x in self._get_items()]
def get_directories(self):
return [Directory(self._join(x)) for x in self._get_items() if os.path.isdir(self._join(x))]
def get_files(self):
return [File(self._join(x)) for x in self._get_items() if os.path.isfile(self._join(x))]
class File(FilesystemObject):
def __enter__(self, mode = "rb"):
self.fileobj = self.get_file_object(mode)
return self.fileobj
def __exit__(self, exc_type, exc_value, traceback):
def __call__(self, mode = "rb"):
obj = self.__enter__(mode)
yield obj
self.fileobj.close() # Can't call __exit__ here because we don't have an exception...
def get_file_object(self, mode = "rb"):
return open(self.path, mode)