Skip to main content

bookmgr

A local ebook library manager. Point it at a folder of purchased ebook files (EPUB/PDF bundles from Humble Bundle and the like), and it identifies each book, fetches metadata and cover art from Open Library (falling back to the cover embedded in the EPUB), and files everything into a flat, title-named library — grouping the EPUB and PDF of the same book under one folder.

It is built as a library first, with a small CLI on top; a separate TUI consumes it as a frontend.

Install

Requires Python 3.10+.

# as a command-line tool
uv tool install "bookmgr[crypto]"

# as a dependency of your own project
uv add bookmgr

Each GitHub Release also carries the wheel. Until 2.0.0, the project was called bookman; the bookman package on PyPI is an unrelated bookmarks tool.

The crypto extra (PyCryptodome) lets bookmgr read AES-encrypted PDFs, such as InformIT's. Without it such a PDF is still imported, named after its file, and bookmgr says what to install to read it.

Usage

bookmgr init ~/Books                     # create the library and remember it
bookmgr import ~/Downloads/humble-bundle # import every EPUB/PDF in a folder
bookmgr import book.epub                 # or a single file
bookmgr list                             # everything, sorted by title
bookmgr list --needs-review              # only books whose match should be checked
bookmgr search "newport"                 # substring match on title or author
bookmgr config                           # which library is in use, and why

The library location is resolved in this order: --library DIR, then $BOOKMGR_LIBRARY, then the config saved by bookmgr init (in the platform user-config directory; $BOOKMGR_CONFIG points it elsewhere).

Library layout

~/Books/
├── .bookmgr-library.json                 # marks the folder as a library
├── .bookmgr-index.sqlite3                # disposable search index
├── Deep Work/
│   ├── Deep Work.epub
│   ├── Deep Work.pdf
│   ├── cover.png
│   └── metadata.json                     # source of truth for this book
└── Software Architecture - The Hard Parts/
    └── ...

Every book is one folder named after its title, holding all of its format files, a cover, and a metadata.json sidecar. There is no author-level nesting; the folder is the book's display name, and metadata.json carries a stable id that survives a rename. metadata.json records not just title/author/ISBN but how the book was identified (isbn, title_author, title_only, or not at all) and the weakest evidence used to group formats together, so a frontend can show why a book needs review rather than just that it does.

How identification works

  1. Parse the file for its own metadata (EPUB OPF, PDF info dictionary) and scan its text for an ISBN.
  2. If an ISBN was found, look it up on Open Library — but accept the record only if its title/author agree with what the file says about itself, so a false-positive ISBN scraped from a PDF can't replace good data.
  3. Otherwise, search Open Library by title and author and take the strongest agreeing candidate.
  4. Group the file with an existing book in the library by the same rule: exact ISBN first, then title + author, then identical title alone.

Imports never block for confirmation. A book that couldn't be identified confidently is flagged for review instead, and a book a human has marked as reviewed keeps its metadata across later imports.

Fixing a book is a frontend's job — the CLI only imports and inspects. From Python, Library.edit, Library.mark_reviewed and Library.reidentify do it; without a frontend, edit title, author or isbn in the book's metadata.json and set "reviewed": true so the fix survives the next import. bookmgr list picks such an edit up at once and refreshes the search index from what it read, so bookmgr search sees it from then on.

As a library

from pathlib import Path

from bookmgr import configured_library

lib = configured_library()
result = lib.import_directory(Path("~/Downloads/humble-bundle").expanduser())
for book in lib.scan():
    print(book.title, book.author, book.needs_review)

configured_library opens the library the user set up, with the metadata source their saved settings choose ("offline": true in the config turns lookups off). Library(root, source=...) itself never reads global state. Metadata lookups go through the MetadataSource protocol, so you can pass NullSource() to work offline or plug in your own source. Every exception raised is a BookmgrError, and the package logs under the "bookmgr" logger with a NullHandler installed.

One writer at a time. A library assumes a single process writes to it: one TUI or one CLI command, not both at once. Reading while something else writes is fine (every metadata.json is written atomically and the index is swapped in one step), but two concurrent imports into the same library can race on a book's folder, and the last metadata.json write wins. There is no lock file; if you run two frontends, run them against different libraries.

The public API is exactly what bookmgr/__init__.py exports. docs/API.md is the frontend guide to it, by task; docs/ARCHITECTURE.md has the design decisions behind it.

Development

uv sync                # editable install with dev extras
uv run pytest          # tests (no network access needed)
uv run pytest --cov
uv run ruff check .
uv run ruff format --check .
uv run mypy src/
uv build               # sdist + wheel into dist/

Releasing is tagging: git tag v<version> && git push origin v<version> runs the checks, builds the wheel and publishes a GitHub Release with it (.github/workflows/release.yml). See CONTRIBUTING.md for the spec-first rule that governs changes to identification and grouping.

The Makefile wraps these and adds targets for a throwaway dev library under .dev/ (make init, make import SRC=..., make list, make clean-dev) that keeps experiments out of your real config.

The only runtime dependency is pypdf; EPUB parsing and HTTP are stdlib. Adding another is a deliberate decision, not a convenience.

License

MIT — see LICENSE. The test fixtures under tests/fixtures/books/ are public-domain texts from Project Gutenberg and carry their own headers.

Metadata

Release files for bookmgr 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bookmgr 2.0.0
File Size Uploaded Provenance Actions
bookmgr-2.0.0.tar.gz 69.1 kB Without provenance Details for bookmgr-2.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bookmgr 2.0.0
File Interpreter ABI Platform Actions
bookmgr-2.0.0-py3-none-any.whl Python 3 none any Details for bookmgr-2.0.0-py3-none-any.whl

Total release size: 148.8 kB

Release files / bookmgr-2.0.0.tar.gz

Download URL bookmgr-2.0.0.tar.gz
Size 69.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c95044403bf9d81a5e75245fc13849605fe517ba82f27ecbdbe53997a08a51c9
BLAKE2b-256 checksum
How to use checksums
9fa41d86d3bddba1b41c4409bbb279c61a819d67bcb99f1917c82597c52338dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / bookmgr-2.0.0-py3-none-any.whl

Download URL bookmgr-2.0.0-py3-none-any.whl
Size 79.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
052e56acde46a778ed2d6cd6e19d0933678524f8b46f7fd326259af91efafba2
BLAKE2b-256 checksum
How to use checksums
ebdc5be1485aede4abd6bc2b291f31a5221e51f5d1a390fab33cfa75086567c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for bookmgr 2.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page