|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to this project will be documented in this file. |
| 4 | + |
| 5 | +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | + |
| 8 | +## [Unreleased] |
| 9 | + |
| 10 | +### Added |
| 11 | +- `MemoryFileSystem.is_file(path)` for API symmetry with `exists()` / `is_dir()` |
| 12 | +- `MemoryFileHandle` file-like methods: `truncate()`, `flush()`, `readable()`, `writable()`, `seekable()` |
| 13 | +- Async counterparts for new file-like methods and `AsyncMemoryFileSystem.is_file()` |
| 14 | +- `IMemoryFile._bulk_load(data)` method for internal use by `import_tree()` / `_deep_copy_subtree()` (improves encapsulation) |
| 15 | + |
| 16 | +### Changed |
| 17 | +- **[BREAKING]** `MFSStatResult.is_sequential` field removed. This internal implementation detail is no longer exposed in the public API. |
| 18 | +- **[BREAKING]** Package renamed from `memory-file-system` / `memory_file_system` to `D-MemFS` / `dmemfs`. Update imports: `from dmemfs import ...` |
| 19 | +- `stat()` directory support: previously raised `IsADirectoryError`; behavior unchanged in this release, but `is_dir` field added to `MFSStatResult` in future |
| 20 | +- `export_as_bytesio()` TOCTOU gap fixed: `_rw_lock.acquire_read()` now called inside `_global_lock` block |
| 21 | +- `exists()` / `is_dir()` now resolve paths under `_global_lock` for consistent thread-safety policy |
| 22 | +- `open(..., preallocate=...)` now performs preallocation under `_global_lock` |
| 23 | +- `import_tree()` quota accounting simplified to net-delta apply after successful write phase |
| 24 | +- `import_tree()` rollback now also cleans up auto-created parent directories |
| 25 | +- CI workflow now runs tests with coverage (`--cov`, `coverage.xml`) and uploads coverage artifact |
| 26 | + |
| 27 | +### Documentation |
| 28 | +- README / README_en updated with lock behavior notes (`_global_lock` hold, writer starvation) |
| 29 | +- README / README_en API tables updated for `is_file()` and new handle methods |
| 30 | +- Clarified that `stat()` is file-only and that `MFSStatResult.is_sequential` is implementation detail |
| 31 | + |
| 32 | +### Tests |
| 33 | +- Added tests for `is_file()`, handle file-like methods, async wrappers, and `import_tree()` parent-dir rollback |
| 34 | + |
| 35 | +## [0.2.0] - 2026-02-26 |
| 36 | + |
| 37 | +### Added |
| 38 | +- **Directory Index Layer**: Internal refactoring from flat `dict[str, IMemoryFile]` to `DirNode`/`FileNode` tree structure |
| 39 | + - `listdir()` complexity improved from O(N total entries) to O(children count) |
| 40 | + - `rename()` / `rmtree()` no longer require prefix scanning |
| 41 | +- **`move(src, dst)`**: Move files/directories with automatic parent directory creation |
| 42 | +- **`copy_tree(src, dst)`**: Deep copy of directory subtrees with quota pre-check |
| 43 | +- **`stat(path)`**: Return `MFSStatResult` with size, timestamps, generation, storage type |
| 44 | +- **`glob("**")`**: Recursive glob pattern matching with `**` support |
| 45 | +- **File timestamps**: `created_at` / `modified_at` on `FileNode`, updated on write/truncate |
| 46 | +- **`MFSStatResult` TypedDict**: New typed return value for `stat()` |
| 47 | +- **`bytearray` shrink**: `RandomAccessMemoryFile.truncate()` reallocates buffer when new size ≤ 25% of old capacity |
| 48 | +- **`AsyncMemoryFileSystem` / `AsyncMemoryFileHandle`**: Async wrappers via `asyncio.to_thread()` |
| 49 | +- **`pytest-asyncio`** added to test dependencies |
| 50 | + |
| 51 | +### Changed |
| 52 | +- `IMemoryFile` is now pure data storage; `is_dir`, `generation`, `_rw_lock` moved to `DirNode`/`FileNode` |
| 53 | +- `wb` mode: truncate now executes **after** write-lock acquisition (prevents data corruption during concurrent reads) |
| 54 | +- `export_as_bytesio()`: entry lookup now protected by `_global_lock` (prevents race with concurrent `remove()`) |
| 55 | +- `MemoryFileHandle.__del__`: `stacklevel` changed from 2 to 1 for correct warning source location |
| 56 | +- `__version__` bumped to `0.2.0` |
| 57 | + |
| 58 | +### Tests |
| 59 | +- 288 tests (was 230): +58 new tests including `test_fs_coverage.py` |
| 60 | +- Coverage target documented at 99% (claim) with CI-side coverage XML generation in place |
| 61 | +- New test files: `test_timestamp.py`, `test_async.py` |
| 62 | +- Extended: `test_rename_move.py` (+13), `test_files_randomaccess.py` (+5), `test_concurrency.py` (+2), `test_open_modes.py` (+2), `test_export_import.py` (+1), `test_mkdir_listdir.py` (+2) |
| 63 | + |
| 64 | +## [0.1.0] - 2026-02-23 |
| 65 | + |
| 66 | +### Added |
| 67 | +- Initial release of MemoryFileSystem (MFS) |
| 68 | +- In-process virtual filesystem with hard quota management |
| 69 | +- `MemoryFileSystem` class with full POSIX-like file operations: |
| 70 | + - `open()` supporting modes `rb`, `wb`, `ab`, `r+b`, `xb` with optional preallocate |
| 71 | + - `mkdir()`, `rename()`, `remove()`, `rmtree()`, `listdir()`, `exists()`, `is_dir()` |
| 72 | + - `stats()` for quota and filesystem metrics |
| 73 | + - `export_as_bytesio()`, `export_tree()`, `iter_export_tree()` with `only_dirty` support |
| 74 | + - `import_tree()` with All-or-Nothing atomicity and rollback |
| 75 | +- `MemoryFileHandle` with full `io.RawIOBase`-compatible interface (read, write, seek, tell, truncate) |
| 76 | +- `MFSQuotaExceededError` — hard quota enforcement before any write |
| 77 | +- `ReadWriteLock` with configurable timeout — concurrent reads, exclusive writes |
| 78 | +- Auto-promotion from `SequentialMemoryFile` to `RandomAccessMemoryFile` on random-access writes |
| 79 | +- PEP 561 `py.typed` marker — full type annotation support |
| 80 | +- Zero external dependencies (Python 3.11+ standard library only) |
| 81 | +- 163 tests across Unit / Integration / Scenario / Property (Hypothesis) layers |
| 82 | +- CI matrix: 3 OS × 3 Python versions (3.11, 3.12, 3.13) |
0 commit comments