A Python wrapper for LittleFS filesystem designed for embedded systems with minimal RAM and flash requirements
—
Comprehensive file I/O operations and directory management capabilities including reading, writing, seeking, directory traversal, and extended attribute support. These operations provide both high-level Python semantics and low-level control over filesystem behavior.
Raw I/O file handle that extends Python's io.RawIOBase for direct binary file operations with LittleFS-specific optimizations.
class FileHandle(io.RawIOBase):
def __init__(self, fs: LFSFilesystem, fh: LFSFile):
"""
Initialize file handle wrapper.
Parameters:
- fs: LFSFilesystem, filesystem object
- fh: LFSFile, low-level file handle
"""
def close(self) -> None:
"""
Close the file handle.
LittleFS automatically flushes on close.
"""
def readable(self) -> bool:
"""
Check if file is readable.
Returns:
bool: True if file was opened for reading
"""
def writable(self) -> bool:
"""
Check if file is writable.
Returns:
bool: True if file was opened for writing
"""
def seekable(self) -> bool:
"""
Check if file supports seeking.
Returns:
bool: Always True for LittleFS files
"""
def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
"""
Seek to position in file.
Parameters:
- offset: int, byte offset
- whence: int, seek reference (SEEK_SET, SEEK_CUR, SEEK_END)
Returns:
int: New absolute position
"""
def tell(self) -> int:
"""
Get current file position.
Returns:
int: Current byte position
"""
def truncate(self, size: int = None) -> int:
"""
Truncate file to specified size.
Parameters:
- size: int, new file size (default: current position)
Returns:
int: New file size
"""
def write(self, data: bytes) -> int:
"""
Write data to file.
Parameters:
- data: bytes, data to write
Returns:
int: Number of bytes written
"""
def readinto(self, b: bytearray) -> int:
"""
Read data into existing buffer.
Parameters:
- b: bytearray, buffer to read into
Returns:
int: Number of bytes read
"""
def readall(self) -> bytes:
"""
Read all remaining data from current position.
Returns:
bytes: All data from current position to end of file
"""
def flush(self) -> None:
"""
Flush file buffers to storage.
"""High-level file operations integrated with Python's I/O system.
# File opening with Python semantics (from LittleFS class)
def open(self, fname: str, mode="r", buffering: int = -1, encoding: str = None,
errors: str = None, newline: str = None):
"""
Open file with full Python I/O stack integration.
Mode combinations:
- 'r': read only (default)
- 'w': write only, truncate existing
- 'a': write only, append to end
- 'x': write only, fail if exists
- '+': read and write
- 'b': binary mode
- 't': text mode (default)
Returns TextIOWrapper, BufferedReader, BufferedWriter, or FileHandle
based on mode and buffering settings.
"""Directory creation, listing, and navigation operations.
# High-level directory operations (from LittleFS class)
def listdir(self, path=".") -> List[str]:
"""List directory contents as filename strings."""
def scandir(self, path="."):
"""Scan directory yielding LFSStat objects with full metadata."""
def mkdir(self, path: str) -> int:
"""Create single directory."""
def makedirs(self, name: str, exist_ok=False):
"""Create directory tree recursively."""
def rmdir(self, path: str) -> int:
"""Remove empty directory."""
def removedirs(self, name):
"""Remove directory tree upward while empty."""
def walk(self, top: str):
"""Walk directory tree yielding (root, dirs, files) tuples."""
# Low-level directory operations (from lfs module)
def dir_open(fs: LFSFilesystem, path: str) -> LFSDirectory:
"""Open directory for manual iteration."""
def dir_read(fs: LFSFilesystem, dh: LFSDirectory) -> Optional[LFSStat]:
"""Read next directory entry, None when exhausted."""
def dir_close(fs: LFSFilesystem, dh: LFSDirectory) -> int:
"""Close directory handle."""
def dir_tell(fs: LFSFilesystem, dh: LFSDirectory) -> int:
"""Get current directory iteration position."""
def dir_rewind(fs: LFSFilesystem, dh: LFSDirectory) -> int:
"""Reset directory iteration to beginning."""File and directory manipulation at the path level.
# High-level path operations (from LittleFS class)
def remove(self, path: str, recursive: bool = False) -> None:
"""
Remove file or directory with optional recursive deletion.
Parameters:
- path: str, path to remove
- recursive: bool, remove directory contents recursively
"""
def rename(self, src: str, dst: str) -> int:
"""Rename or move file/directory."""
def stat(self, path: str) -> LFSStat:
"""Get file/directory status information."""
def unlink(self, path: str) -> int:
"""Remove file (alias for remove)."""
# Low-level path operations (from lfs module)
def remove(fs: LFSFilesystem, path: str) -> int:
"""Remove file or empty directory."""
def rename(fs: LFSFilesystem, oldpath: str, newpath: str) -> int:
"""Rename or move file/directory."""
def stat(fs: LFSFilesystem, path: str) -> LFSStat:
"""Get file/directory status."""Metadata storage and retrieval for files and directories.
# High-level attribute operations (from LittleFS class)
def getattr(self, path: str, typ: Union[str, bytes, int]) -> bytes:
"""
Get extended attribute value.
Parameters:
- path: str, file or directory path
- typ: str/bytes/int, attribute type identifier (converted to 0-255 range)
Returns:
bytes: Attribute data
"""
def setattr(self, path: str, typ: Union[str, bytes, int], data: bytes) -> None:
"""
Set extended attribute value.
Parameters:
- path: str, file or directory path
- typ: str/bytes/int, attribute type identifier (converted to 0-255 range)
- data: bytes, attribute data to store
"""
def removeattr(self, path: str, typ: Union[str, bytes, int]) -> None:
"""
Remove extended attribute.
Parameters:
- path: str, file or directory path
- typ: str/bytes/int, attribute type identifier (converted to 0-255 range)
"""
# Low-level attribute operations (from lfs module)
def getattr(fs: LFSFilesystem, path: str, typ: int) -> bytes:
"""Get extended attribute (typ must be 0-255)."""
def setattr(fs: LFSFilesystem, path: str, typ: int, data: bytes) -> None:
"""Set extended attribute (typ must be 0-255)."""
def removeattr(fs: LFSFilesystem, path: str, typ: int) -> None:
"""Remove extended attribute (typ must be 0-255)."""class LFSStat(NamedTuple):
"""File or directory status information."""
type: int # File type: LFS_TYPE_REG (1) or LFS_TYPE_DIR (2)
size: int # Size in bytes (0 for directories)
name: str # Filename without path
# Type constants
TYPE_REG = 1 # Regular file
TYPE_DIR = 2 # Directoryfrom littlefs import LittleFS
fs = LittleFS(block_size=512, block_count=256)
# Write text file with automatic encoding
with fs.open('config.txt', 'w', encoding='utf-8') as f:
f.write('Configuration data\\n')
f.write('Setting: value\\n')
# Read text file with line iteration
with fs.open('config.txt', 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
print(f"Line {line_num}: {line.rstrip()}")
# Append to text file
with fs.open('config.txt', 'a', encoding='utf-8') as f:
f.write('Additional setting: new_value\\n')import struct
# Write binary data
with fs.open('data.bin', 'wb') as f:
# Write header
f.write(struct.pack('<I', 0x12345678)) # Magic number
f.write(struct.pack('<H', 100)) # Record count
# Write records
for i in range(100):
f.write(struct.pack('<If', i, i * 1.5))
# Read binary data with seeking
with fs.open('data.bin', 'rb') as f:
# Read header
magic = struct.unpack('<I', f.read(4))[0]
count = struct.unpack('<H', f.read(2))[0]
print(f"Magic: 0x{magic:08x}, Records: {count}")
# Seek to specific record
record_num = 50
f.seek(6 + record_num * 8) # Header size + record size
index, value = struct.unpack('<If', f.read(8))
print(f"Record {record_num}: index={index}, value={value}")# Create complex directory structure
fs.makedirs('project/src/main', exist_ok=True)
fs.makedirs('project/src/test', exist_ok=True)
fs.makedirs('project/docs', exist_ok=True)
# Create files in structure
files_to_create = [
'project/README.txt',
'project/src/main/app.py',
'project/src/main/utils.py',
'project/src/test/test_app.py',
'project/docs/manual.txt'
]
for filepath in files_to_create:
with fs.open(filepath, 'w') as f:
f.write(f'Content of {filepath}\\n')
# Walk entire tree
print("Complete directory tree:")
for root, dirs, files in fs.walk('project'):
level = root.count('/') - 1
indent = ' ' * level
print(f'{indent}{root}/')
sub_indent = ' ' * (level + 1)
for file in files:
stat_info = fs.stat(f"{root}/{file}")
print(f'{sub_indent}{file} ({stat_info.size} bytes)')from littlefs import lfs
# Manual directory iteration with full control
cfg = lfs.LFSConfig(block_size=512, block_count=256)
fs_obj = lfs.LFSFilesystem()
lfs.format(fs_obj, cfg)
lfs.mount(fs_obj, cfg)
# Create test files
lfs.mkdir(fs_obj, 'test_dir')
fh = lfs.file_open(fs_obj, 'test_dir/file1.txt', 'w')
lfs.file_write(fs_obj, fh, b'content1')
lfs.file_close(fs_obj, fh)
# Iterate manually
dh = lfs.dir_open(fs_obj, 'test_dir')
entries = []
while True:
entry = lfs.dir_read(fs_obj, dh)
if entry is None:
break
if entry.name not in ['.', '..']:
entries.append(entry)
lfs.dir_close(fs_obj, dh)
for entry in entries:
type_str = 'file' if entry.type == lfs.LFSStat.TYPE_REG else 'dir'
print(f"{entry.name}: {type_str}, {entry.size} bytes")# Store metadata with files
fs.mkdir('media')
# Create media file with metadata
with fs.open('media/photo.jpg', 'wb') as f:
f.write(b'\\xff\\xd8\\xff\\xe0') # JPEG header
# Store extended attributes
fs.setattr('media/photo.jpg', 'mime-type', b'image/jpeg')
fs.setattr('media/photo.jpg', 'camera', b'Canon EOS R5')
fs.setattr('media/photo.jpg', 'iso', b'800')
fs.setattr('media/photo.jpg', 'created', b'2024-01-15T10:30:00Z')
# Read metadata
mime_type = fs.getattr('media/photo.jpg', 'mime-type').decode()
camera = fs.getattr('media/photo.jpg', 'camera').decode()
iso = fs.getattr('media/photo.jpg', 'iso').decode()
created = fs.getattr('media/photo.jpg', 'created').decode()
print(f"File: media/photo.jpg")
print(f"MIME Type: {mime_type}")
print(f"Camera: {camera}")
print(f"ISO: {iso}")
print(f"Created: {created}")
# Numeric attribute types
fs.setattr('media/photo.jpg', 1, b'width=1920') # Type 1: dimension info
fs.setattr('media/photo.jpg', 2, b'height=1080') # Type 2: dimension info
width_info = fs.getattr('media/photo.jpg', 1).decode()
height_info = fs.getattr('media/photo.jpg', 2).decode()
print(f"Dimensions: {width_info}, {height_info}")# Create file with specific size
with fs.open('sparse.dat', 'wb') as f:
f.write(b'Header data')
# Truncate to create sparse file structure
f.truncate(1024) # File now 1024 bytes, rest filled with zeros
# Verify size and read sparse content
stat_info = fs.stat('sparse.dat')
print(f"File size: {stat_info.size} bytes")
with fs.open('sparse.dat', 'rb') as f:
header = f.read(11) # Read header
f.seek(-10, 2) # Seek to 10 bytes from end
tail = f.read() # Read tail (should be zeros)
print(f"Header: {header}")
print(f"Tail: {tail}") # b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'Install with Tessl CLI
npx tessl i tessl/pypi-littlefs-python