Release v0.6.0: rename to mailvault, single CLI, Graph built-in, package restructure - #2
Merged
Conversation
- add ci.yml: basedpyright typecheck + pytest matrix (3.11/3.12/3.13) on push/PR
- release.yml: bump JS actions to current majors (fixes the node20 deprecation)
checkout v4->v7, setup-python v5->v7, setup-uv v5->v9,
action-gh-release v2->v3, upload-artifact v4->v7
Release notes already come from CHANGELOG (house standard). The ruff lint job
is deferred to the scrubbing pass: the code is not yet ruff-clean and the
`[tool.ruff]` config still uses top-level `ignore` (should move to
`[tool.ruff.lint]`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
CPython's email header parser raises IndexError (not just HeaderParseError) on malformed values like "<>" on 3.11/3.12; only 3.13+ returns cleanly. normalize_message_id promised "unusable -> empty string" but let the IndexError escape, which would crash a run when a server reports such an ID. Guard the parse and return "" on any parser failure. Surfaced by the new CI test matrix (3.11/3.12/3.13). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
Rename the import package (src/imapbackup -> src/mailvault) and the distribution name. Updates all internal + test imports, the importlib.metadata version lookup, the [project.scripts] entry-point module paths, the dev self-dependency, and the PyInstaller source paths in release.yml. Entry-point command names (ib-*) stay for now; folding them into a single `mailvault` command is the next step. README and the CHANGELOG breaking-changes prose are deferred to the rename/CLI documentation step (they still describe the ib-* commands that are about to change). Repo URLs (github.com/sniner/imapbackup) unchanged. Tests green on 3.11/3.12/3.13, basedpyright clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
msal and httpx move into [project].dependencies; the optional `graph` extra and the dev self-reference are removed. Microsoft 365 is a first-class mailbox source for mailvault, so Graph support always ships. release.yml now installs `.` instead of `.[graph]`, and the project description is broadened beyond IMAP. CHANGELOG note under 0.6.0. The lazy `from mailvault.graph import MSGraphClient` in mailbox.py stays: it avoids a mailbox<->graph circular import, not for optionality. Tests green on 3.11/3.12/3.13, basedpyright clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
…t` command Replace the three ib-* tools with a single `mailvault <command>` CLI (argparse, nested subparsers). cli/__init__.py builds the parser and dispatches; the former cli modules become pure handler modules (mailbox: folders/backup/verify; copy; archive: stats/import/addresses/ compress/decompress/rebuild-db). Add __main__.py for `python -m mailvault` and the PyInstaller build. - ib-copy folders -> mailvault copy --list-folders - db-from-archive -> rebuild-db (under `mailvault archive`) - unified globals: -v/--verbose, -q/--quiet, --log-file, --config, --allow-exec, --job (given before the command) - pyproject: single `mailvault` entry point - release.yml: build one mailvault.exe The job logic in mailvault.jobs is unchanged. CHANGELOG breaking-changes note under 0.6.0; the README rewrite stays deferred. Tests green on 3.11/3.12/3.13, basedpyright clean; CLI smoke-tested (help, --version, error exit codes, archive stats end-to-end). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
Add an explicit ruff config (line-length 96, select = E/F/I/W/UP; move the
deprecated top-level `ignore` under [tool.ruff.lint]) and add ruff to the dev
group. Apply `ruff format` and `ruff check --fix` across the tree (UP017
datetime.timezone.utc -> datetime.UTC, import sorting, ...) and wrap the
remaining long SQL/select strings by hand.
- move the shared `dummy_eml_bytes` fixture to tests/conftest.py (removes the
imports that tripped F811)
- test_graph: drop the now-pointless pytest.importorskip("httpx") (httpx is a
core dep) and the `_httpx` module indirection; import httpx at the top
- graph.py: hoist the httpx/msal imports to module level now that Graph is core
(no lazy import inside __init__); use httpx.TransportError directly
- remove an unused local in test_mailbox
- ci.yml: add a lint job (ruff check + ruff format --check)
No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright clean, ruff clean.
The mailbox<->graph lazy import in mailbox.py stays (real circular import).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
Rewrite the README around the single `mailvault` command: - rename callout (formerly imapbackup; stay on v0.5.0 for the old ib-* commands) - Overview of the subcommand groups; examples updated (globals before the command) - a "Migrating from ib-*" mapping table - MS Graph documented as built-in (no [graph] extra) - one mailvault.exe on Windows Repo URLs point to sniner/mailvault (the repo is renamed at merge; GitHub redirects the old name afterwards). Add the rename as the headline breaking change in the 0.6.0 CHANGELOG, and bump pyproject/uv.lock to 0.6.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
The shared backend types (the MailboxClient protocol, MessageRef, BackupResult) lived in mailbox.py, so graph.py imported mailbox while mailbox.py imported graph -- a cycle that forced a lazy `import` inside Mailbox.__enter__. Move those types into a neutral `mailvault.backend` module (it only depends on cas). Now graph and mailbox both depend on backend, the cycle is gone, and the MSGraphClient import in mailbox.py is a normal top-level import. No lazy imports left. No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
mailbox.py was misnamed -- it is the IMAP backend, not a generic mailbox. Introduce a mailvault.backend package: - backend/base.py -- shared interface (MailboxClient, MessageRef, BackupResult) - backend/imap.py -- the IMAP backend (was mailbox.py: ImapClient, Mailbox, ...) - backend/graph.py -- the MS Graph backend (moved unchanged) - backend/__init__.py re-exports the base types Update jobs.py and the tests accordingly (test_mailbox.py -> test_imap.py). Import direction stays acyclic: imap -> graph -> base -> cas. No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
The project releases via GitHub Actions; the Forgejo workflow was unused. If it is ever needed again it can be recreated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
datetime.fromisoformat accepts a trailing `Z` from Python 3.11 on, which is the
minimum version, so the `.replace("Z", "+00:00")` shim and its 3.10 rationale
are obsolete. Drop them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
The per-command-group handler modules (cli/mailbox.py, cli/archive.py, cli/copy.py) were thin stubs whose names echoed the old ib-* tools and clashed with the new backend/imap.py. Merge them into a single cli/commands.py (run_mailbox / run_archive / run_copy); cli/__init__.py stays the parser + dispatcher. cli/ is now just __init__.py + commands.py. No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
…porter.py Group the on-disk storage of a mailvault archive into a store/ package: - store/cas.py content-addressed blob store (was cas.py) - store/storedb.py metadata SQLite index (was storedb.py) - store/__init__.py re-exports ContentAddressedStorage, StoreDatabase, ... archive.py becomes importer.py, and its MailArchive class becomes ExternalMailArchive: it reads *external* .eml archives (incl. Docuware) and imports them into the store, so the old name clashed with the store being the actual archive. Kept the module name `storedb` (not store/db) because `db` is the connection variable everywhere; a `db` module would shadow it. No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
…Database Name the metadata index by its content and stop the `Store*`-inside-`store/` stutter: store/metadb.py with MetaDatabase / MetaDatabaseConnection reads as "the store's metadata database" without repeating the namespace. tests/test_storedb.py -> tests/test_metadb.py. No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
Align the metadata-DB rebuild helper with the metadb naming and the CLI's `archive rebuild-db` verb; "update" was imprecise (it rebuilds the index from the CAS content). Point the never-used MetaDatabase path fallback at store.db too. The CLI command `rebuild-db`, the config option `with_db`, and the `store.db` filename stay unchanged (user-facing). No behaviour change. Tests green on 3.11/3.12/3.13, basedpyright + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAeRbKVFkVhmHUGW8dCToY
…en config Structural cleanups from the 0.6.0 review, no change to the normal backup/ verify/copy flows (232 tests green): - Move the mailbox factory out of backend/imap.py into backend/session.py (open_mailbox); ImapClient is now self-contained (connect/close) and no longer reaches into a Mailbox's private connection. The IMAP module no longer imports the Graph module. - Deduplicate the per-message store/callback/result logic and the whole-mailbox loop into base.store_message() / base.run_full_backup(), shared by both backends. - Replace the untyped metadata dict with a mailutils.MessageMetadata dataclass across the callback boundary. - Replace functools.lru_cache on the metadb lookup methods with per-instance caches (no instance leak, no unbounded growth). - Validate job configuration up front (conf.ConfigError): unknown backend or missing Graph credentials now fail early with a clear, job-named message. - copy --idle now raises a clear error for non-IMAP sources instead of failing internally. - Minor: DEFAULT_DB_NAME constant, drop dead blocksize fallback, document the Docuware .eml-only and YAML no-global cases, "from"/"to" address markers, remove metadb dev __main__ block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RCE2sib8vw3CNzwBhD7K7M
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release branch for v0.6.0. Same project, renamed from
imapbackuptomailvault, with a consolidated CLI, Microsoft Graph built in, and an internal restructure. No behaviour change beyond the documented breaking changes.User-facing (CHANGELOG 0.6.0)
imapbackup→mailvault(import package + distribution). Oldib-*commands: stay on v0.5.0.mailvault <command>replacesib-mailbox/ib-archive/ib-copy;db-from-archive→rebuild-db,ib-copy folders→copy --list-folders; onemailvault.exe.[graph]extra removed).<>) no longer crashes on Python 3.11/3.12.Internal (no behaviour change)
ci.yml(lint + typecheck + pytest 3.11/3.12/3.13),release.ymlaction majors bumped.ruff formatapplied.backend/—base.py(interface),imap.py(wasmailbox.py),graph.pystore/—cas.py,metadb.py(MetaDatabase)cli/—__init__.py(parser/dispatch) +commands.pyarchive.py→importer.py(ExternalMailArchive);jobs.update_db_from_archive→rebuild_metadbStatus
f0f41fc;mergeable: MERGEABLE,CLEAN(fast-forward, 16 commits overmain).mailvault backupagainst a real mailbox (dedup + incremental idempotency verified).Post-merge steps (not done here)
main.v0.6.0→ release workflow buildsmailvault.exe.imapbackup→mailvault(README links already point there).Security note
An earlier commit accidentally included a vim swap file of a credentials config (via
git add -A); removed via history rewrite + force-push,.gitignorehardened. Treat those credentials as compromised.