grobid-client-python 0.1.5__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.
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/.github/workflows/ci-build.yml +5 -1
- grobid_client_python-0.2.0/MANIFEST.in +2 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/PKG-INFO +147 -12
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/Readme.md +143 -11
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/config.json +0 -1
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/client.py +44 -21
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/TEI2LossyJSON.py +129 -90
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/TEI2LossyJSON_cli.py +4 -2
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/TEI2Markdown.py +19 -17
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/TEI2Markdown_cli.py +4 -2
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/__main__.py +3 -1
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/validate_json_refs.py +3 -2
- grobid_client_python-0.2.0/grobid_client/grobid_client.py +2203 -0
- grobid_client_python-0.2.0/grobid_client/py.typed +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/PKG-INFO +147 -12
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/SOURCES.txt +2 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/requires.txt +4 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/scm_file_list.json +41 -39
- grobid_client_python-0.2.0/grobid_client_python.egg-info/scm_version.json +8 -0
- grobid_client_python-0.2.0/pyproject.toml +70 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/conftest.py +1 -1
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/test_config.json +1 -1
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/test_conversions.py +1 -1
- grobid_client_python-0.2.0/tests/test_grobid_client.py +1674 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/test_integration.py +4 -4
- grobid_client_python-0.2.0/tests/test_s3.py +180 -0
- grobid_client_python-0.1.5/MANIFEST.in +0 -1
- grobid_client_python-0.1.5/grobid_client/grobid_client.py +0 -1034
- grobid_client_python-0.1.5/grobid_client_python.egg-info/scm_version.json +0 -8
- grobid_client_python-0.1.5/pyproject.toml +0 -41
- grobid_client_python-0.1.5/tests/test_grobid_client.py +0 -673
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/.github/dependabot.yml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/.github/workflows/ci-release.yml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/.gitignore +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/LICENSE +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/example.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/__init__.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client/format/__init__.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/dependency_links.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/entry_points.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/grobid_client_python.egg-info/top_level.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/pytest.ini +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/requirements.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/20180928112135.png +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/20180928112138.csv +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.pdf +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_pdf/mjb3wlzxcb2mc-migowebupload-1766042162782.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-1.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-2.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-3.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-4.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-5.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-6.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/resources/test_txt/reference-strings-7.txt +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/setup.cfg +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/__init__.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/__init__.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/article_withdrawn.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.7554_elife.78558.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/10.7554_elife.78558.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/refs_offsets/2021.naacl-main.224.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.grobid.tei.xml +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.json +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/resources/repeated_text/ijms-24-05988.md +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/run_tests.py +0 -0
- {grobid_client_python-0.1.5 → grobid_client_python-0.2.0}/tests/test_client.py +0 -0
|
@@ -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,6 +38,10 @@ 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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grobid-client-python
|
|
3
|
-
Version: 0.
|
|
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,15 +349,16 @@ grobid_client [OPTIONS] SERVICE
|
|
|
339
349
|
|
|
340
350
|
#### Common Options
|
|
341
351
|
|
|
342
|
-
| Option
|
|
343
|
-
|
|
344
|
-
| `--input`
|
|
345
|
-
| `--output`
|
|
346
|
-
| `--server`
|
|
347
|
-
| `--n`
|
|
348
|
-
| `--config`
|
|
349
|
-
| `--force`
|
|
350
|
-
| `--
|
|
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
|
|
|
@@ -385,8 +396,49 @@ grobid_client --server https://grobid.example.com --input ~/citations.txt proces
|
|
|
385
396
|
|
|
386
397
|
# Force reprocessing with sentence segmentation and JSON output
|
|
387
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
|
|
@@ -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
|
-
| `
|
|
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,24 @@ 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
|
+
|
|
520
655
|
> [!WARNING]
|
|
521
656
|
> **Citation consolidation and the `timeout` setting.** When `--consolidate_citations` (or `consolidate_citations=True`)
|
|
522
657
|
> is enabled, GROBID queries external services (e.g. CrossRef) to enrich the extracted references. This is considerably
|
|
@@ -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,15 +127,16 @@ grobid_client [OPTIONS] SERVICE
|
|
|
120
127
|
|
|
121
128
|
#### Common Options
|
|
122
129
|
|
|
123
|
-
| Option
|
|
124
|
-
|
|
125
|
-
| `--input`
|
|
126
|
-
| `--output`
|
|
127
|
-
| `--server`
|
|
128
|
-
| `--n`
|
|
129
|
-
| `--config`
|
|
130
|
-
| `--force`
|
|
131
|
-
| `--
|
|
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
|
|
|
@@ -166,8 +174,49 @@ grobid_client --server https://grobid.example.com --input ~/citations.txt proces
|
|
|
166
174
|
|
|
167
175
|
# Force reprocessing with sentence segmentation and JSON output
|
|
168
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
|
|
@@ -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
|
-
| `
|
|
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,24 @@ 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
|
+
|
|
301
433
|
> [!WARNING]
|
|
302
434
|
> **Citation consolidation and the `timeout` setting.** When `--consolidate_citations` (or `consolidate_citations=True`)
|
|
303
435
|
> is enabled, GROBID queries external services (e.g. CrossRef) to enrich the extracted references. This is considerably
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
""" Generic API Client """
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
from copy import deepcopy
|
|
3
5
|
import json
|
|
6
|
+
from typing import Any, Optional, Tuple
|
|
7
|
+
|
|
4
8
|
import requests
|
|
5
9
|
|
|
6
10
|
try:
|
|
@@ -16,12 +20,17 @@ class ApiClient(object):
|
|
|
16
20
|
service methods, i.e. ``get``, ``post``, ``put`` and ``delete``.
|
|
17
21
|
"""
|
|
18
22
|
|
|
19
|
-
accept_type = "application/xml"
|
|
20
|
-
api_base = None
|
|
23
|
+
accept_type: str = "application/xml"
|
|
24
|
+
api_base: Optional[str] = None
|
|
21
25
|
|
|
22
26
|
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
|
|
27
|
+
self,
|
|
28
|
+
base_url: str,
|
|
29
|
+
username: Optional[str] = None,
|
|
30
|
+
api_key: Optional[str] = None,
|
|
31
|
+
status_endpoint: Optional[str] = None,
|
|
32
|
+
timeout: int = 60,
|
|
33
|
+
) -> None:
|
|
25
34
|
"""Initialise client.
|
|
26
35
|
|
|
27
36
|
Args:
|
|
@@ -37,7 +46,7 @@ class ApiClient(object):
|
|
|
37
46
|
self.timeout = timeout
|
|
38
47
|
|
|
39
48
|
@staticmethod
|
|
40
|
-
def encode(request, data):
|
|
49
|
+
def encode(request: Any, data: Optional[dict]) -> Any:
|
|
41
50
|
"""Add request content data to request body, set Content-type header.
|
|
42
51
|
|
|
43
52
|
Should be overridden by subclasses if not using JSON encoding.
|
|
@@ -58,7 +67,7 @@ class ApiClient(object):
|
|
|
58
67
|
return request
|
|
59
68
|
|
|
60
69
|
@staticmethod
|
|
61
|
-
def decode(response):
|
|
70
|
+
def decode(response: Any) -> Any:
|
|
62
71
|
"""Decode the returned data in the response.
|
|
63
72
|
|
|
64
73
|
Should be overridden by subclasses if something else than JSON is
|
|
@@ -73,9 +82,9 @@ class ApiClient(object):
|
|
|
73
82
|
try:
|
|
74
83
|
return response.json()
|
|
75
84
|
except ValueError as e:
|
|
76
|
-
return e.message
|
|
85
|
+
return e.message # type: ignore[attr-defined] # pre-existing (Python 2 style)
|
|
77
86
|
|
|
78
|
-
def get_credentials(self):
|
|
87
|
+
def get_credentials(self) -> dict:
|
|
79
88
|
"""Returns parameters to be added to authenticate the request.
|
|
80
89
|
|
|
81
90
|
This lives on its own to make it easier to re-implement it if needed.
|
|
@@ -87,14 +96,14 @@ class ApiClient(object):
|
|
|
87
96
|
|
|
88
97
|
def call_api(
|
|
89
98
|
self,
|
|
90
|
-
method,
|
|
91
|
-
url,
|
|
92
|
-
headers=None,
|
|
93
|
-
params=None,
|
|
94
|
-
data=None,
|
|
95
|
-
files=None,
|
|
96
|
-
timeout=None,
|
|
97
|
-
):
|
|
99
|
+
method: str,
|
|
100
|
+
url: str,
|
|
101
|
+
headers: Optional[dict] = None,
|
|
102
|
+
params: Optional[dict] = None,
|
|
103
|
+
data: Optional[dict] = None,
|
|
104
|
+
files: Optional[dict] = None,
|
|
105
|
+
timeout: Optional[int] = None,
|
|
106
|
+
) -> Tuple[requests.Response, int]:
|
|
98
107
|
"""Call API.
|
|
99
108
|
|
|
100
109
|
This returns object containing data, with error details if applicable.
|
|
@@ -130,7 +139,7 @@ class ApiClient(object):
|
|
|
130
139
|
|
|
131
140
|
return r, r.status_code
|
|
132
141
|
|
|
133
|
-
def get(self, url, params=None, **kwargs):
|
|
142
|
+
def get(self, url: str, params: Optional[dict] = None, **kwargs: Any) -> Tuple[requests.Response, int]:
|
|
134
143
|
"""Call the API with a GET request.
|
|
135
144
|
|
|
136
145
|
Args:
|
|
@@ -142,7 +151,7 @@ class ApiClient(object):
|
|
|
142
151
|
"""
|
|
143
152
|
return self.call_api("GET", url, params=params, **kwargs)
|
|
144
153
|
|
|
145
|
-
def delete(self, url, params=None, **kwargs):
|
|
154
|
+
def delete(self, url: str, params: Optional[dict] = None, **kwargs: Any) -> Tuple[requests.Response, int]:
|
|
146
155
|
"""Call the API with a DELETE request.
|
|
147
156
|
|
|
148
157
|
Args:
|
|
@@ -154,7 +163,14 @@ class ApiClient(object):
|
|
|
154
163
|
"""
|
|
155
164
|
return self.call_api("DELETE", url, params=params, **kwargs)
|
|
156
165
|
|
|
157
|
-
def put(
|
|
166
|
+
def put(
|
|
167
|
+
self,
|
|
168
|
+
url: str,
|
|
169
|
+
params: Optional[dict] = None,
|
|
170
|
+
data: Optional[dict] = None,
|
|
171
|
+
files: Optional[dict] = None,
|
|
172
|
+
**kwargs: Any,
|
|
173
|
+
) -> Tuple[requests.Response, int]:
|
|
158
174
|
"""Call the API with a PUT request.
|
|
159
175
|
|
|
160
176
|
Args:
|
|
@@ -170,7 +186,14 @@ class ApiClient(object):
|
|
|
170
186
|
"PUT", url, params=params, data=data, files=files, **kwargs
|
|
171
187
|
)
|
|
172
188
|
|
|
173
|
-
def post(
|
|
189
|
+
def post(
|
|
190
|
+
self,
|
|
191
|
+
url: str,
|
|
192
|
+
params: Optional[dict] = None,
|
|
193
|
+
data: Optional[dict] = None,
|
|
194
|
+
files: Optional[dict] = None,
|
|
195
|
+
**kwargs: Any,
|
|
196
|
+
) -> Tuple[requests.Response, int]:
|
|
174
197
|
"""Call the API with a POST request.
|
|
175
198
|
|
|
176
199
|
Args:
|
|
@@ -186,7 +209,7 @@ class ApiClient(object):
|
|
|
186
209
|
method="POST", url=url, params=params, data=data, files=files, **kwargs
|
|
187
210
|
)
|
|
188
211
|
|
|
189
|
-
def service_status(self, **kwargs):
|
|
212
|
+
def service_status(self, **kwargs: Any) -> Tuple[requests.Response, int]:
|
|
190
213
|
"""Call the API to get the status of the service.
|
|
191
214
|
|
|
192
215
|
Returns:
|