Skip to content

Memory Path API

pathlib_next.mempath

MemBytesIO(dest, *, append=False)

Bases: BytesIO

A BytesIO that writes its buffer back into the backing bytearray (dest) on flush() and close, so MemPath files persist across open() calls. With append=True every write lands at the end, like O_APPEND, whatever the current position.

Source code in src/pathlib_next/mempath.py
49
50
51
52
53
54
def __init__(self, dest: bytearray, *, append: bool = False) -> None:
    self._bytes = dest
    self._append = append
    super().__init__()
    if append:
        super().write(dest)

close()

Source code in src/pathlib_next/mempath.py
80
81
82
83
def close(self) -> None:
    if not self.closed:
        self._publish()
    return super().close()

flush()

Source code in src/pathlib_next/mempath.py
75
76
77
78
def flush(self) -> None:
    # A real file's flush makes the written bytes visible to readers.
    super().flush()
    self._publish()

write(data)

Source code in src/pathlib_next/mempath.py
65
66
67
68
def write(self, data) -> int:
    if self._append and not self.closed:
        self.seek(0, io.SEEK_END)
    return super().write(data)

writelines(lines)

Source code in src/pathlib_next/mempath.py
70
71
72
73
def writelines(self, lines) -> None:
    # BytesIO.writelines bypasses an overridden write().
    for line in lines:
        self.write(line)

MemFile(*args)

Bases: bytearray

A file's content in a MemPathBackend, carrying its modification time. A plain bytearray placed in a backend by hand still works; it reports st_mtime 0.

Source code in src/pathlib_next/mempath.py
28
29
30
31
def __init__(self, *args) -> None:
    super().__init__(*args)
    self.mtime = 0.0
    _touch(self)

mtime = 0.0 instance-attribute

MemPath(*segments, backend=None, **kwargs)

Bases: Path

In-memory Path implementation over nested dicts (see MemPathBackend) -- a lightweight virtual filesystem for mocks, tests, or transient storage, and the reference exemplar for Track A of extending this library (subclassing Path directly; see docs/guides/extending.md).

Source code in src/pathlib_next/mempath.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def __init__(
    self, *segments: str | Pathname | Path, backend: MemPathBackend = None, **kwargs
):
    # Joined and normalized like `PurePosixPath`: empty and "." segments
    # collapse, and an absolute argument restarts the join. Raw
    # concatenation made `MemPath("/") / "a"` the path "//a", unequal to
    # (and hashed apart from) `MemPath("/a")`, gave "d/" an empty name,
    # and let `MemPath("/root") / "/etc"` address "/root/etc".
    path = ""
    _backend = None
    for segment in segments:
        if isinstance(segment, MemPath):
            text = segment.as_posix()
            _backend = segment.backend
        elif isinstance(segment, Path):
            raise NotImplementedError()
        elif isinstance(segment, Pathname):
            text = "/".join(segment.segments)
        elif isinstance(segment, str):
            text = segment
        else:
            raise TypeError(
                "argument should be a str or a Pathname, "
                f"not {type(segment).__name__!r}"
            )
        if text.startswith("/") or not path:
            path = text
        elif text:
            path = f"{path}/{text}"
    self._segments = self._parse(path)
    # `is not None`, not truthiness: a freshly-created root's backend is
    # an *empty* dict, which is falsy -- `if _backend:` silently treated
    # that as "no backend found" and gave the child a disconnected new
    # one, breaking backend sharing for any join off an empty MemPath.
    if _backend is not None and backend is None:
        backend = _backend
    self._backend = backend if backend is not None else MemPathBackend()
    self._normalized = None

backend property

normalized property

parent property

parts property

segments property

as_uri()

Source code in src/pathlib_next/mempath.py
225
226
def as_uri(self):
    return f"mempath:{_urlquote(self.as_posix())}"

iterdir()

Source code in src/pathlib_next/mempath.py
308
309
310
311
312
313
314
315
316
def iterdir(self):
    parent, name = self._parent_container()
    content = parent.get(name) if name else parent
    if content is None:
        raise _os_error(FileNotFoundError, _errno.ENOENT, self)
    if not isinstance(content, dict):
        raise _os_error(NotADirectoryError, _errno.ENOTDIR, self)
    for c in list(content.keys()):
        yield self.with_segments(*self.segments, c)

relative_to(other)

Source code in src/pathlib_next/mempath.py
215
216
def relative_to(self, other):
    raise NotImplementedError()

rmdir()

Source code in src/pathlib_next/mempath.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def rmdir(self):
    parent, name = self._parent_container()
    if not name:
        raise _os_error(FileNotFoundError, _errno.ENOENT, self)
    content = parent.get(name)
    if content is None:
        raise _os_error(FileNotFoundError, _errno.ENOENT, self)
    elif not isinstance(content, dict):
        raise _os_error(NotADirectoryError, _errno.ENOTDIR, self)
    elif len(content) != 0:
        # pathlib raises OSError(ENOTEMPTY); FileExistsError (EEXIST)
        # carried no errno and matched no caller's ENOTEMPTY check.
        raise OSError(_errno.ENOTEMPTY, "Directory not empty", str(self))
    parent.pop(name)

stat(*, follow_symlinks=True)

Source code in src/pathlib_next/mempath.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
def stat(self, *, follow_symlinks=True):
    parent, name = self._parent_container()
    if not name:
        return FileStat(is_dir=True)

    if name not in parent:
        raise _os_error(FileNotFoundError, _errno.ENOENT, self)

    content = parent[name]
    is_dir = isinstance(content, dict)
    # st_size was never set for files (always defaulted to 0), which
    # silently broke any size-based checksum (e.g. PathSyncer's default
    # usage pattern).
    return FileStat(
        is_dir=is_dir,
        st_size=0 if is_dir else len(content),
        st_mtime=getattr(content, "mtime", 0),
    )
Source code in src/pathlib_next/mempath.py
275
276
277
278
279
280
281
282
283
284
285
286
287
def unlink(self, missing_ok=False):
    parent, name = self._parent_container()
    if not name:
        # The root exists and is a directory: missing_ok does not apply.
        raise _os_error(IsADirectoryError, _errno.EISDIR, self)
    content = parent.get(name)
    if content is None:
        if missing_ok:
            return
        raise _os_error(FileNotFoundError, _errno.ENOENT, self)
    elif isinstance(content, dict):
        raise _os_error(IsADirectoryError, _errno.EISDIR, self)
    parent.pop(name)

with_segments(*segments)

Source code in src/pathlib_next/mempath.py
218
219
220
221
222
223
def with_segments(self, *segments: str):
    if all(isinstance(segment, str) for segment in segments):
        # The `Pathname` protocol's spelling: segments joined with "/",
        # a leading "" marking the root (`("", "a")` is "/a").
        segments = ("/".join(segments),)
    return type(self)(*segments, backend=self.backend)

MemPathBackend

Bases: dict

Nested-dict storage backing one or more MemPath trees. A dict value is a directory; a bytearray value is a file's content. Share one instance across MemPaths (via backend=) to give them the same virtual filesystem.