pubmed-stream 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,25 @@
1
+ """pubmed-stream – Download PubMed Central full-text articles."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .downloader import (
6
+ DownloadStats,
7
+ RateLimiter,
8
+ create_session,
9
+ efetch_pmc,
10
+ esearch_pmc,
11
+ extract_metadata_from_pmc_xml,
12
+ search_and_download,
13
+ strip_xml_tags,
14
+ )
15
+
16
+ __all__ = [
17
+ "DownloadStats",
18
+ "RateLimiter",
19
+ "create_session",
20
+ "efetch_pmc",
21
+ "esearch_pmc",
22
+ "extract_metadata_from_pmc_xml",
23
+ "search_and_download",
24
+ "strip_xml_tags",
25
+ ]
@@ -0,0 +1,5 @@
1
+ """Allow ``python -m pubmed_stream`` as a shorthand for the CLI."""
2
+
3
+ from pubmed_stream.cli import main
4
+
5
+ raise SystemExit(main())
pubmed_stream/cli.py ADDED
@@ -0,0 +1,145 @@
1
+ """Command-line interface for pubmed-stream.
2
+
3
+ Installed as the ``pubmed-stream`` console script, or runnable via
4
+ ``python -m pubmed_stream``.
5
+ """
6
+
7
+ import argparse
8
+ import logging
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from . import __version__
13
+ from .downloader import (
14
+ MAX_WORKERS,
15
+ RATE_LIMIT_WITH_API_KEY,
16
+ RATE_LIMIT_NO_API_KEY,
17
+ search_and_download,
18
+ )
19
+
20
+
21
+ def build_parser() -> argparse.ArgumentParser:
22
+ parser = argparse.ArgumentParser(
23
+ prog="pubmed-stream",
24
+ description="Download PubMed Central full-text articles.",
25
+ )
26
+ parser.add_argument(
27
+ "--version",
28
+ action="version",
29
+ version=f"%(prog)s {__version__}",
30
+ )
31
+
32
+ subparsers = parser.add_subparsers(dest="command", required=True)
33
+
34
+ download = subparsers.add_parser(
35
+ "download",
36
+ help="Search PMC and download full-text articles",
37
+ )
38
+ download.add_argument(
39
+ "keyword",
40
+ help="PubMed search query (example: 'frailty cytokines')",
41
+ )
42
+ download.add_argument(
43
+ "--max-results",
44
+ type=int,
45
+ default=100,
46
+ help="Maximum number of articles to download (default: 100)",
47
+ )
48
+ download.add_argument(
49
+ "--format",
50
+ choices=["text", "xml", "both", "json", "txt"],
51
+ default="text",
52
+ help="Output format (all save as .json): 'text' (default, JSON with metadata+text field), "
53
+ "'xml' (JSON with metadata+xml field), 'both' (JSON with both xml and text fields). "
54
+ "Legacy: 'json'/'txt' (mapped to 'text')",
55
+ )
56
+ download.add_argument(
57
+ "--api-key",
58
+ help="Override NCBI API key (otherwise uses NCBI_API_KEY env var)",
59
+ )
60
+ download.add_argument(
61
+ "--sequential",
62
+ action="store_true",
63
+ help="Use sequential downloads instead of concurrent",
64
+ )
65
+ download.add_argument(
66
+ "--workers",
67
+ type=int,
68
+ default=MAX_WORKERS,
69
+ help=f"Number of concurrent worker threads (default: {MAX_WORKERS})",
70
+ )
71
+ download.add_argument(
72
+ "--verbose",
73
+ "-v",
74
+ action="store_true",
75
+ help="Enable verbose (DEBUG) logging",
76
+ )
77
+ download.add_argument(
78
+ "--exclude-text",
79
+ action="store_true",
80
+ help="Exclude plain-text field in JSON output to save space",
81
+ )
82
+ download.add_argument(
83
+ "--output-dir",
84
+ "-o",
85
+ help="Base output directory for downloads (default: ./publications)",
86
+ )
87
+ download.add_argument(
88
+ "--user-agent",
89
+ help="Override HTTP User-Agent header",
90
+ )
91
+ download.add_argument(
92
+ "--email",
93
+ help="Email address for NCBI contact (or NCBI_EMAIL env var)",
94
+ )
95
+ download.add_argument(
96
+ "--rate-limit",
97
+ type=float,
98
+ help=f"Minimum seconds between HTTP requests (auto: {RATE_LIMIT_WITH_API_KEY}s with API key, "
99
+ f"{RATE_LIMIT_NO_API_KEY}s without)",
100
+ )
101
+
102
+ return parser
103
+
104
+
105
+ def main() -> int:
106
+ parser = build_parser()
107
+ args = parser.parse_args()
108
+
109
+ # Configure logging
110
+ log_level = logging.DEBUG if getattr(args, "verbose", False) else logging.INFO
111
+ logging.basicConfig(
112
+ level=log_level,
113
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
114
+ datefmt="%H:%M:%S",
115
+ stream=sys.stderr,
116
+ )
117
+
118
+ if args.command == "download":
119
+ stats = search_and_download(
120
+ keyword=args.keyword,
121
+ max_results=args.max_results,
122
+ fmt=args.format,
123
+ api_key=args.api_key,
124
+ use_concurrent=not args.sequential,
125
+ max_workers=args.workers,
126
+ include_text=not args.exclude_text,
127
+ output_dir=Path(args.output_dir) if args.output_dir else None,
128
+ user_agent=args.user_agent,
129
+ email=args.email,
130
+ rate_limit=args.rate_limit,
131
+ )
132
+
133
+ if stats.successful > 0:
134
+ return 0
135
+ if stats.skipped > 0 and stats.errors == 0:
136
+ return 0
137
+ if stats.requested == 0:
138
+ return 1
139
+ return 2
140
+
141
+ return 1
142
+
143
+
144
+ if __name__ == "__main__":
145
+ raise SystemExit(main())