dmemfs.\_fs
March 22, 2026 · View on GitHub
dmemfs._fs
MFSNodeLimitExceededError
MFSQuotaExceededError
MemoryFileHandle
MFSStatResult
MFSStats
MemoryFileSystem Objects
class MemoryFileSystem()
In-process volatile virtual filesystem with hard quota enforcement.
Provides a POSIX-style hierarchical filesystem entirely within a Python process. All data is stored in RAM; the filesystem ceases to exist when the process exits.
Key properties
- Hard quota: every write is rejected before it occurs if it would
exceed
max_quota. The process is never killed by OOM due to MFS writes. - Zero external dependencies: uses only the Python 3.11+ standard library.
- Thread-safe: concurrent reads are permitted; writes are exclusive.
Lock acquisition order is always
_global_lock→FileNode._rw_lockto prevent deadlocks. - Binary-only modes:
open()acceptsrb,wb,ab,r+b, andxbonly. Text mode raisesValueError.
Parameters
max_quota : int
Maximum total memory budget in bytes that MFS will allocate for file
data. Default is 256 MiB. Writes that would exceed this limit raise
:exc:MFSQuotaExceededError before any data is written.
chunk_overhead_override : int | None
Override the per-chunk overhead estimate used for quota accounting in
:class:SequentialMemoryFile. None uses the compiled-in default
(128 bytes). Increase if quota is unexpectedly exceeded on your
platform.
promotion_hard_limit : int | None
Maximum file size in bytes at which a SequentialMemoryFile may be
automatically promoted to RandomAccessMemoryFile. None uses
the default of 512 MiB. Set to 0 to disable promotion entirely
when combined with default_storage="sequential".
max_nodes : int | None
Maximum total number of nodes (files + directories) the filesystem
may contain. None (default) means unlimited. Exceeding this
limit raises :exc:MFSNodeLimitExceededError.
default_storage : str
Storage backend used when creating new files. One of:
* ``"auto"`` *(default)* — starts as ``SequentialMemoryFile`` and
promotes to ``RandomAccessMemoryFile`` on the first non-append
write.
* ``"sequential"`` — always ``SequentialMemoryFile``; promotion is
disabled and random-access writes raise ``io.UnsupportedOperation``.
* ``"random_access"`` — always ``RandomAccessMemoryFile``.
default_lock_timeout : float | None
Default timeout in seconds applied to every open() call that does
not provide an explicit lock_timeout. None means block
indefinitely (no timeout). Default is 30.0.
memory_guard : str
Physical-memory guard strategy. One of:
* ``"none"`` *(default)* — no physical-memory checks.
* ``"init"`` — check available RAM once at construction time.
* ``"per_write"`` — re-check available RAM before every write
(rate-limited by ``memory_guard_interval``).
memory_guard_action : str
Action taken when the guard detects low physical memory. One of
"warn" (default, emits ResourceWarning ) or "raise"
(raises MemoryError ).
memory_guard_interval : float
Minimum seconds between physical-memory checks for "per_write"
mode. Default is 1.0.
Raises
ValueError
If default_storage is not one of the accepted values.
Examples
Basic usage::
from dmemfs import MemoryFileSystem
mfs = MemoryFileSystem(max_quota=64 * 1024 * 1024) # 64 MiB
mfs.mkdir("/work")
with mfs.open("/work/data.bin", "wb") as f:
f.write(b"hello")
with mfs.open("/work/data.bin", "rb") as f:
assert f.read() == b"hello"
__init__
def __init__(max_quota: int = 256 * 1024 * 1024,
chunk_overhead_override: int | None = None,
promotion_hard_limit: int | None = None,
max_nodes: int | None = None,
default_storage: str = "auto",
default_lock_timeout: float | None = 30.0,
memory_guard: str = "none",
memory_guard_action: str = "warn",
memory_guard_interval: float = 1.0) -> None
open
def open(path: str,
mode: str = "rb",
preallocate: int = 0,
lock_timeout: float | None = None) -> MemoryFileHandle
Open a file and return a :class:MemoryFileHandle.
Parameters
path : str Absolute path to the file inside MFS. mode : str Binary open mode. Accepted values:
* ``"rb"`` — read-only; raises :exc:`FileNotFoundError` if absent.
Acquires a **shared** read lock (multiple concurrent readers allowed).
* ``"wb"`` — write-only; creates the file if absent, truncates to
zero if it exists. Acquires an **exclusive** write lock.
* ``"ab"`` — append; creates if absent. Every ``write()`` call
automatically moves the cursor to EOF before writing.
Acquires an exclusive write lock.
* ``"r+b"`` — read/write; raises :exc:`FileNotFoundError` if absent.
Does **not** truncate. Acquires an exclusive write lock.
* ``"xb"`` — exclusive create; raises :exc:`FileExistsError` if the
file already exists. Acquires an exclusive write lock.
Text modes (``"r"``, ``"w"``, etc.) are not supported and raise
:exc:`ValueError`. Use :class:`MFSTextHandle` for text I/O.
preallocate : int
If positive, reserve this many bytes of quota immediately and
fill the file with null bytes up to that size. Subsequent writes
within the pre-allocated region do not consume additional quota.
Default is 0 (no pre-allocation).
lock_timeout : float | None
Seconds to wait for the file lock. Overrides
default_lock_timeout for this call only. None inherits the
instance default (30.0 by default). 0.0 is non-blocking.
Returns
MemoryFileHandle
An open file handle. Must be closed via with statement or
an explicit close() call to release the lock.
Raises
ValueError
mode is not one of the five accepted binary modes.
FileNotFoundError
"rb" or "r+b" and path does not exist; or the parent
directory does not exist when creating a new file.
FileExistsError
"xb" and path already exists.
IsADirectoryError
path refers to a directory.
BlockingIOError
The file lock could not be acquired within lock_timeout seconds.
MFSQuotaExceededError
preallocate would exceed the quota.
Notes
The lock is held for the entire lifetime of the handle (from
open() to close()). In multi-threaded code keep handle scopes
short and always use the with statement.
mkdir
def mkdir(path: str, exist_ok: bool = False) -> None
Create a directory, including all missing intermediate directories.
Behaves like :func:os.makedirs with parents=True: every missing
ancestor is created automatically. This differs from :func:os.mkdir,
which raises :exc:FileNotFoundError when a parent is absent.
Parameters
path : str
Absolute path of the directory to create.
exist_ok : bool
If True, do nothing when path already exists as a directory.
If False (default), raise :exc:FileExistsError in that case.
Raises
FileExistsError
path already exists and exist_ok is False, or path
already exists as a file (regardless of exist_ok).
rename
def rename(src: str, dst: str) -> None
Rename or move a file or directory.
Updates the parent node's child mapping; no data is copied. Both files and directories complete in O(depth) time.
Parameters
src : str Existing path. dst : str New path. The parent directory of dst must already exist.
Raises
FileNotFoundError
src does not exist, or the parent directory of dst does not
exist.
FileExistsError
dst already exists.
BlockingIOError
src (or any file beneath it if src is a directory) is
currently open.
ValueError
src is the root directory "/".
Notes
Unlike :meth:move, this method does not create missing
intermediate directories for dst. Use :meth:move when automatic
parent creation is desired.
Timestamps are not updated (metadata-only operation, consistent with
POSIX rename(2) semantics for mtime).
move
def move(src: str, dst: str) -> None
Move a file or directory, creating intermediate directories as needed.
Identical to :meth:rename except that missing parent directories of
dst are created automatically.
Parameters
src : str Existing path. dst : str Destination path. Missing parent directories are created.
Raises
FileNotFoundError
src does not exist.
FileExistsError
dst already exists.
BlockingIOError
src (or any file beneath it) is currently open.
ValueError
src is the root directory "/".
remove
def remove(path: str) -> None
Delete a single file.
Parameters
path : str Absolute path to the file to delete.
Raises
FileNotFoundError
path does not exist.
IsADirectoryError
path is a directory. Use :meth:rmtree for directories.
BlockingIOError
The file is currently open.
rmtree
def rmtree(path: str) -> None
Recursively delete a directory and all its contents.
All quota consumed by files in the subtree is released atomically.
Parameters
path : str Absolute path to the directory to delete.
Raises
FileNotFoundError
path does not exist.
NotADirectoryError
path is a file. Use :meth:remove for files.
BlockingIOError
Any file within the subtree is currently open.
ValueError
path is the root directory "/".
listdir
def listdir(path: str) -> list[str]
Return the names of entries directly inside path.
Returns bare names only (not full paths), matching the behaviour of
:func:os.listdir.
Parameters
path : str Absolute path to a directory.
Returns
list[str] Entry names (files and subdirectories) in unspecified order.
Raises
FileNotFoundError path does not exist. NotADirectoryError path is a file.
Examples
::
mfs.mkdir("/a/b")
with mfs.open("/a/b/f.txt", "wb") as f:
f.write(b"x")
mfs.listdir("/a/b") # → ["f.txt"]
exists
def exists(path: str) -> bool
Return True if path exists (file or directory).
Never raises; returns False for invalid paths.
Parameters
path : str Absolute path to check.
Returns
bool
is_dir
def is_dir(path: str) -> bool
Return True if path exists and is a directory.
Never raises; returns False for invalid paths or non-directories.
Parameters
path : str Absolute path to check.
Returns
bool
is_file
def is_file(path: str) -> bool
Return True if path exists and is a regular file.
Never raises; returns False for invalid paths or directories.
Parameters
path : str Absolute path to check.
Returns
bool
stat
def stat(path: str) -> MFSStatResult
Return metadata for the file or directory at path.
Parameters
path : str Absolute path to a file or directory.
Returns
MFSStatResult
A :class:~dmemfs.MFSStatResult TypedDict with the following
fields:
* ``size`` (*int*) — file size in bytes; ``0`` for directories.
* ``created_at`` (*float*) — creation timestamp
(:func:`time.time` format).
* ``modified_at`` (*float*) — last-modification timestamp.
* ``generation`` (*int*) — monotonically increasing change
counter; ``0`` means the file has not been written since
creation or last ``import_tree``.
* ``is_dir`` (*bool*) — ``True`` for directories.
Raises
FileNotFoundError path does not exist.
stats
def stats() -> MFSStats
Return an aggregate usage snapshot of the entire filesystem.
Returns
MFSStats
A :class:~dmemfs.MFSStats TypedDict with the following fields:
* ``used_bytes`` (*int*) — quota bytes currently consumed.
* ``quota_bytes`` (*int*) — configured maximum quota.
* ``free_bytes`` (*int*) — remaining quota
(``quota_bytes - used_bytes``).
* ``file_count`` (*int*) — total number of files.
* ``dir_count`` (*int*) — total number of directories.
* ``chunk_count`` (*int*) — total chunks held by
``SequentialMemoryFile`` instances (promoted files are not
counted).
* ``overhead_per_chunk_estimate`` (*int*) — per-chunk overhead
used for quota accounting.
get_size
def get_size(path: str) -> int
Return the size of a file in bytes.
Parameters
path : str Absolute path to a file.
Returns
int File size in bytes.
Raises
FileNotFoundError path does not exist. IsADirectoryError path is a directory.
export_as_bytesio
def export_as_bytesio(path: str, max_size: int | None = None) -> io.BytesIO
Export a file's contents as a :class:io.BytesIO object.
The returned object is a deep copy of the file data at the moment
of the call. Subsequent writes to the MFS file do not affect the
returned BytesIO.
Parameters
path : str
Absolute path to the file to export.
max_size : int | None
If given, raise :exc:ValueError before allocating memory when
the file exceeds this size. Use to guard against unexpectedly
large exports consuming heap memory outside quota control.
Returns
io.BytesIO
A new BytesIO positioned at offset 0.
Raises
FileNotFoundError path does not exist. IsADirectoryError path is a directory. ValueError The file size exceeds max_size.
Notes
The returned BytesIO is outside quota management. Large files
may cause significant process-memory growth beyond max_quota.
export_tree
def export_tree(prefix: str = "/",
only_dirty: bool = False) -> dict[str, bytes]
Export all files under prefix as a dict.
Convenience wrapper around :meth:iter_export_tree that materialises
the entire result into memory at once. For large filesystems prefer
:meth:iter_export_tree to avoid peak-memory doubling.
Parameters
prefix : str
Only files whose path starts with prefix are exported.
Default is "/" (all files).
only_dirty : bool
If True, only include files whose generation counter is
greater than zero (modified since creation or last
import_tree).
Returns
dict[str, bytes] Mapping of absolute path → file bytes.
iter_export_tree
def iter_export_tree(prefix: str = "/",
only_dirty: bool = False) -> Iterator[tuple[str, bytes]]
Lazily yield (path, data) pairs for all files under prefix.
The set of paths is snapshotted at the start of iteration under the global lock. Individual file data is read under per-file read locks. Files deleted between snapshot time and read time are silently skipped (weak consistency).
Parameters
prefix : str
Path prefix filter. Default "/" exports all files.
only_dirty : bool
If True, skip files with generation == 0.
Yields
tuple[str, bytes]
(absolute_path, file_bytes) for each matched file.
import_tree
def import_tree(tree: dict[str, bytes]) -> None
Import a dict of files into MFS atomically (All-or-Nothing).
If any error occurs during import (quota exceeded, memory allocation failure, etc.) the entire operation is rolled back and MFS is left in its pre-call state.
Parameters
tree : dict[str, bytes] Mapping of absolute path → file bytes. Missing parent directories are created automatically. Existing files at the same paths are replaced.
Raises
BlockingIOError A target path has an open handle. MFSQuotaExceededError The net quota increase would exceed the available quota. MFS is rolled back. MemoryError The OS could not allocate memory for one of the files. MFS is rolled back.
Notes
generation is reset to 0 for all imported files (they are
treated as a clean baseline).
copy
def copy(src: str, dst: str) -> None
Copy a single file (deep copy of byte content).
The destination file is a completely independent node with new
timestamps. Equivalent to POSIX cp src dst.
Parameters
src : str Source file path. dst : str Destination file path. Must not already exist. The parent directory must exist.
Raises
FileNotFoundError src does not exist, or the parent directory of dst does not exist. FileExistsError dst already exists. IsADirectoryError src is a directory. MFSQuotaExceededError Insufficient quota to store the copied data.
copy_tree
def copy_tree(src: str, dst: str) -> None
Deep-copy an entire directory subtree.
All files are copied as independent byte buffers; no reference sharing (copy-on-write) is performed. Quota for the entire subtree is pre-checked before any node is created; the operation is all-or-nothing with respect to quota.
Parameters
src : str Source directory path. dst : str Destination directory path. Must not already exist. The parent directory must exist.
Raises
FileNotFoundError src does not exist, or the parent of dst does not exist. NotADirectoryError src is not a directory. FileExistsError dst already exists. MFSQuotaExceededError Combined size of all files in the subtree exceeds available quota.
walk
def walk(path: str = "/") -> Iterator[tuple[str, list[str], list[str]]]
Recursively walk the directory tree top-down.
Mirrors the behaviour of :func:os.walk for top-down traversal.
Parameters
path : str
Root of the walk. Default "/" walks the entire filesystem.
Yields
tuple[str, list[str], list[str]]
(dirpath, dirnames, filenames) for each directory visited,
where dirnames are the names of immediate subdirectories and
filenames are the names of immediate files.
Raises
FileNotFoundError path does not exist. NotADirectoryError path is a file.
Warnings
Weak consistency: the global lock is not held between
yield statements. Concurrent structural changes may cause
inconsistencies (e.g. a yielded subdirectory name may no longer
exist by the time it is visited). Deleted entries are skipped
silently; no exception is raised. For a fully consistent snapshot
use :meth:export_tree.
glob
def glob(pattern: str) -> list[str]
Return a sorted list of paths matching pattern.
Parameters
pattern : str Glob pattern with POSIX-style separators. Supported wildcards:
* ``*`` — matches any sequence of characters within a single
path component (does **not** match ``/``).
* ``**`` — matches zero or more path components (recursive).
* ``?`` — matches exactly one character (not ``/``).
* ``[seq]``, ``[!seq]`` — character classes.
Returns
list[str] Sorted list of absolute paths that match pattern. May be empty.
Examples
::
mfs.glob("/src/**/*.py") # all .py files under /src
mfs.glob("/src/*.py") # .py files directly under /src only
Notes
Applies weak consistency (same as :meth:walk): concurrent
structural changes during matching may cause incomplete results but
will not raise exceptions.
dmemfs._handle
MemoryFileHandle Objects
class MemoryFileHandle(io.RawIOBase)
Binary file handle returned by :meth:MemoryFileSystem.open.
Wraps a :class:FileNode and provides standard stream semantics over
MFS in-memory storage. Inherits :class:io.RawIOBase.
The underlying file lock (read or write) is held for the entire
lifetime of the handle — from :meth:MemoryFileSystem.open until
:meth:close. Always use the with statement to ensure timely
release::
with mfs.open("/data.bin", "rb") as f:
data = f.read()
Do not instantiate this class directly; always obtain it through
:meth:MemoryFileSystem.open.
__init__
def __init__(mfs: MemoryFileSystem,
fnode: FileNode,
path: str,
mode: str,
is_append: bool = False) -> None
read
def read(size: int = -1) -> bytes
Read and return up to size bytes.
Parameters
size : int
Maximum bytes to read. -1 (default) reads to EOF.
Returns
bytes
The data read. Returns b"" when the cursor is already at or
past EOF.
Raises
ValueError
The handle is closed.
io.UnsupportedOperation
The handle was opened in a write-only mode
("wb", "ab", or "xb").
write
def write(data: Any) -> int
Write data to the file and return the number of bytes written.
For "ab" mode, the cursor is automatically moved to EOF before
each write (POSIX append semantics).
Quota is charged only for the net increase in file size. Overwriting existing bytes (cursor + len(data) ≤ current size) does not consume additional quota.
Parameters
data : bytes | bytearray | memoryview Data to write.
Returns
int
Number of bytes written (always len(data) unless the storage
raises).
Raises
TypeError
data is not a bytes-like object.
ValueError
The handle is closed.
io.UnsupportedOperation
The handle was opened in read-only mode ("rb").
MFSQuotaExceededError
Writing data would exceed the filesystem quota.
readinto
def readinto(buffer: Any) -> int
seek
def seek(offset: int, whence: int = 0) -> int
Set the stream position and return the new absolute position.
Parameters
offset : int Byte offset relative to the position indicated by whence. whence : int Positioning anchor:
* ``0`` / ``io.SEEK_SET`` — relative to file start;
*offset* must be ≥ 0.
* ``1`` / ``io.SEEK_CUR`` — relative to current position.
* ``2`` / ``io.SEEK_END`` — relative to EOF;
*offset* must be ≤ 0 (seeking *past* EOF is not supported).
Returns
int New absolute stream position.
Raises
ValueError
whence is not 0, 1, or 2; or the resulting position is
negative; or a positive offset is used with SEEK_END.
ValueError
The handle is closed.
tell
def tell() -> int
Return the current stream position.
Returns
int Current byte offset from the beginning of the file.
Raises
ValueError The handle is closed.
truncate
def truncate(size: int | None = None) -> int
Resize the file to exactly size bytes and return the new size.
If size is larger than the current file size, the file is extended with null bytes (POSIX semantics). If size is smaller, excess data is discarded and the freed quota is released immediately.
Parameters
size : int | None
Target size in bytes. None uses the current cursor position.
Must be ≥ 0.
Returns
int The new file size.
Raises
ValueError
The handle is closed, or size is negative.
io.UnsupportedOperation
The handle was opened in read-only mode ("rb").
MFSQuotaExceededError
Extending the file would exceed the filesystem quota.
flush
def flush() -> None
readable
def readable() -> bool
writable
def writable() -> bool
seekable
def seekable() -> bool
close
def close() -> None
Flush and release the file lock.
Subsequent operations on a closed handle raise :exc:ValueError.
Calling close() on an already-closed handle is a no-op.
Notes
Prefer the with statement over explicit close() calls to
guarantee cleanup even when exceptions occur.
__enter__
def __enter__() -> MemoryFileHandle
__exit__
def __exit__(*args: object) -> None
__del__
def __del__() -> None
Fallback cleanup: emit ResourceWarning and close if not already closed.
This is a last-resort safety net. It is invoked by the garbage collector and its timing is not guaranteed (especially outside CPython or when circular references exist).
Do not rely on __del__ for deterministic lock release. Always use
the with statement.
dmemfs._async
Async wrapper around MemoryFileSystem.
All I/O is delegated to :func:asyncio.to_thread, so the underlying
synchronous locks are never held on the event-loop thread.
MemoryFileSystem
MFSStatResult
MFSStats
AsyncMemoryFileHandle Objects
class AsyncMemoryFileHandle()
Async wrapper for a single open-file handle.
__init__
def __init__(_sync_handle) -> None
read
async def read(size: int = -1) -> bytes
Read bytes. See :meth:MemoryFileHandle.read.
write
async def write(data: bytes) -> int
Write bytes. See :meth:MemoryFileHandle.write.
seek
async def seek(offset: int, whence: int = 0) -> int
Set stream position. See :meth:MemoryFileHandle.seek.
tell
async def tell() -> int
Return current stream position. See :meth:MemoryFileHandle.tell.
truncate
async def truncate(size: int | None = None) -> int
Resize the file. See :meth:MemoryFileHandle.truncate.
flush
async def flush() -> None
Flush the write buffer (no-op for MFS). See :meth:MemoryFileHandle.flush.
readable
async def readable() -> bool
Return True if the handle supports reading.
writable
async def writable() -> bool
Return True if the handle supports writing.
seekable
async def seekable() -> bool
Return True (MFS handles are always seekable).
close
async def close() -> None
Release the file lock. See :meth:MemoryFileHandle.close.
__aenter__
async def __aenter__() -> AsyncMemoryFileHandle
__aexit__
async def __aexit__(*args) -> None
AsyncMemoryFileSystem Objects
class AsyncMemoryFileSystem()
Thin async facade over :class:MemoryFileSystem.
Every method delegates to the synchronous implementation via
asyncio.to_thread, so the event-loop is never blocked.
__init__
def __init__(max_quota: int = 256 * 1024 * 1024,
chunk_overhead_override: int | None = None,
promotion_hard_limit: int | None = None,
max_nodes: int | None = None,
default_storage: str = "auto",
default_lock_timeout: float | None = 30.0,
memory_guard: str = "none",
memory_guard_action: str = "warn",
memory_guard_interval: float = 1.0) -> None
Parameters
max_quota : int
See :class:MemoryFileSystem.
chunk_overhead_override : int | None
See :class:MemoryFileSystem.
promotion_hard_limit : int | None
See :class:MemoryFileSystem.
max_nodes : int | None
See :class:MemoryFileSystem.
default_storage : str
See :class:MemoryFileSystem.
default_lock_timeout : float | None
See :class:MemoryFileSystem. Default for async is 30.0.
memory_guard : str
See :class:MemoryFileSystem.
memory_guard_action : str
See :class:MemoryFileSystem.
memory_guard_interval : float
See :class:MemoryFileSystem.
open
async def open(path: str,
mode: str = "rb",
preallocate: int = 0,
lock_timeout: float | None = None) -> AsyncMemoryFileHandle
Open a file and return an :class:AsyncMemoryFileHandle.
All parameters and exceptions are identical to
:meth:MemoryFileSystem.open; the operation is offloaded to a thread
via :func:asyncio.to_thread.
Returns
AsyncMemoryFileHandle
Use with async with to ensure the handle is closed::
async with await mfs.open("/f.bin", "wb") as f:
await f.write(b"data")
mkdir
async def mkdir(path: str, exist_ok: bool = False) -> None
Create a directory. See :meth:MemoryFileSystem.mkdir.
rename
async def rename(src: str, dst: str) -> None
Rename or move a file or directory. See :meth:MemoryFileSystem.rename.
move
async def move(src: str, dst: str) -> None
Move a file or directory, creating parents as needed. See :meth:MemoryFileSystem.move.
remove
async def remove(path: str) -> None
Delete a single file. See :meth:MemoryFileSystem.remove.
rmtree
async def rmtree(path: str) -> None
Recursively delete a directory. See :meth:MemoryFileSystem.rmtree.
listdir
async def listdir(path: str) -> list[str]
Return entry names in a directory. See :meth:MemoryFileSystem.listdir.
exists
async def exists(path: str) -> bool
Return True if path exists. See :meth:MemoryFileSystem.exists.
is_dir
async def is_dir(path: str) -> bool
Return True if path is a directory. See :meth:MemoryFileSystem.is_dir.
is_file
async def is_file(path: str) -> bool
Return True if path is a regular file. See :meth:MemoryFileSystem.is_file.
stat
async def stat(path: str) -> MFSStatResult
Return metadata for path. See :meth:MemoryFileSystem.stat.
stats
async def stats() -> MFSStats
Return aggregate filesystem statistics. See :meth:MemoryFileSystem.stats.
get_size
async def get_size(path: str) -> int
Return the size of a file in bytes. See :meth:MemoryFileSystem.get_size.
export_as_bytesio
async def export_as_bytesio(path: str,
max_size: int | None = None) -> io.BytesIO
Export file contents as BytesIO. See :meth:MemoryFileSystem.export_as_bytesio.
export_tree
async def export_tree(prefix: str = "/",
only_dirty: bool = False) -> dict[str, bytes]
Export files as a dict. See :meth:MemoryFileSystem.export_tree.
import_tree
async def import_tree(tree: dict[str, bytes]) -> None
Import a dict of files atomically. See :meth:MemoryFileSystem.import_tree.
copy
async def copy(src: str, dst: str) -> None
Copy a single file. See :meth:MemoryFileSystem.copy.
copy_tree
async def copy_tree(src: str, dst: str) -> None
Deep-copy a directory subtree. See :meth:MemoryFileSystem.copy_tree.
walk
async def walk(path: str = "/") -> list[tuple[str, list[str], list[str]]]
Walk the directory tree and return results as a list.
Unlike the synchronous :meth:MemoryFileSystem.walk generator, this
method collects all results into a list before returning (the
generator cannot be safely iterated across thread boundaries).
Returns
list[tuple[str, list[str], list[str]]]
Same structure as :func:os.walk top-down results.
glob
async def glob(pattern: str) -> list[str]
Return paths matching pattern. See :meth:MemoryFileSystem.glob.
dmemfs._archive
dmemfs/_archive.py — Pluggable archive extraction adapters.
v15 addition: provides ArchiveAdapter base class, ZipAdapter / TarAdapter
standard adapters, and the expand_archive() / expand_archive_streaming()
public functions for extracting archives directly into a MemoryFileSystem.
References
- spec_v15.md §8 (第8部)
- DetailedDesignSpec_v3.md §27
- DetailedDesignSpec_test_v3.md §23–§27
ArchiveAdapter Objects
class ArchiveAdapter(ABC)
Base class for archive format adapters.
Subclasses implement members() and _can_handle_impl().
The public can_handle() classmethod handles BytesIO seek restoration
and exception suppression automatically — subclasses must not override
it; implement _can_handle_impl() instead.
members
@abstractmethod
def members() -> Iterator[tuple[str, bytes]]
Yield (raw_path_in_archive, file_bytes) for each file entry.
Directory entries must not be yielded. Path normalization is the caller's responsibility.
Notes
This is an :func:~abc.abstractmethod. Every concrete adapter subclass
must implement this method. Do not yield directory entries; only
yield file members.
can_handle
@classmethod
def can_handle(cls, source: str | io.BytesIO) -> bool
Return True if this adapter can process source.
For BytesIO sources the seek position is always restored to its
value before this call. Any exception raised by _can_handle_impl
is caught and False is returned (fail-safe).
Do not override in subclasses — implement _can_handle_impl.
close
def close() -> None
Release any held resources (default: no-op).
__enter__
def __enter__() -> Self
__exit__
def __exit__(*exc: object) -> None
ZipAdapter Objects
class ZipAdapter(ArchiveAdapter)
Adapter for ZIP archives (uses stdlib zipfile).
__init__
def __init__(source: str | io.BytesIO) -> None
members
def members() -> Iterator[tuple[str, bytes]]
TarAdapter Objects
class TarAdapter(ArchiveAdapter)
Adapter for TAR archives (uses stdlib tarfile).
Supports .tar, .tar.gz, .tar.bz2, .tar.xz.
Uses sequential for member in tf: iteration to avoid loading all
member metadata into memory at once (lower peak memory than getmembers()).
__init__
def __init__(source: str | io.BytesIO) -> None
members
def members() -> Iterator[tuple[str, bytes]]
expand_archive
def expand_archive(mfs: "MemoryFileSystem",
source: str | io.BytesIO,
dest: str = "/",
*,
on_conflict: Literal["raise", "overwrite"] = "raise",
adapter: ArchiveAdapter | None = None,
adapters: list[type[ArchiveAdapter]] | None = None) -> None
Extract an archive into mfs atomically (All-or-Nothing).
The extraction delegates to :meth:MemoryFileSystem.import_tree, which
provides full rollback on quota exceeded or any other error.
Parameters
mfs:
Target MemoryFileSystem instance.
source:
File path (str) or io.BytesIO containing the archive.
dest:
Destination prefix inside MFS. Defaults to root "/"
(archive paths are appended directly).
on_conflict:
How to handle collisions:
"raise"(default) — raiseFileExistsError/IsADirectoryErrorand leave MFS unchanged."overwrite"— overwrite existing files (emitUserWarning). Directory collisions always raiseIsADirectoryError.
Note: "skip" is only available in :func:expand_archive_streaming.
adapter:
Provide a pre-instantiated :class:ArchiveAdapter directly.
Bypasses automatic format detection. Takes priority over adapters.
adapters:
List of adapter classes for auto-detection. None uses the
default [ZipAdapter, TarAdapter].
Raises
ValueError
If on_conflict is not "raise" or "overwrite", or if no
adapter can handle source.
FileExistsError
on_conflict="raise" and a file already exists at the destination.
IsADirectoryError
A directory already exists at the destination path.
MFSQuotaExceededError
Combined archive data exceeds MFS quota. MFS is rolled back.
expand_archive_streaming
def expand_archive_streaming(
mfs: "MemoryFileSystem",
source: str | io.BytesIO,
dest: str = "/",
*,
on_conflict: Literal["raise", "overwrite", "skip"] = "raise",
adapter: ArchiveAdapter | None = None,
adapters: list[type[ArchiveAdapter]] | None = None) -> int
Extract an archive into mfs entry-by-entry (streaming).
Unlike :func:expand_archive, this function does not provide
All-or-Nothing atomicity. If an error occurs mid-extraction, already
extracted files remain in MFS.
Parameters
mfs:
Target MemoryFileSystem instance.
source:
File path (str) or io.BytesIO containing the archive.
dest:
Destination prefix inside MFS.
on_conflict:
How to handle collisions:
* ``"raise"`` *(default)* — raise on first conflict, leaving partial
extraction in MFS.
* ``"overwrite"`` — overwrite existing files (emit ``UserWarning``).
* ``"skip"`` — silently skip both archive-duplicate and MFS-existing
entries. Directory collisions always raise ``IsADirectoryError``.
adapter: Pre-instantiated adapter; bypasses auto-detection (takes priority). adapters: Adapter class list for auto-detection.
Returns
int
Number of successful write operations performed. When on_conflict
is "overwrite" and a duplicate archive entry is encountered, each
write is counted individually (so the return value may exceed the
number of unique destination paths). Skipped entries are not counted.
Raises
ValueError
Invalid on_conflict value or no adapter found for source.
FileExistsError
on_conflict="raise" and a collision is detected.
IsADirectoryError
A directory already exists at the destination path
(regardless of on_conflict).
MFSQuotaExceededError
Quota exceeded during write. Partial extraction may remain in MFS.
dmemfs._text
MFSTextHandle: bufferless text I/O helper.
MFS-specific text wrapper used instead of io.TextIOWrapper.
Immediate quota checking, no readinto() required, no cookie seek issues.
MFSTextHandle Objects
class MFSTextHandle()
Bufferless text I/O helper that wraps MemoryFileHandle.
Parameters
handle:
Binary handle obtained from MemoryFileSystem.open().
encoding:
Text encoding (default "utf-8").
errors:
Decode error handling (default "strict").
Example
with mfs.open("/data/hello.bin", "wb") as f: ... th = MFSTextHandle(f, encoding="utf-8") ... th.write("こんにちは世界\n")
__init__
def __init__(handle: MemoryFileHandle,
encoding: str = "utf-8",
errors: str = "strict") -> None
encoding
@property
def encoding() -> str
Text encoding.
errors
@property
def errors() -> str
Decode error handling.
write
def write(text: str) -> int
Encode text and write it to the handle.
Parameters
text: The string to write.
Returns
int Number of characters written (not bytes).
read
def read(size: int = -1) -> str
Read bytes and decode them.
Parameters
size:
Maximum number of characters to read. -1 reads everything.
Returns
str
Decoded text. Returns "" at EOF.
readline
def readline(limit: int = -1) -> str
Read one line.
Recognizes \n, \r\n, and bare \r as line endings.
Parameters
limit:
Maximum number of characters to read (-1 means unlimited).
Returns
str
One line including the terminating newline, or "" at EOF.
__iter__
def __iter__() -> Iterator[str]
Line iterator.
__next__
def __next__() -> str
__enter__
def __enter__() -> MFSTextHandle
Return self to support with MFSTextHandle(...) as th: usage.
__exit__
def __exit__(*args: object) -> None
Exit the context. Does not close the underlying binary handle.
The binary handle's lifecycle is managed by the enclosing
with mfs.open(...) block.
dmemfs._exceptions
MFSQuotaExceededError Objects
class MFSQuotaExceededError(OSError)
Raised when the quota limit is exceeded. Subclass of OSError.
__init__
def __init__(requested: int, available: int) -> None
Parameters
requested : int Number of bytes that were requested. available : int Number of bytes that were available at the time of the request.
MFSNodeLimitExceededError Objects
class MFSNodeLimitExceededError(MFSQuotaExceededError)
Raised when the node count limit is exceeded. Subclass of MFSQuotaExceededError.
__init__
def __init__(current: int, limit: int) -> None
Parameters
current : int
Number of nodes present when the limit was hit.
limit : int
Configured max_nodes value.
dmemfs._typing
MFSStats Objects
class MFSStats(TypedDict)
Aggregate filesystem usage statistics returned by :meth:MemoryFileSystem.stats.
Fields
used_bytes : int
Total quota bytes currently consumed by file data and chunk
overhead.
quota_bytes : int
Configured maximum quota (max_quota passed to the constructor).
free_bytes : int
Remaining quota (quota_bytes - used_bytes).
file_count : int
Number of file nodes in the filesystem.
dir_count : int
Number of directory nodes (including the root).
chunk_count : int
Total number of chunks held by :class:SequentialMemoryFile
instances. Files that have been promoted to
:class:RandomAccessMemoryFile are not counted.
overhead_per_chunk_estimate : int
Per-chunk overhead value used for quota accounting.
used_bytes
quota_bytes
free_bytes
file_count
dir_count
chunk_count
overhead_per_chunk_estimate
MFSStatResult Objects
class MFSStatResult(TypedDict)
Per-path metadata returned by :meth:MemoryFileSystem.stat.
Fields
size : int
File size in bytes. Always 0 for directories.
created_at : float
Creation timestamp in the same format as :func:time.time.
modified_at : float
Timestamp of the last data modification (write() or
truncate()). Equal to created_at for files that have
not been written to.
generation : int
Monotonically increasing change counter. 0 means the file
has not been written since creation or the last
:meth:~MemoryFileSystem.import_tree call.
is_dir : bool
True for directories, False for files.
size
created_at
modified_at
generation
is_dir
dmemfs._pytest_plugin
pytest fixture plugin.
Usage::
# conftest.py
pytest_plugins = ["dmemfs._pytest_plugin"]
This makes the mfs fixture automatically available::
def test_something(mfs):
with mfs.open("/a.txt", "wb") as f:
f.write(b"hello")
MemoryFileSystem
mfs
@pytest.fixture
def mfs() -> MemoryFileSystem
A :class:MemoryFileSystem fixture with default quota (1 MiB).
Provides an independent instance per test (function scope).