Skip to content

feat(api): open a file from memory, and take a File wherever a path is taken - #662

Merged
andiwand merged 1 commit into
mainfrom
feat/file-api
Aug 8, 2026
Merged

feat(api): open a file from memory, and take a File wherever a path is taken#662
andiwand merged 1 commit into
mainfrom
feat/file-api

Conversation

@andiwand

@andiwand andiwand commented Aug 8, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Split out of #661 — no wasm in here.

Every input entry point took a std::string path, so a caller holding bytes and
no path — a download, a browser upload, a decrypted payload — had no way in.
internal::MemoryFile already did the right thing, but no binding could reach
it.

  • File::from_disk(path) and File::from_memory(data), and the same pair on
    DocumentFile so the common case — bytes straight to a document — needs no
    intermediate handle. Named factories rather than constructors, because
    File(const std::string &) already means path.
  • A File overload wherever a path is taken: DecodedFile::list_file_types /
    mimetype, DecodedFile(File, DecodePreference), DocumentFile::type /
    meta and its constructor, and the odr:: free functions. The path forms
    become one-line forwards to from_disk.
  • magic::mimetype gains a File overload — it was the last place that reached
    for DiskFile on its own. The internals keep taking
    const std::shared_ptr<abstract::File> &: open_strategy hands the file on to
    ZipFile / TextFile / PdfFile and friends, which store it, so that is the
    parameter type that says what actually happens.
  • The python binding exposes all of it. The File forms of open carry
    py::call_guard<py::gil_scoped_release> like their path siblings — decoding is
    long-running either way — and DocumentFile.from_memory copies the bytes out
    under the GIL before releasing it for the decode.

Follow-up, not in scope: the path overloads are now pure forwards, so they can
be deprecated and removed once cli/, python/, jni/, apple/ and test/
are migrated off them. CI builds with -Werror, so that order is forced.
File::from_disk stays.

Tested: full C++ suite (764 passed, 9 skipped at the top of the stack) and
the pytest suite (59 passed), which gains coverage for both factories, the
File entry points, binary round-tripping through from_memory, and that the
File and path forms agree.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6fa0505da

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread python/src/bind_core.cpp Outdated
…s taken

Every input entry point took a `std::string` path, so a caller holding bytes
and no path — a download, a browser upload, a decrypted payload — had no way
in. `internal::MemoryFile` already did the right thing, but no binding could
reach it: binding the internals is exactly what the bindings are told not to
do.

`File` gains two named factories, `from_disk` and `from_memory`, and
`DocumentFile` gains the same pair so the common case — bytes straight to a
document — needs no intermediate handle. Named rather than constructors
because `File(const std::string &)` already means *path*, so a
`File(std::string)` overload would silently change what every existing call
site does.

The rest is symmetry: a caller holding a `File` now reaches everything a
caller holding a path reaches — `DecodedFile::list_file_types`/`mimetype`,
`DecodedFile(File, DecodePreference)`, `DocumentFile::type`/`meta` and its
constructor, and the `odr::` free functions. The path forms become one-line
forwards to `from_disk`, and `magic::mimetype` gains a `File` overload; it was
the last place that reached for `DiskFile` on its own.

The python binding exposes all of it. The `File` forms of `open` carry
`py::call_guard<py::gil_scoped_release>` like their path siblings — decoding
is long-running either way — and `DocumentFile.from_memory` copies the bytes
out under the GIL before releasing it for the decode.

The path overloads are now all one-line forwards to `from_disk`, so they can
be deprecated and removed once `cli/`, `python/`, `jni/`, `apple/` and `test/`
are migrated off them — CI builds with `-Werror`, so that order is forced.
`File::from_disk` stays: it is the one place a path belongs. Output paths are
a separate question — `File::copy`, `Document::save` and
`HtmlService::bring_offline` genuinely write to disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjFJ66sma1ye9ZnhM3tNeH
@andiwand
andiwand enabled auto-merge (squash) August 8, 2026 18:06
@andiwand
andiwand merged commit 01dc16c into main Aug 8, 2026
34 checks passed
@andiwand
andiwand deleted the feat/file-api branch August 8, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant