grobid-client-python 0.2.0__tar.gz → 0.2.2__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.2.0 → grobid_client_python-0.2.2}/PKG-INFO +10 -6
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/Readme.md +9 -5
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/grobid_client.py +107 -83
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/PKG-INFO +10 -6
- grobid_client_python-0.2.2/grobid_client_python.egg-info/scm_version.json +8 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/pyproject.toml +1 -1
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_grobid_client.py +161 -6
- grobid_client_python-0.2.0/grobid_client_python.egg-info/scm_version.json +0 -8
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/.github/dependabot.yml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/.github/workflows/ci-build.yml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/.github/workflows/ci-release.yml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/.gitignore +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/LICENSE +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/MANIFEST.in +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/config.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/example.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/__init__.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/client.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2LossyJSON.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2LossyJSON_cli.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2Markdown.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2Markdown_cli.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/__init__.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/__main__.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/validate_json_refs.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/py.typed +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/SOURCES.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/dependency_links.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/entry_points.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/requires.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/scm_file_list.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/top_level.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/pytest.ini +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/requirements.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/20180928112135.png +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/20180928112138.csv +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.pdf +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_pdf/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_pdf/mjb3wlzxcb2mc-migowebupload-1766042162782.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-1.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-2.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-3.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-4.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-5.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-6.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-7.txt +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/setup.cfg +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/__init__.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/conftest.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/0046d83a-edd6-4631-b57c-755cdcce8b7f.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/__init__.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/article_withdrawn.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41477-023-01501-1.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41586-023-05895-y.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1038_s41598-023-32039-z.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.1371_journal.pone.0218311.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.7554_elife.78558.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/10.7554_elife.78558.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/refs_offsets/2021.naacl-main.224.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/repeated_text/ijms-24-05988.grobid.tei.xml +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/repeated_text/ijms-24-05988.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/resources/repeated_text/ijms-24-05988.md +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/run_tests.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_client.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_config.json +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_conversions.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_integration.py +0 -0
- {grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/tests/test_s3.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grobid-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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>
|
|
@@ -411,6 +411,9 @@ grobid_client --input "~/data/**/*.pdf" --output ~/results processFulltextDocu
|
|
|
411
411
|
|
|
412
412
|
> [!NOTE]
|
|
413
413
|
> `--input` accepts a directory, a single file, an **archive**, or a **glob pattern**:
|
|
414
|
+
> - **Directories** are processed recursively and the output mirrors the input tree: `~/pdfs/2023/a.pdf` →
|
|
415
|
+
> `~/results/2023/a.grobid.tei.xml` (subdirectories of `--output` are created as needed, and the JSON/Markdown
|
|
416
|
+
> outputs and error files sit next to each TEI file). Archives keep their internal paths the same way.
|
|
414
417
|
> - **Archives** (`.zip`, `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`) are streamed: eligible entries are read into
|
|
415
418
|
> memory in chunks of `queue_size` and sent to GROBID straight from there — the archive is never fully decompressed and
|
|
416
419
|
> nothing but the results in `--output` ever touches the disk. If `--output` is omitted, results go to a directory named
|
|
@@ -636,18 +639,19 @@ settings.
|
|
|
636
639
|
|
|
637
640
|
### Choosing a queue size
|
|
638
641
|
|
|
639
|
-
`queue_size` controls how many files are grouped into one processing chunk. It is a memory
|
|
642
|
+
`queue_size` controls how many files are grouped into one processing chunk. It is a memory knob, not a
|
|
640
643
|
concurrency one: parallelism toward the GROBID server is set by `-n`, and each chunk is processed `n` files at a time.
|
|
641
644
|
When `queue_size` is not set, the client picks a sensible default per input type (see the table above); set it
|
|
642
645
|
explicitly only if you need to override that. A few guidelines:
|
|
643
646
|
|
|
644
647
|
- **Never set it below `n`.** Effective parallelism is `min(n, queue_size)`: a queue smaller than the thread pool
|
|
645
648
|
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
|
|
647
|
-
|
|
649
|
+
- **Results are written as they complete.** Each output file lands on disk as soon as its document has been
|
|
650
|
+
processed, whatever the queue size, so an interrupted run only loses the documents that were in flight
|
|
648
651
|
(already-written results are skipped on re-run unless `--force` is used).
|
|
649
|
-
- **Local PDF directories:** the queue holds only file paths
|
|
650
|
-
1000.
|
|
652
|
+
- **Local PDF directories:** the queue holds only file paths (each file is opened when it is sent), so large values
|
|
653
|
+
are essentially free — the default is 1000. At the end of each chunk the pool waits for its slowest document, so a
|
|
654
|
+
large queue keeps the workers busy.
|
|
651
655
|
- **Archives (zip/tar) and S3:** each chunk is read or downloaded *into memory* before processing starts, so peak RAM
|
|
652
656
|
grows with `queue_size × average file size`. Keep it moderate — the 1.2 × `n` default is a safe floor; going up to
|
|
653
657
|
a few multiples of `n` (e.g. 2–5 ×) trades memory for slightly better throughput around chunk boundaries.
|
|
@@ -189,6 +189,9 @@ grobid_client --input "~/data/**/*.pdf" --output ~/results processFulltextDocu
|
|
|
189
189
|
|
|
190
190
|
> [!NOTE]
|
|
191
191
|
> `--input` accepts a directory, a single file, an **archive**, or a **glob pattern**:
|
|
192
|
+
> - **Directories** are processed recursively and the output mirrors the input tree: `~/pdfs/2023/a.pdf` →
|
|
193
|
+
> `~/results/2023/a.grobid.tei.xml` (subdirectories of `--output` are created as needed, and the JSON/Markdown
|
|
194
|
+
> outputs and error files sit next to each TEI file). Archives keep their internal paths the same way.
|
|
192
195
|
> - **Archives** (`.zip`, `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`) are streamed: eligible entries are read into
|
|
193
196
|
> memory in chunks of `queue_size` and sent to GROBID straight from there — the archive is never fully decompressed and
|
|
194
197
|
> nothing but the results in `--output` ever touches the disk. If `--output` is omitted, results go to a directory named
|
|
@@ -414,18 +417,19 @@ settings.
|
|
|
414
417
|
|
|
415
418
|
### Choosing a queue size
|
|
416
419
|
|
|
417
|
-
`queue_size` controls how many files are grouped into one processing chunk. It is a memory
|
|
420
|
+
`queue_size` controls how many files are grouped into one processing chunk. It is a memory knob, not a
|
|
418
421
|
concurrency one: parallelism toward the GROBID server is set by `-n`, and each chunk is processed `n` files at a time.
|
|
419
422
|
When `queue_size` is not set, the client picks a sensible default per input type (see the table above); set it
|
|
420
423
|
explicitly only if you need to override that. A few guidelines:
|
|
421
424
|
|
|
422
425
|
- **Never set it below `n`.** Effective parallelism is `min(n, queue_size)`: a queue smaller than the thread pool
|
|
423
426
|
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
|
|
425
|
-
|
|
427
|
+
- **Results are written as they complete.** Each output file lands on disk as soon as its document has been
|
|
428
|
+
processed, whatever the queue size, so an interrupted run only loses the documents that were in flight
|
|
426
429
|
(already-written results are skipped on re-run unless `--force` is used).
|
|
427
|
-
- **Local PDF directories:** the queue holds only file paths
|
|
428
|
-
1000.
|
|
430
|
+
- **Local PDF directories:** the queue holds only file paths (each file is opened when it is sent), so large values
|
|
431
|
+
are essentially free — the default is 1000. At the end of each chunk the pool waits for its slowest document, so a
|
|
432
|
+
large queue keeps the workers busy.
|
|
429
433
|
- **Archives (zip/tar) and S3:** each chunk is read or downloaded *into memory* before processing starts, so peak RAM
|
|
430
434
|
grows with `queue_size × average file size`. Keep it moderate — the 1.2 × `n` default is a safe floor; going up to
|
|
431
435
|
a few multiples of `n` (e.g. 2–5 ×) trades memory for slightly better throughput around chunk boundaries.
|
|
@@ -34,7 +34,7 @@ import shutil
|
|
|
34
34
|
import tarfile
|
|
35
35
|
import tempfile
|
|
36
36
|
import zipfile
|
|
37
|
-
from typing import Any, BinaryIO, Callable, Optional, Tuple, Union
|
|
37
|
+
from typing import Any, BinaryIO, Callable, List, Optional, Tuple, Union
|
|
38
38
|
import copy
|
|
39
39
|
|
|
40
40
|
from .format.TEI2LossyJSON import TEI2LossyJSONConverter
|
|
@@ -449,10 +449,15 @@ class GrobidClient(ApiClient):
|
|
|
449
449
|
input_file_path = pathlib.Path(input_file)
|
|
450
450
|
|
|
451
451
|
if output is not None:
|
|
452
|
-
#
|
|
452
|
+
# Mirror the input tree under the output directory: the path of the
|
|
453
|
+
# file relative to input_path is kept (only the extension changes),
|
|
454
|
+
# so sub/paper.pdf -> <output>/sub/paper.grobid.tei.xml. Keeping the
|
|
455
|
+
# subdirectories also prevents two inputs that share a basename
|
|
456
|
+
# from mapping to the same output file.
|
|
453
457
|
input_path_abs = pathlib.Path(input_path).resolve()
|
|
454
458
|
input_file_rel = input_file_path.resolve().relative_to(input_path_abs)
|
|
455
|
-
filename = pathlib.Path(output) /
|
|
459
|
+
filename = pathlib.Path(output) / input_file_rel.with_name(
|
|
460
|
+
f"{input_file_rel.stem}.grobid.tei.xml")
|
|
456
461
|
else:
|
|
457
462
|
# Use the same directory as the input file
|
|
458
463
|
filename = input_file_path.parent / f"{input_file_path.stem}.grobid.tei.xml"
|
|
@@ -629,9 +634,14 @@ class GrobidClient(ApiClient):
|
|
|
629
634
|
|
|
630
635
|
# Partition into archives (streamed), remote loose files (s3) and local
|
|
631
636
|
# filesystem files (directories are expanded to their eligible files).
|
|
637
|
+
# Local files are grouped with the base directory their output paths
|
|
638
|
+
# are computed from: a directory given as input is the base of the
|
|
639
|
+
# files found inside it (so the output mirrors its tree), while loose
|
|
640
|
+
# files (single paths, glob matches) share a common ancestor.
|
|
632
641
|
archive_paths = []
|
|
633
642
|
remote_files = []
|
|
634
|
-
|
|
643
|
+
fs_groups: List[Tuple[str, list]] = []
|
|
644
|
+
loose_files = []
|
|
635
645
|
for path in matched_paths:
|
|
636
646
|
if self._is_s3(path):
|
|
637
647
|
if self._looks_like_archive(path):
|
|
@@ -643,12 +653,20 @@ class GrobidClient(ApiClient):
|
|
|
643
653
|
elif self._is_archive(path):
|
|
644
654
|
archive_paths.append(path)
|
|
645
655
|
elif os.path.isdir(path):
|
|
646
|
-
|
|
656
|
+
dir_files = self._collect_directory_files(path, service)
|
|
657
|
+
if dir_files:
|
|
658
|
+
fs_groups.append((path, dir_files))
|
|
647
659
|
elif os.path.isfile(path) and self._is_eligible_input(os.path.basename(path), service):
|
|
648
|
-
|
|
660
|
+
loose_files.append(path)
|
|
661
|
+
elif not os.path.exists(path):
|
|
662
|
+
self.logger.warning(f"Input path does not exist: {path}")
|
|
649
663
|
else:
|
|
650
664
|
self.logger.debug(f"Skipping input (not an eligible file/dir/archive): {path}")
|
|
651
665
|
|
|
666
|
+
if loose_files:
|
|
667
|
+
fs_groups.append((self._common_base(loose_files), loose_files))
|
|
668
|
+
fs_files = [f for _, files in fs_groups for f in files]
|
|
669
|
+
|
|
652
670
|
if not fs_files and not archive_paths and not remote_files:
|
|
653
671
|
self.logger.warning(f"No eligible files found in input(s): {inputs}")
|
|
654
672
|
return
|
|
@@ -667,16 +685,17 @@ class GrobidClient(ApiClient):
|
|
|
667
685
|
# Local files gathered from directories and/or loose glob matches
|
|
668
686
|
if fs_files:
|
|
669
687
|
print(f"Found {len(fs_files)} local file(s) to process")
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
688
|
+
for base_dir, group_files in fs_groups:
|
|
689
|
+
bp, be, bs = self._run_file_batches(
|
|
690
|
+
service, group_files, base_dir, output, n,
|
|
691
|
+
generate_ids, consolidate_header, consolidate_citations,
|
|
692
|
+
include_raw_citations, include_raw_affiliations, tei_coordinates,
|
|
693
|
+
segment_sentences, force, verbose, flavor, json_output, markdown_output,
|
|
694
|
+
skip_errors=skip_errors
|
|
695
|
+
)
|
|
696
|
+
processed_files_count += bp
|
|
697
|
+
errors_files_count += be
|
|
698
|
+
skipped_files_count += bs
|
|
680
699
|
total_files += len(fs_files)
|
|
681
700
|
|
|
682
701
|
# Loose remote (s3) files: streamed into memory one chunk at a time
|
|
@@ -927,6 +946,10 @@ class GrobidClient(ApiClient):
|
|
|
927
946
|
|
|
928
947
|
def _is_eligible_input(self, filename: str, service: str) -> bool:
|
|
929
948
|
"""Return True if a file name is a valid input for the given service."""
|
|
949
|
+
# macOS AppleDouble metadata entries (._paper.pdf), which tar and zip
|
|
950
|
+
# add next to the real files, carry the extension but not the document
|
|
951
|
+
if filename.startswith("._"):
|
|
952
|
+
return False
|
|
930
953
|
if filename.endswith(".pdf") or filename.endswith(".PDF"):
|
|
931
954
|
return True
|
|
932
955
|
if service == 'processCitationList' and (
|
|
@@ -1090,8 +1113,9 @@ class GrobidClient(ApiClient):
|
|
|
1090
1113
|
the chunk size and nothing but the results ever touches the disk. (The
|
|
1091
1114
|
exception is ``processCitationList``, whose ``.txt`` inputs are read
|
|
1092
1115
|
from a path and therefore still go through a temporary directory.)
|
|
1093
|
-
Output files follow the same
|
|
1094
|
-
processing
|
|
1116
|
+
Output files follow the same naming convention as directory
|
|
1117
|
+
processing: the entry's path inside the archive is mirrored under
|
|
1118
|
+
``output`` (``nested/y.pdf`` -> ``<output>/nested/y.grobid.tei.xml``).
|
|
1095
1119
|
"""
|
|
1096
1120
|
start_time = time.time()
|
|
1097
1121
|
self._warn_on_consolidation_timeout(consolidate_citations)
|
|
@@ -1529,75 +1553,75 @@ class GrobidClient(ApiClient):
|
|
|
1529
1553
|
|
|
1530
1554
|
results.append(r)
|
|
1531
1555
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1556
|
+
for r in concurrent.futures.as_completed(results):
|
|
1557
|
+
input_file, status, text = r.result()
|
|
1558
|
+
filename = self._output_file_name(input_file, input_path, output)
|
|
1535
1559
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1560
|
+
if status != 200 or text is None:
|
|
1561
|
+
self.logger.error(f"Processing of {input_file} failed with error {status}: {text}")
|
|
1562
|
+
error_count += 1
|
|
1563
|
+
# writing error file with suffixed error code
|
|
1564
|
+
error_filename = self._error_file_name(filename, status)
|
|
1565
|
+
try:
|
|
1566
|
+
self._write_atomic(error_filename, text if text is not None else "")
|
|
1567
|
+
self.logger.info(f"Error details written to {error_filename}")
|
|
1568
|
+
# A previous run may have failed with a different status code;
|
|
1569
|
+
# keep only the marker of this run so the error state of the
|
|
1570
|
+
# document stays unambiguous (and skippable).
|
|
1571
|
+
self._remove_error_files(filename, keep=error_filename)
|
|
1572
|
+
except OSError as e:
|
|
1573
|
+
self.logger.error(f"Failed to write error file {filename}: {str(e)}")
|
|
1574
|
+
else:
|
|
1575
|
+
processed_count += 1
|
|
1576
|
+
# writing TEI file
|
|
1577
|
+
try:
|
|
1578
|
+
self._write_atomic(filename, text)
|
|
1579
|
+
self.logger.debug(f"Successfully wrote TEI file: {filename}")
|
|
1556
1580
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1581
|
+
# The document no longer is in error: drop the marker left by
|
|
1582
|
+
# a previous failed run so it is not skipped by --skip-errors.
|
|
1583
|
+
self._remove_error_files(filename)
|
|
1560
1584
|
|
|
1561
1585
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1586
|
+
# Convert to JSON if requested
|
|
1587
|
+
if json_output:
|
|
1588
|
+
try:
|
|
1589
|
+
converter = TEI2LossyJSONConverter()
|
|
1590
|
+
json_data = converter.convert_tei_file(filename, stream=False)
|
|
1591
|
+
|
|
1592
|
+
if json_data:
|
|
1593
|
+
json_filename = filename.replace('.grobid.tei.xml', '.json')
|
|
1594
|
+
# Always write JSON file when TEI is written (respects --force behavior)
|
|
1595
|
+
json_filename_expanded = os.path.expanduser(json_filename)
|
|
1596
|
+
self._write_atomic(
|
|
1597
|
+
json_filename_expanded,
|
|
1598
|
+
json.dumps(json_data, indent=2, ensure_ascii=False))
|
|
1599
|
+
self.logger.debug(f"Successfully wrote JSON file: {json_filename_expanded}")
|
|
1600
|
+
else:
|
|
1601
|
+
self.logger.warning(f"Failed to convert TEI to JSON for {filename}")
|
|
1602
|
+
except Exception as e:
|
|
1603
|
+
self.logger.error(f"Failed to convert TEI to JSON for {filename}: {str(e)}")
|
|
1604
|
+
|
|
1605
|
+
# Convert to Markdown if requested
|
|
1606
|
+
if markdown_output:
|
|
1607
|
+
try:
|
|
1608
|
+
from .format.TEI2Markdown import TEI2MarkdownConverter
|
|
1609
|
+
converter = TEI2MarkdownConverter()
|
|
1610
|
+
markdown_data = converter.convert_tei_file(filename)
|
|
1611
|
+
|
|
1612
|
+
if markdown_data is not None:
|
|
1613
|
+
markdown_filename = filename.replace('.grobid.tei.xml', '.md')
|
|
1614
|
+
# Always write Markdown file when TEI is written (respects --force behavior)
|
|
1615
|
+
markdown_filename_expanded = os.path.expanduser(markdown_filename)
|
|
1616
|
+
self._write_atomic(markdown_filename_expanded, markdown_data)
|
|
1617
|
+
self.logger.debug(f"Successfully wrote Markdown file: {markdown_filename_expanded}")
|
|
1618
|
+
else:
|
|
1619
|
+
self.logger.warning(f"Failed to convert TEI to Markdown for {filename}")
|
|
1620
|
+
except Exception as e:
|
|
1621
|
+
self.logger.error(f"Failed to convert TEI to Markdown for {filename}: {str(e)}")
|
|
1622
|
+
|
|
1623
|
+
except OSError as e:
|
|
1624
|
+
self.logger.error(f"Failed to write TEI XML file {filename}: {str(e)}")
|
|
1601
1625
|
|
|
1602
1626
|
# Calculate batch statistics
|
|
1603
1627
|
batch_runtime = time.time() - batch_start_time
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grobid-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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>
|
|
@@ -411,6 +411,9 @@ grobid_client --input "~/data/**/*.pdf" --output ~/results processFulltextDocu
|
|
|
411
411
|
|
|
412
412
|
> [!NOTE]
|
|
413
413
|
> `--input` accepts a directory, a single file, an **archive**, or a **glob pattern**:
|
|
414
|
+
> - **Directories** are processed recursively and the output mirrors the input tree: `~/pdfs/2023/a.pdf` →
|
|
415
|
+
> `~/results/2023/a.grobid.tei.xml` (subdirectories of `--output` are created as needed, and the JSON/Markdown
|
|
416
|
+
> outputs and error files sit next to each TEI file). Archives keep their internal paths the same way.
|
|
414
417
|
> - **Archives** (`.zip`, `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`) are streamed: eligible entries are read into
|
|
415
418
|
> memory in chunks of `queue_size` and sent to GROBID straight from there — the archive is never fully decompressed and
|
|
416
419
|
> nothing but the results in `--output` ever touches the disk. If `--output` is omitted, results go to a directory named
|
|
@@ -636,18 +639,19 @@ settings.
|
|
|
636
639
|
|
|
637
640
|
### Choosing a queue size
|
|
638
641
|
|
|
639
|
-
`queue_size` controls how many files are grouped into one processing chunk. It is a memory
|
|
642
|
+
`queue_size` controls how many files are grouped into one processing chunk. It is a memory knob, not a
|
|
640
643
|
concurrency one: parallelism toward the GROBID server is set by `-n`, and each chunk is processed `n` files at a time.
|
|
641
644
|
When `queue_size` is not set, the client picks a sensible default per input type (see the table above); set it
|
|
642
645
|
explicitly only if you need to override that. A few guidelines:
|
|
643
646
|
|
|
644
647
|
- **Never set it below `n`.** Effective parallelism is `min(n, queue_size)`: a queue smaller than the thread pool
|
|
645
648
|
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
|
|
647
|
-
|
|
649
|
+
- **Results are written as they complete.** Each output file lands on disk as soon as its document has been
|
|
650
|
+
processed, whatever the queue size, so an interrupted run only loses the documents that were in flight
|
|
648
651
|
(already-written results are skipped on re-run unless `--force` is used).
|
|
649
|
-
- **Local PDF directories:** the queue holds only file paths
|
|
650
|
-
1000.
|
|
652
|
+
- **Local PDF directories:** the queue holds only file paths (each file is opened when it is sent), so large values
|
|
653
|
+
are essentially free — the default is 1000. At the end of each chunk the pool waits for its slowest document, so a
|
|
654
|
+
large queue keeps the workers busy.
|
|
651
655
|
- **Archives (zip/tar) and S3:** each chunk is read or downloaded *into memory* before processing starts, so peak RAM
|
|
652
656
|
grows with `queue_size × average file size`. Keep it moderate — the 1.2 × `n` default is a safe floor; going up to
|
|
653
657
|
a few multiples of `n` (e.g. 2–5 ×) trades memory for slightly better throughput around chunk boundaries.
|
|
@@ -226,6 +226,17 @@ class TestGrobidClient:
|
|
|
226
226
|
|
|
227
227
|
assert result == expected
|
|
228
228
|
|
|
229
|
+
def test_output_file_name_preserves_subdirectories(self):
|
|
230
|
+
"""The path relative to the input directory is mirrored under the output."""
|
|
231
|
+
with patch('grobid_client.grobid_client.GrobidClient._test_server_connection'):
|
|
232
|
+
with patch('grobid_client.grobid_client.GrobidClient._configure_logging'):
|
|
233
|
+
client = GrobidClient(check_server=False)
|
|
234
|
+
|
|
235
|
+
result = client._output_file_name('/input/path/sub/deeper/document.pdf', '/input/path', '/output/path')
|
|
236
|
+
expected = os.path.join('/output/path', 'sub', 'deeper', 'document.grobid.tei.xml')
|
|
237
|
+
|
|
238
|
+
assert result == expected
|
|
239
|
+
|
|
229
240
|
def test_output_file_name_without_output_path(self):
|
|
230
241
|
"""Test _output_file_name method without output path."""
|
|
231
242
|
with patch('grobid_client.grobid_client.GrobidClient._test_server_connection'):
|
|
@@ -753,11 +764,13 @@ class TestArchiveInput:
|
|
|
753
764
|
|
|
754
765
|
@staticmethod
|
|
755
766
|
def _tei_outputs(output_dir):
|
|
767
|
+
"""TEI outputs under output_dir, as paths relative to it (posix separators)."""
|
|
756
768
|
found = []
|
|
757
769
|
for root, _, files in os.walk(output_dir):
|
|
758
770
|
for f in files:
|
|
759
771
|
if f.endswith('.grobid.tei.xml'):
|
|
760
|
-
|
|
772
|
+
rel = os.path.relpath(os.path.join(root, f), output_dir)
|
|
773
|
+
found.append(rel.replace(os.sep, '/'))
|
|
761
774
|
return sorted(found)
|
|
762
775
|
|
|
763
776
|
def test_is_archive(self):
|
|
@@ -798,7 +811,7 @@ class TestArchiveInput:
|
|
|
798
811
|
posted = self._run(client, zip_path, out)
|
|
799
812
|
|
|
800
813
|
# all 3 PDFs processed, the .txt ignored
|
|
801
|
-
assert self._tei_outputs(out) == ['a.grobid.tei.xml', '
|
|
814
|
+
assert self._tei_outputs(out) == ['a.grobid.tei.xml', 'c.grobid.tei.xml', 'sub/b.grobid.tei.xml']
|
|
802
815
|
# each entry was posted straight from memory, under its archive name
|
|
803
816
|
assert sorted(posted) == [
|
|
804
817
|
('a.pdf', b'%PDF-a'),
|
|
@@ -813,7 +826,8 @@ class TestArchiveInput:
|
|
|
813
826
|
self._make_targz(tar_path, {'x.pdf': b'%PDF-x', 'nested/y.pdf': b'%PDF-y'}, d)
|
|
814
827
|
out = os.path.join(d, 'out')
|
|
815
828
|
posted = self._run(client, tar_path, out)
|
|
816
|
-
|
|
829
|
+
# the entry's path inside the archive is mirrored in the output
|
|
830
|
+
assert self._tei_outputs(out) == ['nested/y.grobid.tei.xml', 'x.grobid.tei.xml']
|
|
817
831
|
assert sorted(posted) == [
|
|
818
832
|
(os.path.join('nested', 'y.pdf'), b'%PDF-y'),
|
|
819
833
|
('x.pdf', b'%PDF-x'),
|
|
@@ -855,7 +869,7 @@ class TestArchiveInput:
|
|
|
855
869
|
client.process('processFulltextDocument', zip_path, output=out, force=True)
|
|
856
870
|
|
|
857
871
|
mock_mkdtemp.assert_not_called()
|
|
858
|
-
assert self._tei_outputs(out) == ['a.grobid.tei.xml', 'b.grobid.tei.xml']
|
|
872
|
+
assert self._tei_outputs(out) == ['a.grobid.tei.xml', 'sub/b.grobid.tei.xml']
|
|
859
873
|
|
|
860
874
|
def test_citation_lists_are_still_extracted_to_disk(self):
|
|
861
875
|
"""process_txt reads from a path, so citation lists keep the temp-dir route."""
|
|
@@ -1039,11 +1053,13 @@ class TestGlobInput:
|
|
|
1039
1053
|
|
|
1040
1054
|
@staticmethod
|
|
1041
1055
|
def _tei_outputs(output_dir):
|
|
1056
|
+
"""TEI outputs under output_dir, as paths relative to it (posix separators)."""
|
|
1042
1057
|
found = []
|
|
1043
1058
|
for root, _, files in os.walk(output_dir):
|
|
1044
1059
|
for f in files:
|
|
1045
1060
|
if f.endswith('.grobid.tei.xml'):
|
|
1046
|
-
|
|
1061
|
+
rel = os.path.relpath(os.path.join(root, f), output_dir)
|
|
1062
|
+
found.append(rel.replace(os.sep, '/'))
|
|
1047
1063
|
return sorted(found)
|
|
1048
1064
|
|
|
1049
1065
|
def _run(self, client, pattern, output):
|
|
@@ -1085,7 +1101,8 @@ class TestGlobInput:
|
|
|
1085
1101
|
open(os.path.join(d, 'sub2', 'note.txt'), 'wb').close()
|
|
1086
1102
|
out = os.path.join(d, 'out')
|
|
1087
1103
|
self._run(client, os.path.join(d, '**', '*.pdf'), out)
|
|
1088
|
-
|
|
1104
|
+
# loose glob matches are placed relative to their common ancestor
|
|
1105
|
+
assert self._tei_outputs(out) == ['sub1/a.grobid.tei.xml', 'sub2/b.grobid.tei.xml']
|
|
1089
1106
|
|
|
1090
1107
|
def test_glob_no_match_warns(self):
|
|
1091
1108
|
client = self._client()
|
|
@@ -1107,6 +1124,107 @@ class TestGlobInput:
|
|
|
1107
1124
|
assert f1.startswith(base) and f2.startswith(base)
|
|
1108
1125
|
|
|
1109
1126
|
|
|
1127
|
+
class TestDirectoryInput:
|
|
1128
|
+
"""A directory input is mirrored under --output (see issue about flat outputs).
|
|
1129
|
+
|
|
1130
|
+
The TEI of ``<in>/2023/p.pdf`` goes to ``<out>/2023/p.grobid.tei.xml``: the
|
|
1131
|
+
subdirectories are kept, so two documents sharing a basename in different
|
|
1132
|
+
subdirectories do not collide on the same output file.
|
|
1133
|
+
"""
|
|
1134
|
+
|
|
1135
|
+
def _client(self, queue_size=50):
|
|
1136
|
+
with patch('grobid_client.grobid_client.GrobidClient._test_server_connection'):
|
|
1137
|
+
with patch('grobid_client.grobid_client.GrobidClient._configure_logging'):
|
|
1138
|
+
client = GrobidClient(check_server=False)
|
|
1139
|
+
client.logger = Mock()
|
|
1140
|
+
client.config['queue_size'] = queue_size
|
|
1141
|
+
return client
|
|
1142
|
+
|
|
1143
|
+
@staticmethod
|
|
1144
|
+
def _touch(*parts):
|
|
1145
|
+
path = os.path.join(*parts)
|
|
1146
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
1147
|
+
open(path, 'wb').close()
|
|
1148
|
+
return path
|
|
1149
|
+
|
|
1150
|
+
@staticmethod
|
|
1151
|
+
def _tei_outputs(output_dir):
|
|
1152
|
+
found = []
|
|
1153
|
+
for root, _, files in os.walk(output_dir):
|
|
1154
|
+
for f in files:
|
|
1155
|
+
if f.endswith('.grobid.tei.xml'):
|
|
1156
|
+
rel = os.path.relpath(os.path.join(root, f), output_dir)
|
|
1157
|
+
found.append(rel.replace(os.sep, '/'))
|
|
1158
|
+
return sorted(found)
|
|
1159
|
+
|
|
1160
|
+
def _run(self, client, input_dir, output, force=True):
|
|
1161
|
+
posted = []
|
|
1162
|
+
|
|
1163
|
+
def fake_post(url, files=None, data=None, headers=None, timeout=None):
|
|
1164
|
+
posted.append(files['input'][0])
|
|
1165
|
+
resp = Mock()
|
|
1166
|
+
resp.text = '<TEI>ok</TEI>'
|
|
1167
|
+
return (resp, 200)
|
|
1168
|
+
|
|
1169
|
+
with patch.object(GrobidClient, 'post', side_effect=fake_post):
|
|
1170
|
+
client.process('processFulltextDocument', input_dir, output=output, force=force)
|
|
1171
|
+
return posted
|
|
1172
|
+
|
|
1173
|
+
def test_output_mirrors_input_tree(self):
|
|
1174
|
+
client = self._client()
|
|
1175
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1176
|
+
self._touch(d, 'in', 'top.pdf')
|
|
1177
|
+
self._touch(d, 'in', '2023', 'p.pdf')
|
|
1178
|
+
self._touch(d, 'in', '2024', 'p.pdf') # same basename as 2023/p.pdf
|
|
1179
|
+
self._touch(d, 'in', '2024', 'deep', 'q.pdf')
|
|
1180
|
+
out = os.path.join(d, 'out')
|
|
1181
|
+
posted = self._run(client, os.path.join(d, 'in'), out)
|
|
1182
|
+
assert len(posted) == 4
|
|
1183
|
+
assert self._tei_outputs(out) == [
|
|
1184
|
+
'2023/p.grobid.tei.xml',
|
|
1185
|
+
'2024/deep/q.grobid.tei.xml',
|
|
1186
|
+
'2024/p.grobid.tei.xml',
|
|
1187
|
+
'top.grobid.tei.xml',
|
|
1188
|
+
]
|
|
1189
|
+
|
|
1190
|
+
def test_same_basename_in_subdirectories_both_skipped_on_rerun(self):
|
|
1191
|
+
"""With flat outputs the second p.pdf used to be reported as already done."""
|
|
1192
|
+
client = self._client()
|
|
1193
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1194
|
+
self._touch(d, 'in', '2023', 'p.pdf')
|
|
1195
|
+
self._touch(d, 'in', '2024', 'p.pdf')
|
|
1196
|
+
out = os.path.join(d, 'out')
|
|
1197
|
+
assert len(self._run(client, os.path.join(d, 'in'), out)) == 2
|
|
1198
|
+
# second run without --force: both outputs exist, nothing is posted
|
|
1199
|
+
assert self._run(client, os.path.join(d, 'in'), out, force=False) == []
|
|
1200
|
+
assert self._tei_outputs(out) == ['2023/p.grobid.tei.xml', '2024/p.grobid.tei.xml']
|
|
1201
|
+
|
|
1202
|
+
def test_single_subdirectory_keeps_its_level(self):
|
|
1203
|
+
"""The base is the directory given as input, not the common path of the files."""
|
|
1204
|
+
client = self._client()
|
|
1205
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1206
|
+
self._touch(d, 'in', '2024', 'a.pdf')
|
|
1207
|
+
self._touch(d, 'in', '2024', 'b.pdf')
|
|
1208
|
+
out = os.path.join(d, 'out')
|
|
1209
|
+
self._run(client, os.path.join(d, 'in'), out)
|
|
1210
|
+
assert self._tei_outputs(out) == ['2024/a.grobid.tei.xml', '2024/b.grobid.tei.xml']
|
|
1211
|
+
|
|
1212
|
+
def test_trailing_slash_and_relative_input(self):
|
|
1213
|
+
client = self._client()
|
|
1214
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1215
|
+
self._touch(d, 'in', 'sub', 'a.pdf')
|
|
1216
|
+
out = os.path.join(d, 'out')
|
|
1217
|
+
self._run(client, os.path.join(d, 'in') + os.sep, out)
|
|
1218
|
+
assert self._tei_outputs(out) == ['sub/a.grobid.tei.xml']
|
|
1219
|
+
|
|
1220
|
+
def test_no_output_writes_next_to_input(self):
|
|
1221
|
+
client = self._client()
|
|
1222
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1223
|
+
self._touch(d, 'in', 'sub', 'a.pdf')
|
|
1224
|
+
self._run(client, os.path.join(d, 'in'), None)
|
|
1225
|
+
assert os.path.isfile(os.path.join(d, 'in', 'sub', 'a.grobid.tei.xml'))
|
|
1226
|
+
|
|
1227
|
+
|
|
1110
1228
|
class TestAtomicWrite:
|
|
1111
1229
|
"""A killed task must never leave a partial output that resume accepts.
|
|
1112
1230
|
|
|
@@ -1378,6 +1496,43 @@ class TestSkipErrors:
|
|
|
1378
1496
|
assert (processed, errors, skipped) == (0, 0, 1)
|
|
1379
1497
|
|
|
1380
1498
|
|
|
1499
|
+
class TestIncrementalWrites:
|
|
1500
|
+
"""Results are written as each document finishes, not once the whole queue is done."""
|
|
1501
|
+
|
|
1502
|
+
def test_finished_document_is_written_while_others_are_in_flight(self):
|
|
1503
|
+
with patch('grobid_client.grobid_client.GrobidClient._test_server_connection'):
|
|
1504
|
+
with patch('grobid_client.grobid_client.GrobidClient._configure_logging'):
|
|
1505
|
+
client = GrobidClient(check_server=False)
|
|
1506
|
+
client.logger = Mock()
|
|
1507
|
+
|
|
1508
|
+
with tempfile.TemporaryDirectory() as d:
|
|
1509
|
+
inp = os.path.join(d, 'in')
|
|
1510
|
+
out = os.path.join(d, 'out')
|
|
1511
|
+
os.makedirs(inp)
|
|
1512
|
+
for name in ('a.pdf', 'b.pdf'):
|
|
1513
|
+
with open(os.path.join(inp, name), 'wb') as f:
|
|
1514
|
+
f.write(b'%PDF')
|
|
1515
|
+
a_output = os.path.join(out, 'a.grobid.tei.xml')
|
|
1516
|
+
seen_while_in_flight = []
|
|
1517
|
+
|
|
1518
|
+
def fake_post(url, files=None, data=None, headers=None, timeout=None):
|
|
1519
|
+
if os.path.basename(files['input'][0]) == 'b.pdf':
|
|
1520
|
+
# Hold b until a's result reaches the disk (or give up)
|
|
1521
|
+
deadline = time.time() + 5
|
|
1522
|
+
while not os.path.isfile(a_output) and time.time() < deadline:
|
|
1523
|
+
time.sleep(0.01)
|
|
1524
|
+
seen_while_in_flight.append(os.path.isfile(a_output))
|
|
1525
|
+
resp = Mock()
|
|
1526
|
+
resp.text = '<TEI>ok</TEI>'
|
|
1527
|
+
return (resp, 200)
|
|
1528
|
+
|
|
1529
|
+
with patch.object(GrobidClient, 'post', side_effect=fake_post):
|
|
1530
|
+
client.process('processFulltextDocument', inp, output=out, n=2)
|
|
1531
|
+
|
|
1532
|
+
assert seen_while_in_flight == [True]
|
|
1533
|
+
assert os.path.isfile(os.path.join(out, 'b.grobid.tei.xml'))
|
|
1534
|
+
|
|
1535
|
+
|
|
1381
1536
|
class TestInMemoryDocuments:
|
|
1382
1537
|
"""Processing a PDF that is already in memory, without writing it to disk.
|
|
1383
1538
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2LossyJSON.py
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2LossyJSON_cli.py
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2Markdown.py
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/TEI2Markdown_cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client/format/validate_json_refs.py
RENAMED
|
File without changes
|
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/grobid_client_python.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-1.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-2.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-3.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-4.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-5.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-6.txt
RENAMED
|
File without changes
|
{grobid_client_python-0.2.0 → grobid_client_python-0.2.2}/resources/test_txt/reference-strings-7.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|