Skip to content

Option to directly pass the PDF as bytes to process_pdf instead of reading from disk - #67

Closed
jangoepfert wants to merge 1 commit into
grobidOrg:masterfrom
jangoepfert:master
Closed

Option to directly pass the PDF as bytes to process_pdf instead of reading from disk#67
jangoepfert wants to merge 1 commit into
grobidOrg:masterfrom
jangoepfert:master

Conversation

@jangoepfert

Copy link
Copy Markdown
Contributor

Hi, it would be nice to be able to pass a PDF file that is already loaded in memory directly to Grobid. In my use case, users upload PDFs using an API, which are then parsed directly without writing the content to files first. The parsing result is then added to a database. As process_pdf already returns the parsing result without writing it to a file, adding the option to pass a PDF as bytes to process_pdf would allow for a workflow where everything is kept in-memory.

Thank you for your great work!

lfoppiano added a commit that referenced this pull request Aug 15, 2026
- Archive streaming without full decompression (this is half true now, because the client needs the files on the disk.., but it will be automatically fixed with PR #67). 
- Glob patterns instead of a bare directory, improve the flexibility
- S3 input streaming can be also done using S3://
- Atomic writing, move the file after it was fully written so that if the client is killed there is no risk of half-made output file
@lfoppiano

Copy link
Copy Markdown
Member

Took over in #124

@lfoppiano lfoppiano closed this Aug 15, 2026
lfoppiano added a commit that referenced this pull request Aug 15, 2026
The archive and s3 streaming (#117) shipped with a known detour: every
entry was written to a temporary directory only so that process_pdf
could open it again from a path, with the commit itself noting this
would go away once PR #67 landed. It has landed, so this plugs the two
together: archive entries and loose s3 objects are now read straight
into memory and posted from there, named after the entry (or the s3
basename), and nothing but the results ever touches the disk.

process_batch accepts the in-memory documents alongside paths - an
entry goes by the name it carries, and since process_pdf returns that
same name, the result lands on the same output file it would have as a
path. The one input that still takes the temp-dir route is
processCitationList, whose .txt files are read by process_txt from a
path.

The archive tests asserted on the temp dirs the posts came from, which
no longer exist; they now assert on what actually crossed the wire -
each entry posted once, under its archive name, with its own bytes -
plus explicitly that mkdtemp is never called on the pdf path.

Completes what #117 left pending on #67.
lfoppiano added a commit that referenced this pull request Aug 16, 2026
process_pdf could only read the document from disk, so callers holding a
PDF in memory - fetched from an API, read out of a database or an object
store - had to write it to a temporary file only for the client to open
it again.

It now takes the document itself as well: bytes, or any binary stream.
Nothing says which of the two it is; the object does. A document also
names itself, from the "name" attribute open() sets on files and that can
be set on anything else, io.BytesIO included, so the identity of a
document is not lost by going through memory - it travels with the
request and comes back with the result. Bytes on their own have nothing
to be named after and fall back to DEFAULT_IN_MEMORY_NAME.

A stream is read once, up front, and re-served from memory afterwards:
the 503 retry sends the same document again, and a consumed (or
non-seekable) stream would silently post an empty body the second time
around. That is also why the retry no longer recurses through the public
entry point, which would have had to re-derive a name from a source that
is by then exhausted.

process_documents processes several of them concurrently, through the
same ThreadPoolExecutor the file-based processing uses. Results come back
in input order rather than in completion order: in-memory documents have
no filenames to be matched back on afterwards, so the caller has nothing
but the order to zip them onto. A single PDF passed by mistake raises
instead of being iterated, which would otherwise send one request per
byte.

An in-memory run keeps up to n documents in flight against the server,
so a client concurrency above the server's engine pool only piles up
requests that queue there or come back as 503, while one below it
leaves engines idle. Neither is visible from the client side until the
throughput disappoints.

Before process_documents and the in-memory archive/s3 streaming start,
the client now asks /api/health how many engines the server has
(pool.maxActive) and logs a warning when n exceeds them - with the
number to use instead - and an info message when they outnumber n. The
check is advisory, not a gate: a server without the endpoint (older
GROBID), an unreadable answer or a connection failure never blocks the
run. A server answering ready: false is also surfaced as a warning.



Completes what #117 left pending on #67.


---------

Co-authored-by: Jan Göpfert <94385965+jangoepfert@users.noreply.github.com>
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.

2 participants