fetchpdf 0.1.3__tar.gz → 0.1.5__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.
- {fetchpdf-0.1.3/fetchpdf.egg-info → fetchpdf-0.1.5}/PKG-INFO +17 -11
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/README.md +16 -10
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/__init__.py +1 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/fetchpdf.py +76 -41
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/get_cookies.py +6 -6
- fetchpdf-0.1.5/fetchpdf/retrieval/byline.py +257 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/figures.py +6 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/institutional.py +3 -3
- fetchpdf-0.1.5/fetchpdf/retrieval/references.py +280 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/to_html.py +163 -19
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/to_markdown.py +10 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5/fetchpdf.egg-info}/PKG-INFO +17 -11
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf.egg-info/SOURCES.txt +4 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_cookie_offer.py +1 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_get_cookies.py +1 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_institutional.py +1 -1
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_supplementary.py +24 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_to_html.py +140 -2
- fetchpdf-0.1.5/tests/test_xml_last.py +76 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/LICENSE +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/MANIFEST.in +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/__main__.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/_env.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/_http.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/fetch_abstract_from_doi.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/fetch_metadata_from_doi.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/__init__.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/_util.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/artifact.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/backends/__init__.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/backends/claude_cli.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/backends/openrouter.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/blocked.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/cache.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/chunk.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/classify.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/context.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/corresponding.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/engine.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/extract_images.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/fulltext_scan.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/goals.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/http.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/identifiers.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/ladder.json +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/linked_artifacts.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/llm_adjudicate.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/llm_agent_retrieval.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/normalize.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/pdf_identity.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/pdf_text.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/provenance.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/pubpeer.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/ratelimit.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/repository_waf.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/request_drafts.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/resolve.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/__init__.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/arxiv.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/core_text.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/crossref_tdm.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/epmc.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/epmc_repo.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/landing.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/legacy_pdf.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/pmc.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/pmc_html.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/preprint.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/publisher_html.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/sources/supplements.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_atypon.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_dspace.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_graph.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_index.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_jci.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_pmc.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplement_publishers.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/supplementary.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/tiers.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/validate.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf/retrieval/verify.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf.egg-info/dependency_links.txt +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf.egg-info/entry_points.txt +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf.egg-info/requires.txt +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/fetchpdf.egg-info/top_level.txt +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/pyproject.toml +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/requirements.txt +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/setup.cfg +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_agent_backends.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_blocked.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_corresponding.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_datacite_supplement_identity.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_dspace.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_entrypoints.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_epmc_repo.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_extract_images.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_figures.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_folder_encoding.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_fulltext_scan.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_jci_supplements.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_json_output.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_landing_candidates.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_landing_page_detection.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_last_resort_reachability.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_linked_artifacts.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_llm_adjudicate.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_llm_agent_retrieval.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_normalize_cells.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pdf_identity.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pdf_trailer.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pmc_page.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_polite_ua.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pubmed_identity.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pubpeer.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pull_for_record_call_sites.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_pure.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_request_drafts.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_retrieval.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_scan_record.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_screening_conversion.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_serpapi_scholar.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_short_conference_xml.py +0 -0
- {fetchpdf-0.1.3 → fetchpdf-0.1.5}/tests/test_supplement_asset_identity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fetchpdf
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A comprehensive tool to download PDFs from DOIs using multiple sources
|
|
5
5
|
Author-email: Dan Elton <delton17@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -56,7 +56,7 @@ A comprehensive Python package to download academic papers (PDFs) from DOIs (or
|
|
|
56
56
|
- [Supplementary Material](#supplementary-material)
|
|
57
57
|
- [`--pull-everything`: the second layer](#--pull-everything-the-second-layer)
|
|
58
58
|
- [Figure Images](#figure-images)
|
|
59
|
-
- [Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions) — papers your library subscribes to (`fetchpdf
|
|
59
|
+
- [Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions) — papers your library subscribes to (`fetchpdf fetchcookies`)
|
|
60
60
|
- [Calling fetchpdf from a script or an agent](#calling-fetchpdf-from-a-script-or-an-agent) — `--json`
|
|
61
61
|
- [API Reference](#api-reference)
|
|
62
62
|
- [Download Sources](#download-sources-in-order-of-priority)
|
|
@@ -85,7 +85,7 @@ A comprehensive Python package to download academic papers (PDFs) from DOIs (or
|
|
|
85
85
|
- 🖨️ **[XML to HTML / PDF](#rendering-the-xml-as-html-or-pdf)**: `--generate-html-from-xml` / `--generate-pdf-from-xml` render a retrieved XML as one self-contained page, figures and tables embedded
|
|
86
86
|
- 🔗 **[Linked Datasets & Code](#linked-datasets-and-code-stem_linked_artifactsjson)**: the same pass discovers each paper's external datasets/software via ScholeXplorer, Europe PMC and DataCite, downloads ownership-confirmed deposits from figshare/Zenodo/OSF/Dryad/Dataverse, and records every link in a sidecar
|
|
87
87
|
- 🤖 **[LLM-assisted retrieval](#--pull-everything-the-second-layer)**: `--pull-everything` adds a second layer — a model reads the paper itself and goes after the SI and datasets the APIs missed
|
|
88
|
-
- 🔐 **[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions)** (opt-in): a one-time `fetchpdf
|
|
88
|
+
- 🔐 **[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions)** (opt-in): a one-time `fetchpdf fetchcookies` lets fetchpdf download papers your library subscribes to
|
|
89
89
|
- 🚀 **Batch Processing**: Process multiple DOIs/PMIDs from CSV files or lists
|
|
90
90
|
- ⚡ **Parallel Execution**: Download multiple papers simultaneously with configurable workers
|
|
91
91
|
|
|
@@ -184,7 +184,7 @@ fetchpdf '10.1002/(SICI)1521-3773(19981116)37:21<2922::AID-ANIE2922>3.0.CO;2-1,
|
|
|
184
184
|
# ...but single-quote any list holding ( ) < > ; -- the shell would read them as syntax
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
**At a university?** Run `fetchpdf
|
|
187
|
+
**At a university?** Run `fetchpdf fetchcookies` once and fetchpdf will also fetch
|
|
188
188
|
papers your library subscribes to. See
|
|
189
189
|
[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions).
|
|
190
190
|
|
|
@@ -1005,11 +1005,17 @@ fetchpdf-render ./out --html --pdf --fetch-figures # XML you already have
|
|
|
1005
1005
|
|
|
1006
1006
|
**A rendering, not the publisher's PDF**, and never written as `{stem}.pdf`:
|
|
1007
1007
|
that name means the typeset article to the skip check and the validators. The
|
|
1008
|
-
page opens with a banner naming the source file, the
|
|
1009
|
-
|
|
1008
|
+
page opens with a banner naming the source file, the fetchpdf version and the
|
|
1009
|
+
DOI, plus a note for anything that could not be shown: figures without an image,
|
|
1010
|
+
tables published only as an image that is missing, or no reference list. A figure whose image could not be had keeps a visible
|
|
1010
1011
|
placeholder, so an incomplete rendering looks incomplete. The prose and
|
|
1011
|
-
tables come from the same walker as `--to-markdown`, so the two agree
|
|
1012
|
-
|
|
1012
|
+
tables come from the same walker as `--to-markdown`, so the two agree, except
|
|
1013
|
+
that the page ends with the reference list (with DOI links), which the Markdown
|
|
1014
|
+
leaves out. Formulas stay as text. The XML never says how large a figure was
|
|
1015
|
+
typeset, so each is given about the same area (a square figure ~10.5 cm across),
|
|
1016
|
+
shaped by its aspect ratio and capped at the column width. Under the title come
|
|
1017
|
+
the authors, their affiliations and the journal citation (volume, issue, pages).
|
|
1018
|
+
Tables always fit the page width in the PDF; very wide ones print smaller.
|
|
1013
1019
|
|
|
1014
1020
|
Only records whose XML was kept are rendered — add `--get-xml-or-html` to keep
|
|
1015
1021
|
the XML beside the PDF.
|
|
@@ -1026,7 +1032,7 @@ library, for the papers that have no open-access copy.
|
|
|
1026
1032
|
Browser cookie support is included in the standard fetchpdf installation.
|
|
1027
1033
|
|
|
1028
1034
|
```bash
|
|
1029
|
-
fetchpdf
|
|
1035
|
+
fetchpdf fetchcookies
|
|
1030
1036
|
```
|
|
1031
1037
|
|
|
1032
1038
|
The wizard asks two questions, then opens some tabs:
|
|
@@ -1058,7 +1064,7 @@ session exists but the test article was refused: the session lapsed, or your
|
|
|
1058
1064
|
library does not take that particular journal. Access is turned on only if at
|
|
1059
1065
|
least one publisher works.
|
|
1060
1066
|
|
|
1061
|
-
**Cookies often expire within hours**. You likely will have to rerun `fetchpdf
|
|
1067
|
+
**Cookies often expire within hours**. You likely will have to rerun `fetchpdf fetchcookies` periodically to restore access.
|
|
1062
1068
|
|
|
1063
1069
|
### Living with it
|
|
1064
1070
|
|
|
@@ -1092,7 +1098,7 @@ run. This works the same for a single DOI and for a batch:
|
|
|
1092
1098
|
```
|
|
1093
1099
|
|
|
1094
1100
|
Runs that cannot be asked (pipes, cron, `--json`) print the command instead
|
|
1095
|
-
(`fetchpdf
|
|
1101
|
+
(`fetchpdf fetchcookies --publishers wiley,tandf`, or `refresh --publishers ...`).
|
|
1096
1102
|
`--no-cookies` and `--cookies FILE` turn the offer off. Royal Society papers are
|
|
1097
1103
|
not offered, because there is no test article to sign in on.
|
|
1098
1104
|
|
|
@@ -17,7 +17,7 @@ A comprehensive Python package to download academic papers (PDFs) from DOIs (or
|
|
|
17
17
|
- [Supplementary Material](#supplementary-material)
|
|
18
18
|
- [`--pull-everything`: the second layer](#--pull-everything-the-second-layer)
|
|
19
19
|
- [Figure Images](#figure-images)
|
|
20
|
-
- [Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions) — papers your library subscribes to (`fetchpdf
|
|
20
|
+
- [Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions) — papers your library subscribes to (`fetchpdf fetchcookies`)
|
|
21
21
|
- [Calling fetchpdf from a script or an agent](#calling-fetchpdf-from-a-script-or-an-agent) — `--json`
|
|
22
22
|
- [API Reference](#api-reference)
|
|
23
23
|
- [Download Sources](#download-sources-in-order-of-priority)
|
|
@@ -46,7 +46,7 @@ A comprehensive Python package to download academic papers (PDFs) from DOIs (or
|
|
|
46
46
|
- 🖨️ **[XML to HTML / PDF](#rendering-the-xml-as-html-or-pdf)**: `--generate-html-from-xml` / `--generate-pdf-from-xml` render a retrieved XML as one self-contained page, figures and tables embedded
|
|
47
47
|
- 🔗 **[Linked Datasets & Code](#linked-datasets-and-code-stem_linked_artifactsjson)**: the same pass discovers each paper's external datasets/software via ScholeXplorer, Europe PMC and DataCite, downloads ownership-confirmed deposits from figshare/Zenodo/OSF/Dryad/Dataverse, and records every link in a sidecar
|
|
48
48
|
- 🤖 **[LLM-assisted retrieval](#--pull-everything-the-second-layer)**: `--pull-everything` adds a second layer — a model reads the paper itself and goes after the SI and datasets the APIs missed
|
|
49
|
-
- 🔐 **[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions)** (opt-in): a one-time `fetchpdf
|
|
49
|
+
- 🔐 **[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions)** (opt-in): a one-time `fetchpdf fetchcookies` lets fetchpdf download papers your library subscribes to
|
|
50
50
|
- 🚀 **Batch Processing**: Process multiple DOIs/PMIDs from CSV files or lists
|
|
51
51
|
- ⚡ **Parallel Execution**: Download multiple papers simultaneously with configurable workers
|
|
52
52
|
|
|
@@ -145,7 +145,7 @@ fetchpdf '10.1002/(SICI)1521-3773(19981116)37:21<2922::AID-ANIE2922>3.0.CO;2-1,
|
|
|
145
145
|
# ...but single-quote any list holding ( ) < > ; -- the shell would read them as syntax
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
**At a university?** Run `fetchpdf
|
|
148
|
+
**At a university?** Run `fetchpdf fetchcookies` once and fetchpdf will also fetch
|
|
149
149
|
papers your library subscribes to. See
|
|
150
150
|
[Institutional access](#institutional-access-via-cookies-ie-using-your-librarys-subscriptions).
|
|
151
151
|
|
|
@@ -966,11 +966,17 @@ fetchpdf-render ./out --html --pdf --fetch-figures # XML you already have
|
|
|
966
966
|
|
|
967
967
|
**A rendering, not the publisher's PDF**, and never written as `{stem}.pdf`:
|
|
968
968
|
that name means the typeset article to the skip check and the validators. The
|
|
969
|
-
page opens with a banner naming the source file, the
|
|
970
|
-
|
|
969
|
+
page opens with a banner naming the source file, the fetchpdf version and the
|
|
970
|
+
DOI, plus a note for anything that could not be shown: figures without an image,
|
|
971
|
+
tables published only as an image that is missing, or no reference list. A figure whose image could not be had keeps a visible
|
|
971
972
|
placeholder, so an incomplete rendering looks incomplete. The prose and
|
|
972
|
-
tables come from the same walker as `--to-markdown`, so the two agree
|
|
973
|
-
|
|
973
|
+
tables come from the same walker as `--to-markdown`, so the two agree, except
|
|
974
|
+
that the page ends with the reference list (with DOI links), which the Markdown
|
|
975
|
+
leaves out. Formulas stay as text. The XML never says how large a figure was
|
|
976
|
+
typeset, so each is given about the same area (a square figure ~10.5 cm across),
|
|
977
|
+
shaped by its aspect ratio and capped at the column width. Under the title come
|
|
978
|
+
the authors, their affiliations and the journal citation (volume, issue, pages).
|
|
979
|
+
Tables always fit the page width in the PDF; very wide ones print smaller.
|
|
974
980
|
|
|
975
981
|
Only records whose XML was kept are rendered — add `--get-xml-or-html` to keep
|
|
976
982
|
the XML beside the PDF.
|
|
@@ -987,7 +993,7 @@ library, for the papers that have no open-access copy.
|
|
|
987
993
|
Browser cookie support is included in the standard fetchpdf installation.
|
|
988
994
|
|
|
989
995
|
```bash
|
|
990
|
-
fetchpdf
|
|
996
|
+
fetchpdf fetchcookies
|
|
991
997
|
```
|
|
992
998
|
|
|
993
999
|
The wizard asks two questions, then opens some tabs:
|
|
@@ -1019,7 +1025,7 @@ session exists but the test article was refused: the session lapsed, or your
|
|
|
1019
1025
|
library does not take that particular journal. Access is turned on only if at
|
|
1020
1026
|
least one publisher works.
|
|
1021
1027
|
|
|
1022
|
-
**Cookies often expire within hours**. You likely will have to rerun `fetchpdf
|
|
1028
|
+
**Cookies often expire within hours**. You likely will have to rerun `fetchpdf fetchcookies` periodically to restore access.
|
|
1023
1029
|
|
|
1024
1030
|
### Living with it
|
|
1025
1031
|
|
|
@@ -1053,7 +1059,7 @@ run. This works the same for a single DOI and for a batch:
|
|
|
1053
1059
|
```
|
|
1054
1060
|
|
|
1055
1061
|
Runs that cannot be asked (pipes, cron, `--json`) print the command instead
|
|
1056
|
-
(`fetchpdf
|
|
1062
|
+
(`fetchpdf fetchcookies --publishers wiley,tandf`, or `refresh --publishers ...`).
|
|
1057
1063
|
`--no-cookies` and `--cookies FILE` turn the offer off. Royal Society papers are
|
|
1058
1064
|
not offered, because there is no test article to sign in on.
|
|
1059
1065
|
|
|
@@ -15,7 +15,7 @@ from .fetch_metadata_from_doi import fetch_metadata_from_doi
|
|
|
15
15
|
# behavioural difference, it is the same object.
|
|
16
16
|
fetch_pdf_from_doi = fetch_pdf
|
|
17
17
|
|
|
18
|
-
__version__ = "0.1.
|
|
18
|
+
__version__ = "0.1.5"
|
|
19
19
|
__all__ = [
|
|
20
20
|
"fetch_pdf",
|
|
21
21
|
"fetch_pdf_from_doi", # deprecated alias for fetch_pdf
|
|
@@ -2721,10 +2721,6 @@ def try_pmid_direct_pdf_fallback(pmid: str, save_path: str, verbose=False, allow
|
|
|
2721
2721
|
if verbose:
|
|
2722
2722
|
print(f"✅ PMID native PMCID route success for PMID {pmid}")
|
|
2723
2723
|
return True
|
|
2724
|
-
if allow_xml:
|
|
2725
|
-
xml_got = try_pmc_xml_fallback(pmcid, save_path, verbose=verbose, doi=f"pmid:{pmid}")
|
|
2726
|
-
if xml_got:
|
|
2727
|
-
return xml_got
|
|
2728
2724
|
|
|
2729
2725
|
# Europe PMC record full-text URLs.
|
|
2730
2726
|
try:
|
|
@@ -2797,6 +2793,12 @@ def try_pmid_direct_pdf_fallback(pmid: str, save_path: str, verbose=False, allow
|
|
|
2797
2793
|
if verbose:
|
|
2798
2794
|
print(f" PMID native PubMed provider lookup failed for {pmid}: {str(e)[:100]}")
|
|
2799
2795
|
|
|
2796
|
+
# XML only after every PDF route above has failed.
|
|
2797
|
+
if allow_xml and pmcid:
|
|
2798
|
+
xml_got = try_pmc_xml_fallback(pmcid, save_path, verbose=verbose, doi=f"pmid:{pmid}")
|
|
2799
|
+
if xml_got:
|
|
2800
|
+
return xml_got
|
|
2801
|
+
|
|
2800
2802
|
return False
|
|
2801
2803
|
|
|
2802
2804
|
|
|
@@ -4024,7 +4026,7 @@ def _try_institutional(save_path, resolved, cookies_file=None, verbose=False,
|
|
|
4024
4026
|
_source_out=None):
|
|
4025
4027
|
"""The institutional-access route. Returns the saved path, or None.
|
|
4026
4028
|
|
|
4027
|
-
Off unless the user opted in -- `fetchpdf
|
|
4029
|
+
Off unless the user opted in -- `fetchpdf fetchcookies`, or --cookies FILE --
|
|
4028
4030
|
and silent for a publisher the route does not cover, so a run over APA or
|
|
4029
4031
|
Elsevier DOIs carries no extra noise.
|
|
4030
4032
|
"""
|
|
@@ -4372,6 +4374,12 @@ def _fetch_pdf_chain(doi,
|
|
|
4372
4374
|
# Defer Elsevier XML fallback until all PDF-oriented fallbacks have failed.
|
|
4373
4375
|
elsevier_crossref_message = None
|
|
4374
4376
|
_pmid_cache = [None] # Cached from PMC idconv when available; used for DOI->PMID fallback
|
|
4377
|
+
# XML copies found mid-chain (PMC, eLife, Crossref text-mining links) are
|
|
4378
|
+
# queued here, not returned: an open PDF later in the chain beats them.
|
|
4379
|
+
# PLOS ONE 10.1371/journal.pone.0000308 came back as XML because Europe
|
|
4380
|
+
# PMC's ?pdf=render 403'd and the PMC step took the JATS, while PLOS
|
|
4381
|
+
# itself serves the PDF. Each entry is (source, callable -> path or None).
|
|
4382
|
+
deferred_xml = []
|
|
4375
4383
|
|
|
4376
4384
|
# Skip if PDF or XML already exists
|
|
4377
4385
|
xml_path = os.path.splitext(save_path)[0] + ".xml"
|
|
@@ -5055,12 +5063,9 @@ def _fetch_pdf_chain(doi,
|
|
|
5055
5063
|
# pdf=render 404s for OA records Europe PMC holds only as
|
|
5056
5064
|
# JATS (eLife 34573, PLOS Currents). The XML is the paper.
|
|
5057
5065
|
if allow_xml_fallback:
|
|
5058
|
-
|
|
5066
|
+
deferred_xml.append(("pmc", lambda pmcid=pmcid: try_pmc_xml_fallback(
|
|
5059
5067
|
pmcid, save_path, verbose=verbose, doi=doi
|
|
5060
|
-
)
|
|
5061
|
-
if xml_got:
|
|
5062
|
-
_record_source(_source_out, "pmc")
|
|
5063
|
-
return xml_got
|
|
5068
|
+
)))
|
|
5064
5069
|
# Europe PMC serves fullTextXML only for records inside
|
|
5065
5070
|
# its OA subset. NIH author manuscripts are not in it --
|
|
5066
5071
|
# free to read on the PMC website, not redistributable --
|
|
@@ -5075,26 +5080,17 @@ def _fetch_pdf_chain(doi,
|
|
|
5075
5080
|
# A non-OA record's efetch reply is a well-formed
|
|
5076
5081
|
# <article> with no <body> and the refusal in an XML
|
|
5077
5082
|
# comment; _looks_like_jats_fulltext already rejects it.
|
|
5078
|
-
|
|
5083
|
+
deferred_xml.append(("pmc_efetch", lambda pmcid=pmcid: try_pmc_efetch_xml_fallback(
|
|
5079
5084
|
pmcid, save_path, verbose=verbose, doi=doi
|
|
5080
|
-
)
|
|
5081
|
-
if xml_got:
|
|
5082
|
-
_record_source(_source_out, "pmc_efetch")
|
|
5083
|
-
return xml_got
|
|
5085
|
+
)))
|
|
5084
5086
|
except Exception as e:
|
|
5085
5087
|
if verbose: print(f"Error with PMC: {e}")
|
|
5086
5088
|
pass
|
|
5087
5089
|
|
|
5088
5090
|
# ---------------- eLife CDN / API XML (HTML site 406s) ----------------
|
|
5089
5091
|
if allow_xml_fallback and _elife_article_id(doi):
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
if xml_got:
|
|
5093
|
-
_record_source(_source_out, "elife")
|
|
5094
|
-
return xml_got
|
|
5095
|
-
except Exception as e:
|
|
5096
|
-
if verbose:
|
|
5097
|
-
print(f"Error with eLife XML: {e}")
|
|
5092
|
+
deferred_xml.append(("elife", lambda: try_elife_xml_fallback(
|
|
5093
|
+
doi, save_path, verbose=verbose)))
|
|
5098
5094
|
|
|
5099
5095
|
# ---------------- eScholarship via PubMed LinkOut ----------------
|
|
5100
5096
|
# UC and other universities deposit in eScholarship; PubMed abstract lists these
|
|
@@ -5131,7 +5127,7 @@ def _fetch_pdf_chain(doi,
|
|
|
5131
5127
|
# tail (Crossref landing pages, CORE, DOAJ, DataCite, PMID, Elsevier) that
|
|
5132
5128
|
# rarely finds what a subscription serves in a second or two. Grey sources
|
|
5133
5129
|
# (fetchpdf_grey) still run after the whole walk. A no-op unless the user
|
|
5134
|
-
# ran `fetchpdf
|
|
5130
|
+
# ran `fetchpdf fetchcookies` or passed --cookies, and for publishers it does
|
|
5135
5131
|
# not cover. Once per record: not on related-DOI re-entries.
|
|
5136
5132
|
if _top_level and not (xml_only or xml_html_only):
|
|
5137
5133
|
institutional_path = _try_institutional(
|
|
@@ -5165,12 +5161,9 @@ def _fetch_pdf_chain(doi,
|
|
|
5165
5161
|
# is the CDN JATS file the HTML site 406s would have pointed at.
|
|
5166
5162
|
if allow_xml_fallback:
|
|
5167
5163
|
for xml_url in _crossref_tdm_xml_urls(m):
|
|
5168
|
-
|
|
5164
|
+
deferred_xml.append(("crossref", lambda xml_url=xml_url: _try_save_jats_xml(
|
|
5169
5165
|
xml_url, save_path, verbose=verbose, doi=doi
|
|
5170
|
-
)
|
|
5171
|
-
if xml_got:
|
|
5172
|
-
_record_source(_source_out, "crossref")
|
|
5173
|
-
return xml_got
|
|
5166
|
+
)))
|
|
5174
5167
|
# Landing page fallback
|
|
5175
5168
|
landing = m.get("URL")
|
|
5176
5169
|
if try_download(landing, save_path, verbose):
|
|
@@ -5199,13 +5192,14 @@ def _fetch_pdf_chain(doi,
|
|
|
5199
5192
|
elsevier_crossref_message = m
|
|
5200
5193
|
# A published paper's PsyArXiv/bioRxiv copy is a Crossref relation,
|
|
5201
5194
|
# not Unpaywall's "best" location. Re-enter the chain on that DOI;
|
|
5202
|
-
# _visited stops a cycle.
|
|
5195
|
+
# _visited stops a cycle. PDF only: the related chain returning its
|
|
5196
|
+
# XML here would beat every PDF source still ahead of this one.
|
|
5203
5197
|
for related in _crossref_related_dois(m, doi):
|
|
5204
5198
|
if verbose:
|
|
5205
5199
|
print(f" Trying Crossref related DOI: {related}")
|
|
5206
5200
|
related_path = fetch_pdf(
|
|
5207
5201
|
related, save_path, email, verbose, delay=0,
|
|
5208
|
-
allow_xml_fallback=
|
|
5202
|
+
allow_xml_fallback=False,
|
|
5209
5203
|
use_playwright=use_playwright,
|
|
5210
5204
|
_source_out=_source_out, _visited=_visited,
|
|
5211
5205
|
)
|
|
@@ -5496,12 +5490,19 @@ def _fetch_pdf_chain(doi,
|
|
|
5496
5490
|
if pmid:
|
|
5497
5491
|
pmid_str = str(pmid).strip()
|
|
5498
5492
|
pmid_got = try_pmid_direct_pdf_fallback(
|
|
5499
|
-
pmid_str, save_path, verbose, allow_xml=
|
|
5493
|
+
pmid_str, save_path, verbose, allow_xml=False
|
|
5500
5494
|
)
|
|
5501
5495
|
if pmid_got:
|
|
5502
5496
|
_record_source(_source_out, "pmid_direct")
|
|
5503
5497
|
return pmid_got if isinstance(pmid_got, str) else save_path
|
|
5504
|
-
|
|
5498
|
+
# The PMID route's own PMC XML, unless the PMC step above already
|
|
5499
|
+
# queued XML for this record. Re-running the route repeats its PDF
|
|
5500
|
+
# attempts before the XML, but only once every PDF source has failed.
|
|
5501
|
+
if allow_xml_fallback and not any(
|
|
5502
|
+
source in ("pmc", "pmc_efetch") for source, _ in deferred_xml
|
|
5503
|
+
):
|
|
5504
|
+
deferred_xml.append(("pmid_direct", lambda: try_pmid_direct_pdf_fallback(
|
|
5505
|
+
pmid_str, save_path, verbose, allow_xml=True)))
|
|
5505
5506
|
|
|
5506
5507
|
# ---------------- Google Scholar via SerpApi ---------------------------
|
|
5507
5508
|
# Preserve search credits until the ordinary PDF sources are exhausted.
|
|
@@ -5530,6 +5531,20 @@ def _fetch_pdf_chain(doi,
|
|
|
5530
5531
|
if verbose:
|
|
5531
5532
|
print(f"Error with deferred Elsevier fallback: {e}")
|
|
5532
5533
|
|
|
5534
|
+
# ---------------- Deferred XML (every PDF source has failed) -----------
|
|
5535
|
+
# Last of all, after SerpApi too: XML is the last-ditch format, so a PDF
|
|
5536
|
+
# from any source -- even one that spends a search credit -- comes first.
|
|
5537
|
+
for xml_source, attempt in deferred_xml:
|
|
5538
|
+
try:
|
|
5539
|
+
xml_got = attempt()
|
|
5540
|
+
except Exception as e:
|
|
5541
|
+
if verbose:
|
|
5542
|
+
print(f"Error with deferred {xml_source} XML: {e}")
|
|
5543
|
+
continue
|
|
5544
|
+
if xml_got:
|
|
5545
|
+
_record_source(_source_out, xml_source)
|
|
5546
|
+
return xml_got if isinstance(xml_got, str) else save_path
|
|
5547
|
+
|
|
5533
5548
|
|
|
5534
5549
|
def _append_missing_si_to_report(output_dir, si_statuses, run_timestamp, lock,
|
|
5535
5550
|
drafts_by_doi=None, figure_statuses=None):
|
|
@@ -5809,6 +5824,14 @@ def _prune_empty_record_dir(record_dir):
|
|
|
5809
5824
|
"""
|
|
5810
5825
|
if not record_dir:
|
|
5811
5826
|
return
|
|
5827
|
+
# The resolution cache is written beside the artifact, so a record that got
|
|
5828
|
+
# nothing still holds one; alone, it is not something the record wrote.
|
|
5829
|
+
from .retrieval.cache import CACHE_FILENAME
|
|
5830
|
+
try:
|
|
5831
|
+
if os.listdir(record_dir) == [CACHE_FILENAME]:
|
|
5832
|
+
os.unlink(os.path.join(record_dir, CACHE_FILENAME))
|
|
5833
|
+
except OSError:
|
|
5834
|
+
pass
|
|
5812
5835
|
try:
|
|
5813
5836
|
os.rmdir(record_dir)
|
|
5814
5837
|
except OSError:
|
|
@@ -6423,7 +6446,11 @@ def _batch_fetch_pdfs_inner(dois, output_dir, email=None, verbose=False, delay=0
|
|
|
6423
6446
|
# path, or a record with four supplementary PDFs would report "pdf 5" and
|
|
6424
6447
|
# corrupt the format composition tally.
|
|
6425
6448
|
_pull_si(display_id, raw_identifier, canonical_doi, save_path)
|
|
6426
|
-
|
|
6449
|
+
# Unlike the supplements, figures belong to an article we have: with no
|
|
6450
|
+
# artifact there is nothing for them to illustrate, and the pass would
|
|
6451
|
+
# leave an empty `_figures/` and manifest in a record that failed.
|
|
6452
|
+
if success:
|
|
6453
|
+
_pull_figures(display_id, raw_identifier, canonical_doi, save_path)
|
|
6427
6454
|
_render_xml(display_id, save_path)
|
|
6428
6455
|
_pull_images(result or save_path, display_id)
|
|
6429
6456
|
|
|
@@ -7000,7 +7027,7 @@ def _ask_yes(question: str) -> bool:
|
|
|
7000
7027
|
|
|
7001
7028
|
|
|
7002
7029
|
def _import_cookies(cfg, publishers) -> bool:
|
|
7003
|
-
"""Run `fetchpdf
|
|
7030
|
+
"""Run `fetchpdf fetchcookies` (no config yet) or `refresh` in-process,
|
|
7004
7031
|
for *publishers* only -- the ones the failed papers are at.
|
|
7005
7032
|
|
|
7006
7033
|
True when the retry is worth making: setup succeeded, or refresh found a
|
|
@@ -7064,7 +7091,7 @@ def _offer_cookie_import(failed, retry):
|
|
|
7064
7091
|
if cfg is None:
|
|
7065
7092
|
print(f"🔐 {n} failed {noun} ({where}) may be available through your "
|
|
7066
7093
|
f"library's subscriptions, by importing your browser's cookies.")
|
|
7067
|
-
command = f"fetchpdf
|
|
7094
|
+
command = f"fetchpdf fetchcookies --publishers {','.join(publishers)}"
|
|
7068
7095
|
else:
|
|
7069
7096
|
print(f"🔐 {n} failed {noun} ({where}) are at publishers your library access "
|
|
7070
7097
|
f"covers. If your library sign-in there has lapsed, signing in again may get them.")
|
|
@@ -7319,6 +7346,9 @@ def main(argv=None):
|
|
|
7319
7346
|
if argv and argv[0] == "cookies":
|
|
7320
7347
|
from .get_cookies import main as cookies_main
|
|
7321
7348
|
return cookies_main(argv[1:])
|
|
7349
|
+
if argv and argv[0] == "fetchcookies":
|
|
7350
|
+
from .get_cookies import main as cookies_main
|
|
7351
|
+
return cookies_main(["setup"] + argv[1:])
|
|
7322
7352
|
|
|
7323
7353
|
pre = argparse.ArgumentParser(add_help=False)
|
|
7324
7354
|
pre.add_argument("--json", action="store_true")
|
|
@@ -7410,7 +7440,7 @@ def _main(argv=None, json_out=None):
|
|
|
7410
7440
|
description="Download PDFs from DOIs using multiple fallback sources.",
|
|
7411
7441
|
epilog=(
|
|
7412
7442
|
"Institutional access:\n"
|
|
7413
|
-
" fetchpdf
|
|
7443
|
+
" fetchpdf fetchcookies # sign in through your library\n"
|
|
7414
7444
|
" fetchpdf cookies --help # all cookie commands\n\n"
|
|
7415
7445
|
"Examples:\n"
|
|
7416
7446
|
" fetchpdf papers.csv -o ./pdfs # batch from CSV\n"
|
|
@@ -7824,7 +7854,7 @@ def _main(argv=None, json_out=None):
|
|
|
7824
7854
|
metavar="FILE",
|
|
7825
7855
|
default=None,
|
|
7826
7856
|
help="Institutional access from this cookie file instead of the one "
|
|
7827
|
-
"`fetchpdf
|
|
7857
|
+
"`fetchpdf fetchcookies` configured. Without it, access is used only "
|
|
7828
7858
|
"if setup has been run. Covers publishers whose PDF URL follows "
|
|
7829
7859
|
"from the DOI (Wiley, T&F, SAGE, Springer, Royal Society, Hogrefe, "
|
|
7830
7860
|
"INFORMS); tried after the fast open-access lookups. Only cookies "
|
|
@@ -7890,7 +7920,7 @@ def _main(argv=None, json_out=None):
|
|
|
7890
7920
|
if args.cookies_only:
|
|
7891
7921
|
if args.no_cookies or not _institutional.active_cookies_file():
|
|
7892
7922
|
print("❌ --cookies-only: institutional access is not set up. Run "
|
|
7893
|
-
"`fetchpdf
|
|
7923
|
+
"`fetchpdf fetchcookies` (or pass --cookies FILE).")
|
|
7894
7924
|
return 2
|
|
7895
7925
|
_institutional.set_cookies_only(True)
|
|
7896
7926
|
# "Alone" includes the grey last resorts an installed fetchpdf_grey adds.
|
|
@@ -8295,7 +8325,9 @@ def _main(argv=None, json_out=None):
|
|
|
8295
8325
|
except Exception as e:
|
|
8296
8326
|
print(f"\n📎 supplementary pass failed: {str(e)[:200]}")
|
|
8297
8327
|
|
|
8298
|
-
|
|
8328
|
+
# Only for a record that was retrieved: figures with no article to go
|
|
8329
|
+
# with them are an empty `_figures/` and manifest in a failed record.
|
|
8330
|
+
if args.pull_figures and result:
|
|
8299
8331
|
# A separate call above fetch_pdf, for the same reason the
|
|
8300
8332
|
# supplementary pass is one: the tiered engine re-enters the chain
|
|
8301
8333
|
# at T5 with a temporary save_path, and a flag threaded through
|
|
@@ -8337,6 +8369,8 @@ def _main(argv=None, json_out=None):
|
|
|
8337
8369
|
print(f"\n🖨️ render: {outcome['error']}")
|
|
8338
8370
|
except Exception as e:
|
|
8339
8371
|
print(f"\n🖨️ render failed: {str(e)[:200]}")
|
|
8372
|
+
elif not result:
|
|
8373
|
+
print("\n🖨️ no XML could be retrieved for this record, so nothing to render")
|
|
8340
8374
|
else:
|
|
8341
8375
|
print("\n🖨️ no .xml was kept for this record, so nothing to render "
|
|
8342
8376
|
"(add --get-xml-or-html)")
|
|
@@ -8391,7 +8425,8 @@ def _main(argv=None, json_out=None):
|
|
|
8391
8425
|
return 0
|
|
8392
8426
|
else:
|
|
8393
8427
|
_prune_empty_record_dir(made_record_dir)
|
|
8394
|
-
|
|
8428
|
+
wanted = "XML" if args.xml_only else ("XML or HTML" if args.xml_html_only else "PDF")
|
|
8429
|
+
print(f"\n❌ Failed to download {wanted} for {args.doi}")
|
|
8395
8430
|
_emit_json(json_out, args.doi, resolved_identifier, None,
|
|
8396
8431
|
JSON_STATUS_FAILED, identity=identity,
|
|
8397
8432
|
reasons=reasons or ["no source produced a usable artifact"],
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""fetchpdf cookies: set up institutional access for fetchpdf, once.
|
|
2
2
|
|
|
3
|
-
fetchpdf
|
|
3
|
+
fetchpdf fetchcookies # one-time wizard
|
|
4
4
|
fetchpdf cookies check # which publishers your sessions currently open
|
|
5
5
|
fetchpdf cookies refresh # re-open sign-in tabs for the ones that lapsed
|
|
6
6
|
fetchpdf cookies disable # stop using institutional access
|
|
7
7
|
|
|
8
8
|
Institutional access lets fetchpdf download a paper your library subscribes to
|
|
9
|
-
when no open-access copy exists. It is OFF until you run ``fetchpdf
|
|
9
|
+
when no open-access copy exists. It is OFF until you run ``fetchpdf fetchcookies``.
|
|
10
10
|
|
|
11
11
|
How it works. Each publisher keeps your library sign-in as a cookie on its own
|
|
12
12
|
site (wiley.com, tandfonline.com, ...). The wizard opens one subscription
|
|
@@ -378,7 +378,7 @@ def cmd_setup(args) -> int:
|
|
|
378
378
|
import browser_cookie3 # noqa: F401
|
|
379
379
|
except ImportError:
|
|
380
380
|
print(" A required dependency is missing: browser_cookie3.\n"
|
|
381
|
-
" Run pip install 'browser_cookie3>=0.19', then run fetchpdf
|
|
381
|
+
" Run pip install 'browser_cookie3>=0.19', then run fetchpdf fetchcookies again.")
|
|
382
382
|
return 2
|
|
383
383
|
|
|
384
384
|
# 1. Library
|
|
@@ -443,7 +443,7 @@ def cmd_setup(args) -> int:
|
|
|
443
443
|
|
|
444
444
|
if not any(st == "ok" for st in status.values()):
|
|
445
445
|
print(" No publisher served a PDF, so institutional access is NOT turned on.\n"
|
|
446
|
-
" Check you can open a PDF in your browser, then run fetchpdf
|
|
446
|
+
" Check you can open a PDF in your browser, then run fetchpdf fetchcookies again.")
|
|
447
447
|
return 1
|
|
448
448
|
|
|
449
449
|
cfg["publishers"] = status
|
|
@@ -463,7 +463,7 @@ def cmd_setup(args) -> int:
|
|
|
463
463
|
def _require_config() -> Optional[dict]:
|
|
464
464
|
cfg = inst.load_access_config()
|
|
465
465
|
if not cfg:
|
|
466
|
-
print("Institutional access is not set up. Run: fetchpdf
|
|
466
|
+
print("Institutional access is not set up. Run: fetchpdf fetchcookies")
|
|
467
467
|
return cfg
|
|
468
468
|
|
|
469
469
|
|
|
@@ -516,7 +516,7 @@ def cmd_disable(args) -> int:
|
|
|
516
516
|
return 0
|
|
517
517
|
cfg["enabled"] = False
|
|
518
518
|
_write_private_json(ACCESS_CONFIG, cfg)
|
|
519
|
-
print(f"Institutional access is OFF. Turn it back on with: fetchpdf
|
|
519
|
+
print(f"Institutional access is OFF. Turn it back on with: fetchpdf fetchcookies")
|
|
520
520
|
return 0
|
|
521
521
|
|
|
522
522
|
|