grobid-client-python 0.1.4__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. grobid_client_python-0.2.0/.github/dependabot.yml +9 -0
  2. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/.github/workflows/ci-build.yml +8 -4
  3. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/.github/workflows/ci-release.yml +2 -2
  4. grobid_client_python-0.2.0/MANIFEST.in +2 -0
  5. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/PKG-INFO +165 -22
  6. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/Readme.md +161 -21
  7. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/config.json +0 -1
  8. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/client.py +44 -21
  9. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/TEI2LossyJSON.py +129 -90
  10. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/TEI2LossyJSON_cli.py +4 -2
  11. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/TEI2Markdown.py +19 -17
  12. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/TEI2Markdown_cli.py +4 -2
  13. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/__main__.py +3 -1
  14. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/validate_json_refs.py +3 -2
  15. grobid_client_python-0.2.0/grobid_client/grobid_client.py +2203 -0
  16. grobid_client_python-0.2.0/grobid_client/py.typed +0 -0
  17. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/PKG-INFO +165 -22
  18. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/SOURCES.txt +5 -0
  19. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/requires.txt +4 -0
  20. grobid_client_python-0.2.0/grobid_client_python.egg-info/scm_file_list.json +65 -0
  21. grobid_client_python-0.2.0/grobid_client_python.egg-info/scm_version.json +8 -0
  22. grobid_client_python-0.2.0/pyproject.toml +70 -0
  23. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/conftest.py +1 -1
  24. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/test_config.json +1 -1
  25. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/test_conversions.py +1 -1
  26. grobid_client_python-0.2.0/tests/test_grobid_client.py +1674 -0
  27. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/test_integration.py +6 -6
  28. grobid_client_python-0.2.0/tests/test_s3.py +180 -0
  29. grobid_client_python-0.1.4/MANIFEST.in +0 -1
  30. grobid_client_python-0.1.4/grobid_client/grobid_client.py +0 -998
  31. grobid_client_python-0.1.4/pyproject.toml +0 -41
  32. grobid_client_python-0.1.4/tests/test_grobid_client.py +0 -673
  33. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/.gitignore +0 -0
  34. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/LICENSE +0 -0
  35. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/example.py +0 -0
  36. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/__init__.py +0 -0
  37. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client/format/__init__.py +0 -0
  38. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/dependency_links.txt +0 -0
  39. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/entry_points.txt +0 -0
  40. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/top_level.txt +0 -0
  41. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/pytest.ini +0 -0
  42. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/requirements.txt +0 -0
  43. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/20180928112135.png +0 -0
  44. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/20180928112138.csv +0 -0
  45. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.pdf +0 -0
  46. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
  47. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_pdf/mjb3wlzxcb2mc-migowebupload-1766042162782.grobid.tei.xml +0 -0
  48. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-1.txt +0 -0
  49. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-2.txt +0 -0
  50. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-3.txt +0 -0
  51. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-4.txt +0 -0
  52. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-5.txt +0 -0
  53. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-6.txt +0 -0
  54. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-7.txt +0 -0
  55. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/setup.cfg +0 -0
  56. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/__init__.py +0 -0
  57. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
  58. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/__init__.py +0 -0
  59. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/article_withdrawn.grobid.tei.xml +0 -0
  60. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.grobid.tei.xml +0 -0
  61. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.json +0 -0
  62. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.grobid.tei.xml +0 -0
  63. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.json +0 -0
  64. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.grobid.tei.xml +0 -0
  65. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.json +0 -0
  66. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.grobid.tei.xml +0 -0
  67. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.json +0 -0
  68. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.7554_elife.78558.grobid.tei.xml +0 -0
  69. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.7554_elife.78558.json +0 -0
  70. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/2021.naacl-main.224.grobid.tei.xml +0 -0
  71. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.grobid.tei.xml +0 -0
  72. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.json +0 -0
  73. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.md +0 -0
  74. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/run_tests.py +0 -0
  75. {grobid_client_python-0.1.4 → grobid_client_python-0.2.0}/tests/test_client.py +0 -0
@@ -0,0 +1,9 @@
1
+ version: 2
2
+ updates:
3
+ # Keep GitHub Actions used in the workflows up to date
4
+ - package-ecosystem: "github-actions"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ commit-message:
9
+ prefix: "ci"
@@ -15,9 +15,9 @@ jobs:
15
15
  fail-fast: true
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v4
18
+ - uses: actions/checkout@v7
19
19
  - name: Set up Python ${{ matrix.python-version }}
20
- uses: actions/setup-python@v5
20
+ uses: actions/setup-python@v7
21
21
  with:
22
22
  python-version: ${{ matrix.python-version }}
23
23
  cache: 'pip'
@@ -30,7 +30,7 @@ jobs:
30
30
  - name: Install dependencies
31
31
  run: |
32
32
  python -m pip install --upgrade pip
33
- pip install --upgrade flake8 pytest pycodestyle pytest-cov pytest-mock
33
+ pip install --upgrade flake8 pytest pycodestyle pytest-cov pytest-mock "mypy==2.3.*"
34
34
  if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
35
35
  - name: Lint with flake8
36
36
  run: |
@@ -38,12 +38,16 @@ jobs:
38
38
  flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
39
39
  # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
40
40
  flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
41
+ - name: Type check with mypy
42
+ if: matrix.python-version == '3.11'
43
+ run: |
44
+ mypy grobid_client
41
45
  - name: Test with pytest
42
46
  run: |
43
47
  pytest tests/ -v --cov=grobid_client --cov-report=xml --cov-report=term-missing
44
48
  - name: Upload coverage to Codecov
45
49
  if: matrix.python-version == '3.11'
46
- uses: codecov/codecov-action@v4
50
+ uses: codecov/codecov-action@v7
47
51
  with:
48
52
  file: ./coverage.xml
49
53
  flags: unittests
@@ -14,9 +14,9 @@ jobs:
14
14
  build:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v7
18
18
  - name: Set up Python 3.9
19
- uses: actions/setup-python@v5
19
+ uses: actions/setup-python@v7
20
20
  with:
21
21
  python-version: "3.9"
22
22
  cache: 'pip'
@@ -0,0 +1,2 @@
1
+ include Readme.md
2
+ include grobid_client/py.typed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grobid-client-python
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: Simple python client for GROBID REST services
5
5
  Author-email: Patrice Lopez <patrice.lopez@science-miner.com>
6
6
  Maintainer-email: Patrice Lopez <patrice.lopez@science-miner.com>, Luca Foppiano <lucanoro@duck.com>
@@ -216,6 +216,9 @@ Requires-Dist: requests
216
216
  Requires-Dist: dateparser
217
217
  Requires-Dist: beautifulsoup4
218
218
  Requires-Dist: lxml
219
+ Provides-Extra: s3
220
+ Requires-Dist: smart_open[s3]>=6.0; extra == "s3"
221
+ Requires-Dist: boto3; extra == "s3"
219
222
 
220
223
  # GROBID Client Python
221
224
 
@@ -252,6 +255,10 @@ concurrent processing capabilities for PDF documents, reference strings, and pat
252
255
  - **Sentence Segmentation**: Layout-aware sentence segmentation capabilities
253
256
  - **JSON Output**: Convert TEI XML output to structured JSON format with CORD-19-like structure
254
257
  - **Markdown Output**: Convert TEI XML output to clean Markdown format with structured sections
258
+ - **Type Hints**: Ships inline type annotations and a `py.typed` marker (PEP 561) for static type checking
259
+ - **Archive Streaming**: Process files directly from `.zip`/`.tar`/`.tar.gz` archives without fully decompressing them
260
+ - **S3 Streaming**: Read PDFs and zips straight from `s3://` (range-streamed, no full download) with the optional `[s3]` extra
261
+ - **In-Memory Documents**: Send PDFs held as bytes straight to GROBID, without writing them to disk first
255
262
 
256
263
  ## 📋 Prerequisites
257
264
 
@@ -276,6 +283,9 @@ Choose one of the following installation methods:
276
283
 
277
284
  ```bash
278
285
  pip install grobid-client-python
286
+
287
+ # to stream inputs directly from S3 (s3:// URIs), install the optional 's3' extra:
288
+ pip install "grobid-client-python[s3]"
279
289
  ```
280
290
 
281
291
  ### Development Version
@@ -339,27 +349,28 @@ grobid_client [OPTIONS] SERVICE
339
349
 
340
350
  #### Common Options
341
351
 
342
- | Option | Description | Default |
343
- |-------------|--------------------------|-------------------------|
344
- | `--input` | Input directory path | Required |
345
- | `--output` | Output directory path | Same as input |
346
- | `--server` | GROBID server URL | `http://localhost:8070` |
347
- | `--n` | Concurrency level | 10 |
348
- | `--config` | Config file path | Optional |
349
- | `--force` | Overwrite existing files | False |
350
- | `--verbose` | Enable verbose logging | False |
352
+ | Option | Description | Default |
353
+ |-----------------|---------------------------------------------------|-------------------------|
354
+ | `--input` | Input directory path | Required |
355
+ | `--output` | Output directory path | Same as input |
356
+ | `--server` | GROBID server URL | `http://localhost:8070` |
357
+ | `--n` | Concurrency level | 10 |
358
+ | `--config` | Config file path | Optional |
359
+ | `--force` | Overwrite existing files | False |
360
+ | `--skip_errors` | Also skip documents that failed in a previous run | False |
361
+ | `--verbose` | Enable verbose logging | False |
351
362
 
352
363
  #### Processing Options
353
364
 
354
365
  | Option | Description |
355
366
  |------------------------------|-------------------------------------------|
356
- | `--generateIDs` | Generate random XML IDs |
367
+ | `--generate_ids` | Generate random XML IDs |
357
368
  | `--consolidate_header` | Consolidate header metadata |
358
369
  | `--consolidate_citations` | Consolidate bibliographic references |
359
370
  | `--include_raw_citations` | Include raw citation text |
360
371
  | `--include_raw_affiliations` | Include raw affiliation text |
361
- | `--teiCoordinates` | Add PDF coordinates to XML |
362
- | `--segmentSentences` | Segment sentences with coordinates |
372
+ | `--tei_coordinates` | Add PDF coordinates to XML |
373
+ | `--segment_sentences` | Segment sentences with coordinates |
363
374
  | `--flavor` | Processing flavor for fulltext extraction |
364
375
  | `--json` | Convert TEI output to JSON format |
365
376
  | `--markdown` | Convert TEI output to Markdown format |
@@ -372,7 +383,7 @@ grobid_client [OPTIONS] SERVICE
372
383
  grobid_client --input ~/documents --output ~/results processFulltextDocument
373
384
 
374
385
  # High concurrency with coordinates
375
- grobid_client --input ~/pdfs --output ~/tei --n 20 --teiCoordinates processFulltextDocument
386
+ grobid_client --input ~/pdfs --output ~/tei --n 20 --tei_coordinates processFulltextDocument
376
387
 
377
388
  # Process with JSON output
378
389
  grobid_client --input ~/pdfs --output ~/results --json processFulltextDocument
@@ -384,9 +395,50 @@ grobid_client --input ~/pdfs --output ~/results --markdown processFulltextDocume
384
395
  grobid_client --server https://grobid.example.com --input ~/citations.txt processCitationList
385
396
 
386
397
  # Force reprocessing with sentence segmentation and JSON output
387
- grobid_client --input ~/docs --force --segmentSentences --json processFulltextDocument
398
+ grobid_client --input ~/docs --force --segment_sentences --json processFulltextDocument
399
+
400
+ # Resume an interrupted run without retrying the documents that already failed
401
+ grobid_client --input ~/docs --output ~/results --skip_errors processFulltextDocument
402
+
403
+ # Process PDFs directly from a zip or tar.gz archive (streamed, not fully decompressed)
404
+ grobid_client --input ~/papers.zip --output ~/results processFulltextDocument
405
+ grobid_client --input ~/papers.tar.gz --output ~/results processFulltextDocument
406
+
407
+ # --input also accepts glob patterns (quote them so the shell does not expand them)
408
+ grobid_client --input "~/papers/*.zip" --output ~/results processFulltextDocument # many archives
409
+ grobid_client --input "~/data/**/*.pdf" --output ~/results processFulltextDocument # PDFs in subdirectories
388
410
  ```
389
411
 
412
+ > [!NOTE]
413
+ > `--input` accepts a directory, a single file, an **archive**, or a **glob pattern**:
414
+ > - **Archives** (`.zip`, `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`) are streamed: eligible entries are read into
415
+ > memory in chunks of `queue_size` and sent to GROBID straight from there — the archive is never fully decompressed and
416
+ > nothing but the results in `--output` ever touches the disk. If `--output` is omitted, results go to a directory named
417
+ > after the archive (e.g. `papers.zip` → `papers/`).
418
+ > - **Glob patterns** (`paper.zip`, `paper*.zip`, `**/paper*.zip`, `**/*.pdf`, …) are expanded with `**` recursion; each
419
+ > match is handled by type (archive → streamed, directory → recursed, file → processed). Quote the pattern so your shell
420
+ > passes it through to the client unexpanded.
421
+ > - **S3** (requires `pip install "grobid-client-python[s3]"`): pass an `s3://` object, prefix or glob. A remote zip is
422
+ > **range-streamed** (only its central directory and the entries are fetched — never the whole object); loose remote
423
+ > PDFs are fetched a batch at a time, directly into memory without being written to a local file first. Credentials use
424
+ > the standard AWS chain (env vars / `~/.aws` / IAM role).
425
+ > ```bash
426
+ > grobid_client --input "s3://my-bucket/papers/2021.zip" --output ~/out processFulltextDocument # one remote zip
427
+ > grobid_client --input "s3://my-bucket/pdfs/*.pdf" --output ~/out processFulltextDocument # loose PDFs
428
+ > grobid_client --input "s3://my-bucket/zips/" --output ~/out processFulltextDocument # every object under a prefix
429
+ > ```
430
+ >
431
+ > A **manifest of paths** (local, glob or `s3://`, one per line, `#` comments allowed) can be processed together via
432
+ > `--input-list paths.txt` (combinable with `--input`).
433
+
434
+ > [!NOTE]
435
+ > **Skipping already handled documents.** By default a re-run skips a document only when its TEI output already exists,
436
+ > so documents that failed are sent to GROBID again. Since a failed document generally fails again unless something
437
+ > changed, `--skip_errors` also skips the documents for which a previous run left an error file
438
+ > (`<name>_<status>.txt`, e.g. `paper_500.txt`) next to the expected TEI output. Drop the flag (or use `--force`) to
439
+ > retry them. Error files are kept in sync automatically: the marker is deleted once the document is processed
440
+ > successfully, and replaced when the same document fails again with a different status code.
441
+
390
442
  ### Python Library
391
443
 
392
444
  #### Basic Usage
@@ -421,10 +473,10 @@ client.process(
421
473
  input_path="/path/to/pdfs",
422
474
  output_path="/path/to/output",
423
475
  n=10,
424
- generateIDs=True,
476
+ generate_ids=True,
425
477
  consolidate_header=True,
426
- teiCoordinates=True,
427
- segmentSentences=True
478
+ tei_coordinates=True,
479
+ segment_sentences=True
428
480
  )
429
481
 
430
482
  # Process with JSON output
@@ -443,6 +495,15 @@ client.process(
443
495
  markdown_output=True
444
496
  )
445
497
 
498
+ # Re-run without retrying the documents that failed before
499
+ client.process(
500
+ service="processFulltextDocument",
501
+ input_path="/path/to/pdfs",
502
+ output_path="/path/to/output",
503
+ force=False,
504
+ skip_errors=True
505
+ )
506
+
446
507
  ```python
447
508
  # Process citation lists
448
509
  client.process(
@@ -452,6 +513,63 @@ client.process(
452
513
  )
453
514
  ```
454
515
 
516
+ #### Processing a PDF from memory
517
+
518
+ A PDF that is already in memory - downloaded from an API, read from a database or an object store - can be sent
519
+ directly, without writing it to a temporary file first. `process_pdf` takes either a path or the document itself, as
520
+ `bytes` or as any binary stream, and returns the TEI as a string:
521
+
522
+ ```python
523
+ import io
524
+ import requests
525
+
526
+ pdf = io.BytesIO(requests.get("https://example.org/paper.pdf").content)
527
+ pdf.name = "paper.pdf" # optional, see below
528
+
529
+ name, status, tei = client.process_pdf(
530
+ service="processFulltextDocument",
531
+ pdf_file=pdf,
532
+ consolidate_header=True,
533
+ tei_coordinates=True
534
+ )
535
+
536
+ if status == 200:
537
+ print(tei)
538
+ ```
539
+
540
+ There is no flag to say where the document comes from: the object itself says it. A document also carries its own name,
541
+ taken from the `name` attribute that `open()` sets on files and that can be set on anything else, `io.BytesIO` included.
542
+ The name identifies the document in the request sent to GROBID, in the logs, and as the first element of the result, so
543
+ documents processed this way stay distinguishable. Bytes passed on their own have nothing to be named after and fall
544
+ back to `document.pdf`.
545
+
546
+ Several documents can be sent concurrently with `process_documents`, which runs them through the same thread pool the
547
+ file-based processing uses:
548
+
549
+ ```python
550
+ results = client.process_documents(
551
+ service="processFulltextDocument",
552
+ documents=[pdf1, pdf2, "/path/to/paper3.pdf"],
553
+ n=10 # documents sent concurrently
554
+ )
555
+
556
+ for name, status, tei in results:
557
+ ...
558
+ ```
559
+
560
+ Documents that do not name themselves are named `document-1.pdf`, `document-2.pdf`, ... after their position. Results
561
+ come back **in the order the documents were given**, not in completion order, so they can be zipped back onto whatever
562
+ the caller has them keyed by. A document that fails does not stop the others: its own entry carries the error status.
563
+
564
+ Before an in-memory run starts (this includes archive and `s3://` streaming), the client asks the server's `/api/health`
565
+ how many engines it actually has and logs a warning when the requested concurrency `n` exceeds them - the surplus
566
+ requests would only queue on the server or bounce as 503 - and an info message when engines would sit idle. The check is
567
+ advisory: a server without the endpoint (older GROBID) never blocks the run.
568
+
569
+ > [!NOTE]
570
+ > Both return the TEI instead of writing it to disk, so the caller decides what to do with it. Use `process()` for the
571
+ > directory-oriented processing with resume and JSON/Markdown conversion.
572
+
455
573
  ### Standalone Conversion Tools
456
574
 
457
575
  The library includes standalone scripts to convert TEI XML files to other formats without using the main client or server.
@@ -488,7 +606,6 @@ settings.
488
606
  ```json
489
607
  {
490
608
  "grobid_server": "http://localhost:8070",
491
- "batch_size": 1000,
492
609
  "sleep_time": 5,
493
610
  "timeout": 60,
494
611
  "coordinates": [
@@ -507,7 +624,7 @@ settings.
507
624
  | Parameter | Description | Default |
508
625
  |-----------------|------------------------------------------------------------------------------------------------------------------|-------------------------|
509
626
  | `grobid_server` | GROBID server URL | `http://localhost:8070` |
510
- | `batch_size` | Thread pool size. **Tune carefully: a large batch size will result in the data being written less frequently** | 1000 |
627
+ | `queue_size` | Number of files queued per processing chunk. See [Choosing a queue size](#choosing-a-queue-size). | 1000 for local directories, 1.2 × `n` for archives and S3 |
511
628
  | `sleep_time` | Wait time when server is busy (seconds) | 5 |
512
629
  | `timeout` | Client-side timeout (seconds) | 180 |
513
630
  | `coordinates` | XML elements for coordinate extraction | See above |
@@ -517,6 +634,32 @@ settings.
517
634
  > Since version 0.0.12, the config file is optional. The client will use default localhost settings if no configuration
518
635
  > is provided.
519
636
 
637
+ ### Choosing a queue size
638
+
639
+ `queue_size` controls how many files are grouped into one processing chunk. It is a memory/durability knob, not a
640
+ concurrency one: parallelism toward the GROBID server is set by `-n`, and each chunk is processed `n` files at a time.
641
+ When `queue_size` is not set, the client picks a sensible default per input type (see the table above); set it
642
+ explicitly only if you need to override that. A few guidelines:
643
+
644
+ - **Never set it below `n`.** Effective parallelism is `min(n, queue_size)`: a queue smaller than the thread pool
645
+ leaves workers idle. A bit of headroom above `n` (the default streaming value is 1.2 × `n`) keeps the pool busy.
646
+ - **Results are written per chunk.** Output files land on disk only once a whole chunk has been processed, so a
647
+ larger queue means results are written less frequently and an interrupted run loses at most one chunk of work
648
+ (already-written results are skipped on re-run unless `--force` is used).
649
+ - **Local PDF directories:** the queue holds only file paths, so large values are essentially free — the default is
650
+ 1000. Lower it if you want results flushed to disk more often on long runs.
651
+ - **Archives (zip/tar) and S3:** each chunk is read or downloaded *into memory* before processing starts, so peak RAM
652
+ grows with `queue_size × average file size`. Keep it moderate — the 1.2 × `n` default is a safe floor; going up to
653
+ a few multiples of `n` (e.g. 2–5 ×) trades memory for slightly better throughput around chunk boundaries.
654
+
655
+ > [!WARNING]
656
+ > **Citation consolidation and the `timeout` setting.** When `--consolidate_citations` (or `consolidate_citations=True`)
657
+ > is enabled, GROBID queries external services (e.g. CrossRef) to enrich the extracted references. This is considerably
658
+ > slower than a plain extraction, and a low `timeout` frequently causes `HTTP 408 (Request Timeout)` errors.
659
+ > Set the `timeout` to at least **120 seconds (2-3 minutes recommended)** when consolidating citations. The client emits
660
+ > a warning when consolidation is requested with a timeout below 120 seconds.
661
+ > See [issue #54](https://github.com/grobidOrg/grobid-client-python/issues/54).
662
+
520
663
  ### Logging Configuration
521
664
 
522
665
  The client provides configurable logging with different verbosity levels. By default, only essential statistics and warnings are shown.
@@ -673,7 +816,7 @@ When using the `--json` flag, the client converts TEI XML output to a structured
673
816
  grobid_client --input pdfs/ --output results/ --json processFulltextDocument
674
817
 
675
818
  # JSON output with coordinates and sentence segmentation
676
- grobid_client --input pdfs/ --output results/ --json --teiCoordinates --segmentSentences processFulltextDocument
819
+ grobid_client --input pdfs/ --output results/ --json --tei_coordinates --segment_sentences processFulltextDocument
677
820
  ```
678
821
 
679
822
  ```python
@@ -754,7 +897,7 @@ Competing interests statement...
754
897
  grobid_client --input pdfs/ --output results/ --markdown processFulltextDocument
755
898
 
756
899
  # Markdown output with coordinates and sentence segmentation
757
- grobid_client --input pdfs/ --output results/ --markdown --teiCoordinates --segmentSentences processFulltextDocument
900
+ grobid_client --input pdfs/ --output results/ --markdown --tei_coordinates --segment_sentences processFulltextDocument
758
901
  ```
759
902
 
760
903
  ```python
@@ -33,6 +33,10 @@ concurrent processing capabilities for PDF documents, reference strings, and pat
33
33
  - **Sentence Segmentation**: Layout-aware sentence segmentation capabilities
34
34
  - **JSON Output**: Convert TEI XML output to structured JSON format with CORD-19-like structure
35
35
  - **Markdown Output**: Convert TEI XML output to clean Markdown format with structured sections
36
+ - **Type Hints**: Ships inline type annotations and a `py.typed` marker (PEP 561) for static type checking
37
+ - **Archive Streaming**: Process files directly from `.zip`/`.tar`/`.tar.gz` archives without fully decompressing them
38
+ - **S3 Streaming**: Read PDFs and zips straight from `s3://` (range-streamed, no full download) with the optional `[s3]` extra
39
+ - **In-Memory Documents**: Send PDFs held as bytes straight to GROBID, without writing them to disk first
36
40
 
37
41
  ## 📋 Prerequisites
38
42
 
@@ -57,6 +61,9 @@ Choose one of the following installation methods:
57
61
 
58
62
  ```bash
59
63
  pip install grobid-client-python
64
+
65
+ # to stream inputs directly from S3 (s3:// URIs), install the optional 's3' extra:
66
+ pip install "grobid-client-python[s3]"
60
67
  ```
61
68
 
62
69
  ### Development Version
@@ -120,27 +127,28 @@ grobid_client [OPTIONS] SERVICE
120
127
 
121
128
  #### Common Options
122
129
 
123
- | Option | Description | Default |
124
- |-------------|--------------------------|-------------------------|
125
- | `--input` | Input directory path | Required |
126
- | `--output` | Output directory path | Same as input |
127
- | `--server` | GROBID server URL | `http://localhost:8070` |
128
- | `--n` | Concurrency level | 10 |
129
- | `--config` | Config file path | Optional |
130
- | `--force` | Overwrite existing files | False |
131
- | `--verbose` | Enable verbose logging | False |
130
+ | Option | Description | Default |
131
+ |-----------------|---------------------------------------------------|-------------------------|
132
+ | `--input` | Input directory path | Required |
133
+ | `--output` | Output directory path | Same as input |
134
+ | `--server` | GROBID server URL | `http://localhost:8070` |
135
+ | `--n` | Concurrency level | 10 |
136
+ | `--config` | Config file path | Optional |
137
+ | `--force` | Overwrite existing files | False |
138
+ | `--skip_errors` | Also skip documents that failed in a previous run | False |
139
+ | `--verbose` | Enable verbose logging | False |
132
140
 
133
141
  #### Processing Options
134
142
 
135
143
  | Option | Description |
136
144
  |------------------------------|-------------------------------------------|
137
- | `--generateIDs` | Generate random XML IDs |
145
+ | `--generate_ids` | Generate random XML IDs |
138
146
  | `--consolidate_header` | Consolidate header metadata |
139
147
  | `--consolidate_citations` | Consolidate bibliographic references |
140
148
  | `--include_raw_citations` | Include raw citation text |
141
149
  | `--include_raw_affiliations` | Include raw affiliation text |
142
- | `--teiCoordinates` | Add PDF coordinates to XML |
143
- | `--segmentSentences` | Segment sentences with coordinates |
150
+ | `--tei_coordinates` | Add PDF coordinates to XML |
151
+ | `--segment_sentences` | Segment sentences with coordinates |
144
152
  | `--flavor` | Processing flavor for fulltext extraction |
145
153
  | `--json` | Convert TEI output to JSON format |
146
154
  | `--markdown` | Convert TEI output to Markdown format |
@@ -153,7 +161,7 @@ grobid_client [OPTIONS] SERVICE
153
161
  grobid_client --input ~/documents --output ~/results processFulltextDocument
154
162
 
155
163
  # High concurrency with coordinates
156
- grobid_client --input ~/pdfs --output ~/tei --n 20 --teiCoordinates processFulltextDocument
164
+ grobid_client --input ~/pdfs --output ~/tei --n 20 --tei_coordinates processFulltextDocument
157
165
 
158
166
  # Process with JSON output
159
167
  grobid_client --input ~/pdfs --output ~/results --json processFulltextDocument
@@ -165,9 +173,50 @@ grobid_client --input ~/pdfs --output ~/results --markdown processFulltextDocume
165
173
  grobid_client --server https://grobid.example.com --input ~/citations.txt processCitationList
166
174
 
167
175
  # Force reprocessing with sentence segmentation and JSON output
168
- grobid_client --input ~/docs --force --segmentSentences --json processFulltextDocument
176
+ grobid_client --input ~/docs --force --segment_sentences --json processFulltextDocument
177
+
178
+ # Resume an interrupted run without retrying the documents that already failed
179
+ grobid_client --input ~/docs --output ~/results --skip_errors processFulltextDocument
180
+
181
+ # Process PDFs directly from a zip or tar.gz archive (streamed, not fully decompressed)
182
+ grobid_client --input ~/papers.zip --output ~/results processFulltextDocument
183
+ grobid_client --input ~/papers.tar.gz --output ~/results processFulltextDocument
184
+
185
+ # --input also accepts glob patterns (quote them so the shell does not expand them)
186
+ grobid_client --input "~/papers/*.zip" --output ~/results processFulltextDocument # many archives
187
+ grobid_client --input "~/data/**/*.pdf" --output ~/results processFulltextDocument # PDFs in subdirectories
169
188
  ```
170
189
 
190
+ > [!NOTE]
191
+ > `--input` accepts a directory, a single file, an **archive**, or a **glob pattern**:
192
+ > - **Archives** (`.zip`, `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`) are streamed: eligible entries are read into
193
+ > memory in chunks of `queue_size` and sent to GROBID straight from there — the archive is never fully decompressed and
194
+ > nothing but the results in `--output` ever touches the disk. If `--output` is omitted, results go to a directory named
195
+ > after the archive (e.g. `papers.zip` → `papers/`).
196
+ > - **Glob patterns** (`paper.zip`, `paper*.zip`, `**/paper*.zip`, `**/*.pdf`, …) are expanded with `**` recursion; each
197
+ > match is handled by type (archive → streamed, directory → recursed, file → processed). Quote the pattern so your shell
198
+ > passes it through to the client unexpanded.
199
+ > - **S3** (requires `pip install "grobid-client-python[s3]"`): pass an `s3://` object, prefix or glob. A remote zip is
200
+ > **range-streamed** (only its central directory and the entries are fetched — never the whole object); loose remote
201
+ > PDFs are fetched a batch at a time, directly into memory without being written to a local file first. Credentials use
202
+ > the standard AWS chain (env vars / `~/.aws` / IAM role).
203
+ > ```bash
204
+ > grobid_client --input "s3://my-bucket/papers/2021.zip" --output ~/out processFulltextDocument # one remote zip
205
+ > grobid_client --input "s3://my-bucket/pdfs/*.pdf" --output ~/out processFulltextDocument # loose PDFs
206
+ > grobid_client --input "s3://my-bucket/zips/" --output ~/out processFulltextDocument # every object under a prefix
207
+ > ```
208
+ >
209
+ > A **manifest of paths** (local, glob or `s3://`, one per line, `#` comments allowed) can be processed together via
210
+ > `--input-list paths.txt` (combinable with `--input`).
211
+
212
+ > [!NOTE]
213
+ > **Skipping already handled documents.** By default a re-run skips a document only when its TEI output already exists,
214
+ > so documents that failed are sent to GROBID again. Since a failed document generally fails again unless something
215
+ > changed, `--skip_errors` also skips the documents for which a previous run left an error file
216
+ > (`<name>_<status>.txt`, e.g. `paper_500.txt`) next to the expected TEI output. Drop the flag (or use `--force`) to
217
+ > retry them. Error files are kept in sync automatically: the marker is deleted once the document is processed
218
+ > successfully, and replaced when the same document fails again with a different status code.
219
+
171
220
  ### Python Library
172
221
 
173
222
  #### Basic Usage
@@ -202,10 +251,10 @@ client.process(
202
251
  input_path="/path/to/pdfs",
203
252
  output_path="/path/to/output",
204
253
  n=10,
205
- generateIDs=True,
254
+ generate_ids=True,
206
255
  consolidate_header=True,
207
- teiCoordinates=True,
208
- segmentSentences=True
256
+ tei_coordinates=True,
257
+ segment_sentences=True
209
258
  )
210
259
 
211
260
  # Process with JSON output
@@ -224,6 +273,15 @@ client.process(
224
273
  markdown_output=True
225
274
  )
226
275
 
276
+ # Re-run without retrying the documents that failed before
277
+ client.process(
278
+ service="processFulltextDocument",
279
+ input_path="/path/to/pdfs",
280
+ output_path="/path/to/output",
281
+ force=False,
282
+ skip_errors=True
283
+ )
284
+
227
285
  ```python
228
286
  # Process citation lists
229
287
  client.process(
@@ -233,6 +291,63 @@ client.process(
233
291
  )
234
292
  ```
235
293
 
294
+ #### Processing a PDF from memory
295
+
296
+ A PDF that is already in memory - downloaded from an API, read from a database or an object store - can be sent
297
+ directly, without writing it to a temporary file first. `process_pdf` takes either a path or the document itself, as
298
+ `bytes` or as any binary stream, and returns the TEI as a string:
299
+
300
+ ```python
301
+ import io
302
+ import requests
303
+
304
+ pdf = io.BytesIO(requests.get("https://example.org/paper.pdf").content)
305
+ pdf.name = "paper.pdf" # optional, see below
306
+
307
+ name, status, tei = client.process_pdf(
308
+ service="processFulltextDocument",
309
+ pdf_file=pdf,
310
+ consolidate_header=True,
311
+ tei_coordinates=True
312
+ )
313
+
314
+ if status == 200:
315
+ print(tei)
316
+ ```
317
+
318
+ There is no flag to say where the document comes from: the object itself says it. A document also carries its own name,
319
+ taken from the `name` attribute that `open()` sets on files and that can be set on anything else, `io.BytesIO` included.
320
+ The name identifies the document in the request sent to GROBID, in the logs, and as the first element of the result, so
321
+ documents processed this way stay distinguishable. Bytes passed on their own have nothing to be named after and fall
322
+ back to `document.pdf`.
323
+
324
+ Several documents can be sent concurrently with `process_documents`, which runs them through the same thread pool the
325
+ file-based processing uses:
326
+
327
+ ```python
328
+ results = client.process_documents(
329
+ service="processFulltextDocument",
330
+ documents=[pdf1, pdf2, "/path/to/paper3.pdf"],
331
+ n=10 # documents sent concurrently
332
+ )
333
+
334
+ for name, status, tei in results:
335
+ ...
336
+ ```
337
+
338
+ Documents that do not name themselves are named `document-1.pdf`, `document-2.pdf`, ... after their position. Results
339
+ come back **in the order the documents were given**, not in completion order, so they can be zipped back onto whatever
340
+ the caller has them keyed by. A document that fails does not stop the others: its own entry carries the error status.
341
+
342
+ Before an in-memory run starts (this includes archive and `s3://` streaming), the client asks the server's `/api/health`
343
+ how many engines it actually has and logs a warning when the requested concurrency `n` exceeds them - the surplus
344
+ requests would only queue on the server or bounce as 503 - and an info message when engines would sit idle. The check is
345
+ advisory: a server without the endpoint (older GROBID) never blocks the run.
346
+
347
+ > [!NOTE]
348
+ > Both return the TEI instead of writing it to disk, so the caller decides what to do with it. Use `process()` for the
349
+ > directory-oriented processing with resume and JSON/Markdown conversion.
350
+
236
351
  ### Standalone Conversion Tools
237
352
 
238
353
  The library includes standalone scripts to convert TEI XML files to other formats without using the main client or server.
@@ -269,7 +384,6 @@ settings.
269
384
  ```json
270
385
  {
271
386
  "grobid_server": "http://localhost:8070",
272
- "batch_size": 1000,
273
387
  "sleep_time": 5,
274
388
  "timeout": 60,
275
389
  "coordinates": [
@@ -288,7 +402,7 @@ settings.
288
402
  | Parameter | Description | Default |
289
403
  |-----------------|------------------------------------------------------------------------------------------------------------------|-------------------------|
290
404
  | `grobid_server` | GROBID server URL | `http://localhost:8070` |
291
- | `batch_size` | Thread pool size. **Tune carefully: a large batch size will result in the data being written less frequently** | 1000 |
405
+ | `queue_size` | Number of files queued per processing chunk. See [Choosing a queue size](#choosing-a-queue-size). | 1000 for local directories, 1.2 × `n` for archives and S3 |
292
406
  | `sleep_time` | Wait time when server is busy (seconds) | 5 |
293
407
  | `timeout` | Client-side timeout (seconds) | 180 |
294
408
  | `coordinates` | XML elements for coordinate extraction | See above |
@@ -298,6 +412,32 @@ settings.
298
412
  > Since version 0.0.12, the config file is optional. The client will use default localhost settings if no configuration
299
413
  > is provided.
300
414
 
415
+ ### Choosing a queue size
416
+
417
+ `queue_size` controls how many files are grouped into one processing chunk. It is a memory/durability knob, not a
418
+ concurrency one: parallelism toward the GROBID server is set by `-n`, and each chunk is processed `n` files at a time.
419
+ When `queue_size` is not set, the client picks a sensible default per input type (see the table above); set it
420
+ explicitly only if you need to override that. A few guidelines:
421
+
422
+ - **Never set it below `n`.** Effective parallelism is `min(n, queue_size)`: a queue smaller than the thread pool
423
+ leaves workers idle. A bit of headroom above `n` (the default streaming value is 1.2 × `n`) keeps the pool busy.
424
+ - **Results are written per chunk.** Output files land on disk only once a whole chunk has been processed, so a
425
+ larger queue means results are written less frequently and an interrupted run loses at most one chunk of work
426
+ (already-written results are skipped on re-run unless `--force` is used).
427
+ - **Local PDF directories:** the queue holds only file paths, so large values are essentially free — the default is
428
+ 1000. Lower it if you want results flushed to disk more often on long runs.
429
+ - **Archives (zip/tar) and S3:** each chunk is read or downloaded *into memory* before processing starts, so peak RAM
430
+ grows with `queue_size × average file size`. Keep it moderate — the 1.2 × `n` default is a safe floor; going up to
431
+ a few multiples of `n` (e.g. 2–5 ×) trades memory for slightly better throughput around chunk boundaries.
432
+
433
+ > [!WARNING]
434
+ > **Citation consolidation and the `timeout` setting.** When `--consolidate_citations` (or `consolidate_citations=True`)
435
+ > is enabled, GROBID queries external services (e.g. CrossRef) to enrich the extracted references. This is considerably
436
+ > slower than a plain extraction, and a low `timeout` frequently causes `HTTP 408 (Request Timeout)` errors.
437
+ > Set the `timeout` to at least **120 seconds (2-3 minutes recommended)** when consolidating citations. The client emits
438
+ > a warning when consolidation is requested with a timeout below 120 seconds.
439
+ > See [issue #54](https://github.com/grobidOrg/grobid-client-python/issues/54).
440
+
301
441
  ### Logging Configuration
302
442
 
303
443
  The client provides configurable logging with different verbosity levels. By default, only essential statistics and warnings are shown.
@@ -454,7 +594,7 @@ When using the `--json` flag, the client converts TEI XML output to a structured
454
594
  grobid_client --input pdfs/ --output results/ --json processFulltextDocument
455
595
 
456
596
  # JSON output with coordinates and sentence segmentation
457
- grobid_client --input pdfs/ --output results/ --json --teiCoordinates --segmentSentences processFulltextDocument
597
+ grobid_client --input pdfs/ --output results/ --json --tei_coordinates --segment_sentences processFulltextDocument
458
598
  ```
459
599
 
460
600
  ```python
@@ -535,7 +675,7 @@ Competing interests statement...
535
675
  grobid_client --input pdfs/ --output results/ --markdown processFulltextDocument
536
676
 
537
677
  # Markdown output with coordinates and sentence segmentation
538
- grobid_client --input pdfs/ --output results/ --markdown --teiCoordinates --segmentSentences processFulltextDocument
678
+ grobid_client --input pdfs/ --output results/ --markdown --tei_coordinates --segment_sentences processFulltextDocument
539
679
  ```
540
680
 
541
681
  ```python
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "grobid_server": "http://localhost:8070",
3
- "batch_size": 1000,
4
3
  "timeout": 180,
5
4
  "sleep_time": 5,
6
5
  "coordinates": [