Initial commit
parent
069c88ee19
commit
226132dd48
@ -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](http://wtfpl.net/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), 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`.
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
some_dir = filething.Directory("/path/to/directory")
|
||||
```
|
||||
|
||||
To create a new `File` object:
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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*): ?
|
||||
|
||||
On RISCOS:
|
||||
|
||||
* **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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
Examples:
|
||||
|
||||
```python
|
||||
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`.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
some_file = filething.File("/some/file/on/my/system")
|
||||
|
||||
with some_file as f:
|
||||
print f.read()
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```python
|
||||
some_file = filething.File("/some/file/on/my/system")
|
||||
|
||||
with some_file("wb") as f:
|
||||
f.write("hi!")
|
||||
```
|
||||
|
||||
#### 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!
|
||||
|
||||
```python
|
||||
some_file = filething.File("/some/file/on/my/system")
|
||||
|
||||
f = some_file.get_file_object("wb")
|
||||
f.write("hi!")
|
||||
f.close()
|
||||
```
|
@ -0,0 +1,131 @@
|
||||
import sys, os, collections, contextlib
|
||||
|
||||
def map_attributes(obj, attr_map):
|
||||
attrs = {}
|
||||
|
||||
for original, mapped in attr_map.iteritems():
|
||||
try:
|
||||
attrs[mapped] = getattr(obj, original)
|
||||
except AttributeError, e:
|
||||
pass
|
||||
|
||||
if len(attrs) == 0:
|
||||
raise FilesystemException("No stat data received! This is probably a bug, please report it.")
|
||||
|
||||
return Attributes(attrs)
|
||||
|
||||
class FilesystemException(Exception):
|
||||
pass
|
||||
|
||||
class Attributes(object):
|
||||
def __init__(self, data = {}):
|
||||
self.data = data
|
||||
self.__setattr__ = self._setattr # To prevent the previous line from causing havoc
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return self.data[attr]
|
||||
except KeyError, e:
|
||||
raise AttributeError("No such attribute.")
|
||||
|
||||
def _setattr(self, attr, value):
|
||||
self.data[attr] = value
|
||||
|
||||
def __getitem__(self, attr):
|
||||
return self.data[attr]
|
||||
|
||||
def __setitem__(self, attr, value):
|
||||
self.data[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
|
||||
self.name = 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
|
||||
else:
|
||||
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)
|
||||
else:
|
||||
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)
|
||||
else:
|
||||
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):
|
||||
self.fileobj.close()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def __call__(self, mode = "rb"):
|
||||
obj = self.__enter__(mode)
|
||||
|
||||
try:
|
||||
yield obj
|
||||
finally:
|
||||
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)
|
Loading…
Reference in New Issue