nobodd.path

Defines the FatPath class, a Path-like class for interacting with directories and sub-directories in a FatFileSystem instance. You should never need to construct this class directly; instead it should be derived from the root attribute which is itself a FatPath instance.

>>> from nobodd.disk import DiskImage
>>> from nobodd.fs import FatFileSystem
>>> img = DiskImage('test.img')
>>> fs = FatFileSystem(img.partitions[1].data)
>>> for p in fs.root.iterdir():
...     print(repr(p))
...
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/foo')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/bar.txt')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/setup.cfg')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/baz')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/adir')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/BDIR')

FatPath

class nobodd.path.FatPath(fs, *pathsegments)[source]

A Path-like object representing a filepath within an associated FatFileSystem.

There is rarely a need to construct this class directly. Instead, instances should be obtained via the root property of a FatFileSystem. If constructed directly, fs is a FatFileSystem instance, and pathsegments is the sequence of strings to be joined with a path separator into the path.

Instances provide almost all the facilities of the pathlib.Path class they are modeled after, including the crucial open() method, iterdir(), glob(), and rglob() for enumerating directories, stat(), is_dir(), and is_file() for querying information about files, division for construction of new paths, and all the usual name, parent, stem, and suffix attributes. When the FatFileSystem is writable, then unlink(), touch(), mkdir(), rmdir(), and rename() may also be used.

Instances are also comparable for the purposes of sorting, but only within the same FatFileSystem instance (comparisons across file-system instances raise TypeError).

exists()[source]

Whether the path points to an existing file or directory:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> (fs.root / 'foo').exists()
True
>>> (fs.root / 'fooo').exists()
False
glob(pattern)[source]

Glob the given relative pattern in the directory represented by this path, yielding matching files (of any kind):

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> sorted((fs.root / 'nobodd').glob('*.py'))
[FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/__init__.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/disk.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/fat.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/fs.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/gpt.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/main.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/mbr.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/tftp.py'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/tools.py')]

Patterns are the same as for fnmatch(), with the addition of “**” which means “this directory and all subdirectories, recursively”. In other words, it enables recurisve globbing.

Warning

Using the “**” pattern in large directory trees may consume an inordinate amount of time.

is_absolute()[source]

Return whether the path is absolute or not. A path is considered absolute if it has a “/” prefix.

is_dir()[source]

Return a bool indicating whether the path points to a directory. False is also returned if the path doesn’t exist.

is_file()[source]

Returns a bool indicating whether the path points to a regular file. False is also returned if the path doesn’t exist.

is_mount()[source]

Returns a bool indicating whether the path is a mount point. In this implementation, this is only True for the root path.

is_relative_to(*other)[source]

Return whether or not this path is relative to the other path.

iterdir()[source]

When the path points to a directory, yield path objects of the directory contents:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> for child in fs.root.iterdir(): child
...
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/foo')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/bar.txt')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/setup.cfg')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/baz')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/adir')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/BDIR')

The children are yielded in arbitrary order (the order they are found in the file-system), and the special entries '.' and '..' are not included.

joinpath(*other)[source]

Calling this method is equivalent to combining the path with each of the other arguments in turn:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> fs.root
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/')
>>> fs.root.joinpath('nobodd')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd')
>>> fs.root.joinpath('nobodd', 'main.py')
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/main.py')
match(pattern)[source]

Match this path against the provided glob-style pattern. Returns a bool indicating if the match is successful.

If pattern is relative, the path may be either relative or absolute, and matching is done from the right:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> f = fs / 'nobodd' / 'mbr.py'
>>> f
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd/mbr.py')
>>> f.match('*.py')
True
>>> f.match('nobodd/*.py')
True
>>> f.match('/*.py')
False

As FAT file-systems are case-insensitive, all matches are likewise case-insensitive.

mkdir(mode=511, parents=False, exist_ok=False)[source]

Create a new directory at this given path. The mode parameter exists only for compatibility with pathlib.Path and is otherwise ignored. If the path already exists, FileExistsError is raised.

If parents is true, any missing parents of this path are created as needed.

If parents is false (the default), a missing parent raises FileNotFoundError.

If exist_ok is false (the default), FileExistsError is raised if the target directory already exists.

If exist_ok is true, FileExistsError exceptions will be ignored (same behavior as the POSIX mkdir -p command), but only if the last path component is not an existing non-directory file.

open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)[source]

Open the file pointed to by the path, like the built-in open() function does. The mode, buffering, encoding, errors and newline options are as for the open() function. If successful, a FatFile instance is returned.

Note

This implementation is read-only, so any modes other than “r” and “rb” will fail with PermissionError.

read_bytes()[source]

Return the binary contents of the pointed-to file as a bytes object:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> (fs.root / 'foo').read_text()
b'foo\n'
read_text(encoding=None, errors=None)[source]

Return the decoded contents of the pointed-to file as a string:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> (fs.root / 'foo').read_text()
'foo\n'
relative_to(*other)[source]

Compute a version of this path relative to the path represented by other. If it’s impossible, ValueError is raised.

rename(target)[source]

Rename this file or directory to the given target, and return a new FatPath instance pointing to target. If target exists and is a file, it will be replaced silently. target can be either a string or another path object:

>>> p = fs.root / 'foo'
>>> p.open('w').write('some text')
9
>>> target = fs.root / 'bar'
>>> p.rename(target)
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/bar')
>>> target.read_text()
'some text'

The target path must be absolute. There are no guarantees of atomic behaviour (in contrast to os.rename()).

Note

pathlib.Path.rename() permits relative paths, but interprets them relative to the working directory which is a concept FatPath does not support.

resolve(strict=False)[source]

Make the path absolute, resolving any symlinks. A new FatPath object is returned.

".." components are also eliminated (this is the only method to do so). If the path doesn’t exist and strict is True, FileNotFoundError is raised. If strict is False, the path is resolved as far as possible and any remainder is appended without checking whether it exists.

Note that as there is no concept of the “current” directory within FatFileSystem, relative paths cannot be resolved by this function, only absolute.

rglob(pattern)[source]

This is like calling glob() with a prefix of “**/” to the specified pattern.

rmdir()[source]

Remove this directory. The directory must be empty.

stat(*, follow_symlinks=True)[source]

Return a os.stat_result object containing information about this path:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.stat().st_size
388
>>> p.stat().st_ctime
1696606672.02

Note

In a FAT file-system, atime has day resolution, mtime has 2-second resolution, and ctime has either 2-second or millisecond resolution depending on the driver that created it. Directories have no timestamp information.

The follow_symlinks parameter is included purely for compatibility with pathlib.Path.stat(); it is ignored as symlinks are not supported.

touch(mode=438, exist_ok=True)[source]

Create a file at this given path. The mode parameter is only present for compatibility with pathlib.Path and is otherwise ignored. If the file already exists, the function succeeds if exist_ok is True (and its modification time is updated to the current time), otherwise FileExistsError is raised.

Remove this file. If the path points to a directory, use rmdir() instead.

If missing_ok is False (the default), FileNotFoundError is raised if the path does not exist. If missing_ok is True, FileNotFoundError exceptions will be ignored (same behaviour as the POSIX rm -f command).

with_name(name)[source]

Return a new path with the name changed. If the original path doesn’t have a name, ValueError is raised.

with_stem(stem)[source]

Return a new path with the stem changed. If the original path doesn’t have a name, ValueError is raised.

with_suffix(suffix)[source]

Return a new path with the suffix changed. If the original path doesn’t have a suffix, the new suffix is appended instead. If the suffix is an empty string, the original suffix is removed.

write_bytes(data)[source]

Open the file pointed to in bytes mode, write data to it, and close the file:

>>> p = fs.root / 'my_binary_file'
>>> p.write_bytes(b'Binary file contents')
20
>>> p.read_bytes()
b'Binary file contents'

An existing file of the same name is overwritten.

write_text(data, encoding=None, errors=None, newline=None)[source]

Open the file pointed to in text mode, write data to it, and close the file:

>>> p = fs.root / 'my_text_file'
>>> p.write_text('Text file contents')
18
>>> p.read_text()
'Text file contents'

An existing file of the same name is overwritten. The optional parameters have the same meaning as in open().

property anchor

Returns the concatenation of the drive and root. This is always “/”.

property fs

Returns the FatFileSystem instance that this instance was constructed with.

property name

A string representing the final path component, excluding the root:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.name
'main.py'
property parent

The logical parent of the path:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.parent
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd')

You cannot go past an anchor:

>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.parent.parent.parent
FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/')
property parents

An immutable sequence providing access to the logical ancestors of the path:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.parents
(FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/nobodd'),
 FatPath(<FatFileSystem label='TEST' fat_type='fat16'>, '/'))
property parts

A tuple giving access to the path’s various components:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.parts
['/', 'nobodd', 'main.py']
property root

Returns a string representing the root. This is always “/”.

property stem

The final path component, without its suffix:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.stem
'main'
property suffix

The file extension of the final component, if any:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd' / 'main.py')
>>> p.suffix
'.py'
property suffixes

A list of the path’s file extensions:

>>> fs
<FatFileSystem label='TEST' fat_type='fat16'>
>>> p = (fs.root / 'nobodd.tar.gz')
>>> p.suffixes
['.tar', '.gz']

Internal Functions

nobodd.path.get_cluster(entry, fat_type)[source]

Given entry, a DirectoryEntry, and the fat_type indicating the size of FAT clusters, return the first cluster of the file associated with the directory entry.