scitex 2.14.0__py3-none-any.whl → 2.15.1__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.
- scitex/__init__.py +47 -0
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +191 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +127 -59
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/engines/elevenlabs_engine.py +6 -1
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +443 -0
- scitex/cli/main.py +198 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/scholar/__init__.py +8 -0
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/writer.py +117 -0
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +191 -0
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/introspect/__init__.py +75 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +42 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/introspect/_mcp/__init__.py +37 -0
- scitex/introspect/_mcp/handlers.py +208 -0
- scitex/introspect/_members.py +151 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/bundle/README.md +1 -1
- scitex/mcp_server.py +98 -5
- scitex/plt/__init__.py +248 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/sh/README.md +1 -1
- scitex/social/__init__.py +153 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/template/README.md +1 -1
- scitex/template/clone_writer_directory.py +5 -5
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.1.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-24
|
|
3
|
+
# File: src/scitex/scholar/crossref_scitex.py
|
|
4
|
+
"""CrossRef-SciTeX: Local CrossRef database integration for scitex.scholar.
|
|
5
|
+
|
|
6
|
+
This module provides access to the local CrossRef database (167M+ papers)
|
|
7
|
+
through the crossref-local package. Branded as "crossref-scitex" to distinguish
|
|
8
|
+
from the official CrossRef API. Supports both direct database access and HTTP
|
|
9
|
+
API mode for remote servers.
|
|
10
|
+
|
|
11
|
+
Quick Start
|
|
12
|
+
-----------
|
|
13
|
+
|
|
14
|
+
Search for papers:
|
|
15
|
+
>>> from scitex.scholar import crossref_scitex
|
|
16
|
+
>>> results = crossref_scitex.search("hippocampal sharp wave ripples")
|
|
17
|
+
>>> print(results.total, "papers found")
|
|
18
|
+
|
|
19
|
+
Get paper by DOI:
|
|
20
|
+
>>> work = crossref_scitex.get("10.1126/science.aax0758")
|
|
21
|
+
>>> print(work.title)
|
|
22
|
+
|
|
23
|
+
Configuration
|
|
24
|
+
-------------
|
|
25
|
+
|
|
26
|
+
The mode is automatically detected:
|
|
27
|
+
- If CROSSREF_LOCAL_DB is set, uses direct database access
|
|
28
|
+
- If CROSSREF_LOCAL_API_URL is set, uses HTTP API
|
|
29
|
+
- Default: tries localhost:31291 (SciTeX port scheme)
|
|
30
|
+
|
|
31
|
+
Environment variables (SCITEX_SCHOLAR_CROSSREF_* takes priority):
|
|
32
|
+
SCITEX_SCHOLAR_CROSSREF_DB: Path to local database
|
|
33
|
+
SCITEX_SCHOLAR_CROSSREF_MODE: 'db' or 'http'
|
|
34
|
+
CROSSREF_LOCAL_DB: Path to local database (fallback)
|
|
35
|
+
CROSSREF_LOCAL_API_URL: HTTP API URL (fallback)
|
|
36
|
+
|
|
37
|
+
SSH tunnel for remote database:
|
|
38
|
+
$ ssh -L 31291:127.0.0.1:31291 your-server
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
44
|
+
|
|
45
|
+
# Set environment variables for crossref-local configuration
|
|
46
|
+
# SCITEX_SCHOLAR_CROSSREF_* vars take priority in crossref-local's config.py
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from crossref_local import SearchResult, Work
|
|
50
|
+
from crossref_local.citations import CitationNetwork
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
# Core search/retrieval
|
|
54
|
+
"search",
|
|
55
|
+
"count",
|
|
56
|
+
"get",
|
|
57
|
+
"get_many",
|
|
58
|
+
"exists",
|
|
59
|
+
# Enrichment
|
|
60
|
+
"enrich",
|
|
61
|
+
"enrich_dois",
|
|
62
|
+
# Configuration
|
|
63
|
+
"configure",
|
|
64
|
+
"configure_http",
|
|
65
|
+
"get_mode",
|
|
66
|
+
"info",
|
|
67
|
+
"is_available",
|
|
68
|
+
# Citation functions
|
|
69
|
+
"get_citing",
|
|
70
|
+
"get_cited",
|
|
71
|
+
"get_citation_count",
|
|
72
|
+
# Classes (re-exported)
|
|
73
|
+
"Work",
|
|
74
|
+
"SearchResult",
|
|
75
|
+
"CitationNetwork",
|
|
76
|
+
# Async API
|
|
77
|
+
"aio",
|
|
78
|
+
# Cache module
|
|
79
|
+
"cache",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _ensure_crossref_local():
|
|
84
|
+
"""Ensure crossref-local is available."""
|
|
85
|
+
try:
|
|
86
|
+
import crossref_local
|
|
87
|
+
|
|
88
|
+
return crossref_local
|
|
89
|
+
except ImportError as e:
|
|
90
|
+
raise ImportError(
|
|
91
|
+
"crossref-local not installed. Install with: pip install crossref-local"
|
|
92
|
+
) from e
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_available() -> bool:
|
|
96
|
+
"""Check if crossref-local is available and configured.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
True if crossref-local can be used (either DB or HTTP mode)
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
crl = _ensure_crossref_local()
|
|
104
|
+
info_result = crl.info()
|
|
105
|
+
return info_result.get("status") == "ok"
|
|
106
|
+
except Exception:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# Core Search/Retrieval Functions (delegated to crossref-local)
|
|
112
|
+
# =============================================================================
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def search(
|
|
116
|
+
query: str,
|
|
117
|
+
limit: int = 20,
|
|
118
|
+
offset: int = 0,
|
|
119
|
+
**kwargs: Any,
|
|
120
|
+
) -> SearchResult:
|
|
121
|
+
"""Search for papers in the CrossRef database.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
query: Search query string (full-text search)
|
|
125
|
+
limit: Maximum number of results (default: 20)
|
|
126
|
+
offset: Number of results to skip for pagination
|
|
127
|
+
**kwargs: Additional arguments passed to crossref-local
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
SearchResult containing matching papers
|
|
132
|
+
|
|
133
|
+
Examples
|
|
134
|
+
--------
|
|
135
|
+
>>> from scitex.scholar import crossref
|
|
136
|
+
>>> results = crossref.search("deep learning")
|
|
137
|
+
>>> for work in results:
|
|
138
|
+
... print(work.title)
|
|
139
|
+
"""
|
|
140
|
+
crl = _ensure_crossref_local()
|
|
141
|
+
return crl.search(query, limit=limit, offset=offset, **kwargs)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def count(query: str) -> int:
|
|
145
|
+
"""Count papers matching a search query.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
query: Search query string
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
Number of matching papers
|
|
153
|
+
"""
|
|
154
|
+
crl = _ensure_crossref_local()
|
|
155
|
+
return crl.count(query)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get(doi: str) -> Optional[Work]:
|
|
159
|
+
"""Get a paper by DOI.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
doi: DOI of the paper (e.g., "10.1126/science.aax0758")
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
Work object if found, None otherwise
|
|
167
|
+
|
|
168
|
+
Examples
|
|
169
|
+
--------
|
|
170
|
+
>>> work = crossref.get("10.1038/nature12373")
|
|
171
|
+
>>> if work:
|
|
172
|
+
... print(work.title, work.year)
|
|
173
|
+
"""
|
|
174
|
+
crl = _ensure_crossref_local()
|
|
175
|
+
return crl.get(doi)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_many(dois: list[str]) -> list[Work]:
|
|
179
|
+
"""Get multiple papers by DOI.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
dois: List of DOIs
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
List of Work objects (None for DOIs not found)
|
|
187
|
+
"""
|
|
188
|
+
crl = _ensure_crossref_local()
|
|
189
|
+
return crl.get_many(dois)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def exists(doi: str) -> bool:
|
|
193
|
+
"""Check if a DOI exists in the database.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
doi: DOI to check
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
True if DOI exists
|
|
201
|
+
"""
|
|
202
|
+
crl = _ensure_crossref_local()
|
|
203
|
+
return crl.exists(doi)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# =============================================================================
|
|
207
|
+
# Enrichment Functions
|
|
208
|
+
# =============================================================================
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def enrich(results: SearchResult) -> SearchResult:
|
|
212
|
+
"""Enrich search results with citation data.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
results: SearchResult to enrich
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
Enriched SearchResult with citation counts and references
|
|
220
|
+
"""
|
|
221
|
+
crl = _ensure_crossref_local()
|
|
222
|
+
return crl.enrich(results)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def enrich_dois(dois: list[str]) -> list[Work]:
|
|
226
|
+
"""Get and enrich papers by DOI with citation data.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
dois: List of DOIs to enrich
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
List of enriched Work objects
|
|
234
|
+
"""
|
|
235
|
+
crl = _ensure_crossref_local()
|
|
236
|
+
return crl.enrich_dois(dois)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# =============================================================================
|
|
240
|
+
# Citation Functions
|
|
241
|
+
# =============================================================================
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def get_citing(doi: str) -> list[str]:
|
|
245
|
+
"""Get DOIs of papers that cite this paper.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
doi: DOI of the paper
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
List of DOIs that cite this paper
|
|
253
|
+
"""
|
|
254
|
+
crl = _ensure_crossref_local()
|
|
255
|
+
return crl.get_citing(doi)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_cited(doi: str) -> list[str]:
|
|
259
|
+
"""Get DOIs of papers cited by this paper.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
doi: DOI of the paper
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
List of DOIs cited by this paper (references)
|
|
267
|
+
"""
|
|
268
|
+
crl = _ensure_crossref_local()
|
|
269
|
+
return crl.get_cited(doi)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_citation_count(doi: str) -> int:
|
|
273
|
+
"""Get the citation count for a paper.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
doi: DOI of the paper
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
Number of citations
|
|
281
|
+
"""
|
|
282
|
+
crl = _ensure_crossref_local()
|
|
283
|
+
return crl.get_citation_count(doi)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# =============================================================================
|
|
287
|
+
# Configuration Functions
|
|
288
|
+
# =============================================================================
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def configure(db_path: str) -> None:
|
|
292
|
+
"""Configure direct database access mode.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
db_path: Path to the crossref.db file
|
|
296
|
+
"""
|
|
297
|
+
crl = _ensure_crossref_local()
|
|
298
|
+
crl.configure(db_path)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def configure_http(api_url: Optional[str] = None) -> None:
|
|
302
|
+
"""Configure HTTP API mode.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
api_url: API URL (default: http://localhost:31291)
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
>>> # After setting up SSH tunnel: ssh -L 31291:127.0.0.1:31291 server
|
|
309
|
+
>>> crossref.configure_http()
|
|
310
|
+
"""
|
|
311
|
+
crl = _ensure_crossref_local()
|
|
312
|
+
crl.configure_http(api_url)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def get_mode() -> str:
|
|
316
|
+
"""Get the current operating mode.
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
"db" for direct database access, "http" for API mode
|
|
321
|
+
"""
|
|
322
|
+
crl = _ensure_crossref_local()
|
|
323
|
+
return crl.get_mode()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def info() -> dict:
|
|
327
|
+
"""Get information about the crossref-local configuration.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
Dict with status, mode, version, database stats, etc.
|
|
332
|
+
"""
|
|
333
|
+
crl = _ensure_crossref_local()
|
|
334
|
+
return crl.info()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# =============================================================================
|
|
338
|
+
# Re-exported Classes and Modules
|
|
339
|
+
# =============================================================================
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def __getattr__(name: str):
|
|
343
|
+
"""Lazy import for classes and modules."""
|
|
344
|
+
if name == "Work":
|
|
345
|
+
from crossref_local import Work
|
|
346
|
+
|
|
347
|
+
return Work
|
|
348
|
+
elif name == "SearchResult":
|
|
349
|
+
from crossref_local import SearchResult
|
|
350
|
+
|
|
351
|
+
return SearchResult
|
|
352
|
+
elif name == "CitationNetwork":
|
|
353
|
+
from crossref_local.citations import CitationNetwork
|
|
354
|
+
|
|
355
|
+
return CitationNetwork
|
|
356
|
+
elif name == "aio":
|
|
357
|
+
from crossref_local import aio
|
|
358
|
+
|
|
359
|
+
return aio
|
|
360
|
+
elif name == "cache":
|
|
361
|
+
from crossref_local import cache
|
|
362
|
+
|
|
363
|
+
return cache
|
|
364
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# EOF
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# External Package Branding Guide
|
|
2
|
+
|
|
3
|
+
When scitex wraps external packages (like `figrecipe` for `scitex.plt` or `crossref-local` for `scitex.scholar`), those packages should support configurable branding so documentation and error messages show the scitex namespace.
|
|
4
|
+
|
|
5
|
+
## When to Use Branding
|
|
6
|
+
|
|
7
|
+
- **Use branding**: External packages that scitex wraps (figrecipe, crossref-local, etc.)
|
|
8
|
+
- **Don't use branding**: Internal scitex modules (scitex.audio, scitex.stats, etc.) - just hardcode `SCITEX_*` prefix
|
|
9
|
+
|
|
10
|
+
## Pattern Overview
|
|
11
|
+
|
|
12
|
+
The external package provides a `_branding.py` module that:
|
|
13
|
+
1. Reads brand name from environment variable
|
|
14
|
+
2. Derives environment variable prefix from brand name
|
|
15
|
+
3. Provides helper functions for rebranding text/docstrings
|
|
16
|
+
|
|
17
|
+
## Implementation Template
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# external_package/_branding.py
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
# Environment variables for branding
|
|
27
|
+
# Parent package sets these before importing
|
|
28
|
+
BRAND_NAME = os.environ.get("{PACKAGE}_BRAND", "{package}")
|
|
29
|
+
BRAND_ALIAS = os.environ.get("{PACKAGE}_ALIAS", "{alias}")
|
|
30
|
+
|
|
31
|
+
# Original values for replacement
|
|
32
|
+
_ORIGINAL_NAME = "{package}"
|
|
33
|
+
_ORIGINAL_ALIAS = "{alias}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _brand_to_env_prefix(brand: str) -> str:
|
|
37
|
+
"""Convert brand name to environment variable prefix.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
"figrecipe" -> "FIGRECIPE"
|
|
41
|
+
"scitex.plt" -> "SCITEX_PLT"
|
|
42
|
+
"crossref-local" -> "CROSSREF_LOCAL"
|
|
43
|
+
"""
|
|
44
|
+
return brand.upper().replace(".", "_").replace("-", "_")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Environment variable prefix based on brand
|
|
48
|
+
ENV_PREFIX = _brand_to_env_prefix(BRAND_NAME)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_env(key: str, default: Optional[str] = None) -> Optional[str]:
|
|
52
|
+
"""Get environment variable with brand-aware prefix.
|
|
53
|
+
|
|
54
|
+
Checks {ENV_PREFIX}_{key} first, then falls back to original prefix.
|
|
55
|
+
"""
|
|
56
|
+
value = os.environ.get(f"{ENV_PREFIX}_{key}")
|
|
57
|
+
if value is not None:
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
# Fall back to original prefix if different
|
|
61
|
+
original_prefix = _brand_to_env_prefix(_ORIGINAL_NAME)
|
|
62
|
+
if ENV_PREFIX != original_prefix:
|
|
63
|
+
value = os.environ.get(f"{original_prefix}_{key}")
|
|
64
|
+
if value is not None:
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
return default
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def rebrand_text(text: Optional[str]) -> Optional[str]:
|
|
71
|
+
"""Apply branding to a text string (docstrings, error messages)."""
|
|
72
|
+
if text is None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
if BRAND_NAME == _ORIGINAL_NAME and BRAND_ALIAS == _ORIGINAL_ALIAS:
|
|
76
|
+
return text
|
|
77
|
+
|
|
78
|
+
result = text
|
|
79
|
+
|
|
80
|
+
# Replace import statements
|
|
81
|
+
result = re.sub(
|
|
82
|
+
rf"import\s+{_ORIGINAL_NAME}\s+as\s+{_ORIGINAL_ALIAS}",
|
|
83
|
+
f"import {BRAND_NAME} as {BRAND_ALIAS}",
|
|
84
|
+
result,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Replace "from package" statements
|
|
88
|
+
result = re.sub(
|
|
89
|
+
rf"from\s+{_ORIGINAL_NAME}(\s+import|\s*\.)",
|
|
90
|
+
lambda m: f"from {BRAND_NAME}{m.group(1)}",
|
|
91
|
+
result,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_mcp_server_name() -> str:
|
|
98
|
+
"""Get the MCP server name based on branding."""
|
|
99
|
+
return BRAND_NAME.replace(".", "-")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Usage in Parent Package (scitex)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# scitex/plt/__init__.py
|
|
106
|
+
import os
|
|
107
|
+
|
|
108
|
+
# Set branding BEFORE importing the external package
|
|
109
|
+
os.environ["FIGRECIPE_BRAND"] = "scitex.plt"
|
|
110
|
+
os.environ["FIGRECIPE_ALIAS"] = "plt"
|
|
111
|
+
|
|
112
|
+
# Now import - docstrings will show scitex.plt instead of figrecipe
|
|
113
|
+
from figrecipe import *
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Port Scheme
|
|
117
|
+
|
|
118
|
+
SciTeX uses port scheme 3129X (sa-i-te-ku-su → 3-1-2-9 in Japanese):
|
|
119
|
+
|
|
120
|
+
| Port | Service |
|
|
121
|
+
|-------|------------------|
|
|
122
|
+
| 31290 | scitex-cloud |
|
|
123
|
+
| 31291 | crossref-local |
|
|
124
|
+
| 31292 | openalex |
|
|
125
|
+
| 31293 | scitex-audio |
|
|
126
|
+
|
|
127
|
+
## Environment Variable Pattern
|
|
128
|
+
|
|
129
|
+
External packages should use `{ENV_PREFIX}_{SETTING}`:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
SCITEX_PLT_MODE=local
|
|
133
|
+
CROSSREF_LOCAL_API_URL=http://localhost:31291
|
|
134
|
+
SCITEX_AUDIO_RELAY_URL=http://localhost:31293
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Example: crossref-local
|
|
138
|
+
|
|
139
|
+
See GitHub Issue: https://github.com/ywatanabe1989/crossref-local/issues/11
|
|
140
|
+
|
|
141
|
+
The crossref-local package should implement:
|
|
142
|
+
- `CROSSREF_LOCAL_BRAND` / `CROSSREF_LOCAL_ALIAS` env vars
|
|
143
|
+
- Dynamic `ENV_PREFIX` derived from brand name
|
|
144
|
+
- When used via scitex.scholar, shows `scitex.scholar` in docs
|
|
145
|
+
|
|
146
|
+
## References
|
|
147
|
+
|
|
148
|
+
- figrecipe/_branding.py - Reference implementation
|
|
149
|
+
- scitex/audio/_branding.py - Simple internal module (no rebranding needed)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: 2026-01-22
|
|
4
|
+
# File: src/scitex/scholar/examples/00_run_all.sh
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
# Run all scholar examples in sequence
|
|
7
|
+
# ----------------------------------------
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<EOF
|
|
15
|
+
Usage: $(basename "$0") [OPTIONS]
|
|
16
|
+
|
|
17
|
+
Run all scholar module examples in sequence.
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-h, --help Show this help message
|
|
21
|
+
--dry-run Show commands without executing
|
|
22
|
+
--skip-auth Skip authentication example (01_auth.py)
|
|
23
|
+
--quick Run only quick examples (00, 01, 02)
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
$(basename "$0") # Run all examples
|
|
27
|
+
$(basename "$0") --quick # Run quick examples only
|
|
28
|
+
$(basename "$0") --dry-run # Show what would run
|
|
29
|
+
|
|
30
|
+
EOF
|
|
31
|
+
exit 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
DRY_RUN=false
|
|
35
|
+
SKIP_AUTH=false
|
|
36
|
+
QUICK=false
|
|
37
|
+
|
|
38
|
+
while [[ $# -gt 0 ]]; do
|
|
39
|
+
case $1 in
|
|
40
|
+
-h | --help) usage ;;
|
|
41
|
+
--dry-run)
|
|
42
|
+
DRY_RUN=true
|
|
43
|
+
shift
|
|
44
|
+
;;
|
|
45
|
+
--skip-auth)
|
|
46
|
+
SKIP_AUTH=true
|
|
47
|
+
shift
|
|
48
|
+
;;
|
|
49
|
+
--quick)
|
|
50
|
+
QUICK=true
|
|
51
|
+
shift
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
echo "Unknown option: $1"
|
|
55
|
+
usage
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
run_example() {
|
|
61
|
+
local script="$1"
|
|
62
|
+
shift
|
|
63
|
+
local args=("$@")
|
|
64
|
+
|
|
65
|
+
if [[ ! -f "$SCRIPT_DIR/$script" ]]; then
|
|
66
|
+
echo "[SKIP] $script not found"
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo ""
|
|
71
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
72
|
+
echo "Running: $script ${args[*]}"
|
|
73
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
74
|
+
|
|
75
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
76
|
+
echo "[DRY-RUN] python $SCRIPT_DIR/$script ${args[*]}"
|
|
77
|
+
else
|
|
78
|
+
python "$SCRIPT_DIR/$script" "${args[@]}"
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
echo "SciTeX Scholar Examples Runner"
|
|
83
|
+
echo "==============================="
|
|
84
|
+
|
|
85
|
+
# Core examples
|
|
86
|
+
run_example "00_config.py"
|
|
87
|
+
|
|
88
|
+
if [[ "$SKIP_AUTH" != "true" ]]; then
|
|
89
|
+
run_example "01_auth.py"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
run_example "02_browser.py"
|
|
93
|
+
|
|
94
|
+
if [[ "$QUICK" == "true" ]]; then
|
|
95
|
+
echo ""
|
|
96
|
+
echo "Quick mode: Stopping after basic examples"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Engine examples
|
|
101
|
+
run_example "03_01-engine.py"
|
|
102
|
+
run_example "03_02-engine-for-bibtex.py" --no-cache
|
|
103
|
+
|
|
104
|
+
# URL finding examples
|
|
105
|
+
run_example "04_01-url.py" --no-cache
|
|
106
|
+
run_example "04_02-url-for-bibtex.py" --no-cache-url-finder --n-samples 3
|
|
107
|
+
|
|
108
|
+
# Download examples
|
|
109
|
+
run_example "05_download_pdf.py"
|
|
110
|
+
run_example "06_find_and_download.py"
|
|
111
|
+
|
|
112
|
+
# Storage integration
|
|
113
|
+
run_example "07_storage_integration.py"
|
|
114
|
+
|
|
115
|
+
echo ""
|
|
116
|
+
echo "==============================="
|
|
117
|
+
echo "All examples completed!"
|
|
118
|
+
echo "==============================="
|
|
119
|
+
|
|
120
|
+
# EOF
|
|
@@ -59,15 +59,39 @@ async def fetch_single_executor(
|
|
|
59
59
|
force=force,
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
+
# Granular success flags
|
|
63
|
+
has_doi = bool(paper and paper.metadata.id.doi)
|
|
64
|
+
has_metadata = bool(paper and paper.metadata.basic.title)
|
|
65
|
+
has_pdf = bool(symlink_path)
|
|
66
|
+
has_content = bool(
|
|
67
|
+
paper
|
|
68
|
+
and hasattr(paper, "container")
|
|
69
|
+
and paper.container.pdf_size_bytes
|
|
70
|
+
and paper.container.pdf_size_bytes > 0
|
|
71
|
+
)
|
|
72
|
+
pdf_method = None
|
|
73
|
+
if paper and paper.metadata.path.pdfs_engines:
|
|
74
|
+
pdf_method = paper.metadata.path.pdfs_engines[0]
|
|
75
|
+
|
|
62
76
|
if progress_callback:
|
|
63
|
-
progress_callback(
|
|
77
|
+
progress_callback(
|
|
78
|
+
completed=1, message="Completed" if has_pdf else "Completed (no PDF)"
|
|
79
|
+
)
|
|
64
80
|
|
|
65
81
|
return {
|
|
66
|
-
"success":
|
|
82
|
+
"success": has_pdf, # Overall success = PDF obtained
|
|
83
|
+
"success_doi": has_doi,
|
|
84
|
+
"success_metadata": has_metadata,
|
|
85
|
+
"success_pdf": has_pdf,
|
|
86
|
+
"success_content": has_content,
|
|
87
|
+
"pdf_method": pdf_method,
|
|
88
|
+
"message": "Paper fetched"
|
|
89
|
+
if has_pdf
|
|
90
|
+
else "Metadata fetched but PDF not downloaded",
|
|
67
91
|
"doi": paper.metadata.id.doi if paper else None,
|
|
68
92
|
"title": paper.metadata.basic.title if paper else None,
|
|
69
93
|
"path": str(symlink_path) if symlink_path else None,
|
|
70
|
-
"has_pdf":
|
|
94
|
+
"has_pdf": has_pdf,
|
|
71
95
|
"timestamp": datetime.now().isoformat(),
|
|
72
96
|
}
|
|
73
97
|
|