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.
Files changed (218) hide show
  1. scitex/__init__.py +47 -0
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +191 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/writer.py +21 -204
  17. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  18. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  19. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  20. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  21. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  22. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  23. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  24. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  25. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  26. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  27. scitex/audio/README.md +40 -36
  28. scitex/audio/__init__.py +127 -59
  29. scitex/audio/_branding.py +185 -0
  30. scitex/audio/_mcp/__init__.py +32 -0
  31. scitex/audio/_mcp/handlers.py +59 -6
  32. scitex/audio/_mcp/speak_handlers.py +238 -0
  33. scitex/audio/_relay.py +225 -0
  34. scitex/audio/engines/elevenlabs_engine.py +6 -1
  35. scitex/audio/mcp_server.py +228 -75
  36. scitex/canvas/README.md +1 -1
  37. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  38. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  39. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  40. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  41. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  42. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  43. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  44. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  45. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  46. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  47. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  48. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  49. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  50. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  51. scitex/cli/__init__.py +38 -43
  52. scitex/cli/audio.py +76 -27
  53. scitex/cli/capture.py +13 -20
  54. scitex/cli/introspect.py +443 -0
  55. scitex/cli/main.py +198 -109
  56. scitex/cli/mcp.py +60 -34
  57. scitex/cli/scholar/__init__.py +8 -0
  58. scitex/cli/scholar/_crossref_scitex.py +296 -0
  59. scitex/cli/scholar/_fetch.py +25 -3
  60. scitex/cli/social.py +314 -0
  61. scitex/cli/writer.py +117 -0
  62. scitex/config/README.md +1 -1
  63. scitex/config/__init__.py +16 -2
  64. scitex/config/_env_registry.py +191 -0
  65. scitex/diagram/__init__.py +42 -19
  66. scitex/diagram/mcp_server.py +13 -125
  67. scitex/introspect/__init__.py +75 -0
  68. scitex/introspect/_call_graph.py +303 -0
  69. scitex/introspect/_class_hierarchy.py +163 -0
  70. scitex/introspect/_core.py +42 -0
  71. scitex/introspect/_docstring.py +131 -0
  72. scitex/introspect/_examples.py +113 -0
  73. scitex/introspect/_imports.py +271 -0
  74. scitex/introspect/_mcp/__init__.py +37 -0
  75. scitex/introspect/_mcp/handlers.py +208 -0
  76. scitex/introspect/_members.py +151 -0
  77. scitex/introspect/_resolve.py +89 -0
  78. scitex/introspect/_signature.py +131 -0
  79. scitex/introspect/_source.py +80 -0
  80. scitex/introspect/_type_hints.py +172 -0
  81. scitex/io/bundle/README.md +1 -1
  82. scitex/mcp_server.py +98 -5
  83. scitex/plt/__init__.py +248 -550
  84. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  85. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  86. scitex/plt/gallery/README.md +1 -1
  87. scitex/plt/utils/_hitmap/__init__.py +82 -0
  88. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  89. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  90. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  91. scitex/plt/utils/_hitmap/_constants.py +40 -0
  92. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  93. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  94. scitex/plt/utils/_hitmap/_query.py +113 -0
  95. scitex/plt/utils/_hitmap.py +46 -1616
  96. scitex/plt/utils/_metadata/__init__.py +80 -0
  97. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  98. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  99. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  100. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  101. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  102. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  103. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  104. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  105. scitex/plt/utils/_metadata/_csv.py +416 -0
  106. scitex/plt/utils/_metadata/_detect.py +225 -0
  107. scitex/plt/utils/_metadata/_legend.py +127 -0
  108. scitex/plt/utils/_metadata/_rounding.py +117 -0
  109. scitex/plt/utils/_metadata/_verification.py +202 -0
  110. scitex/schema/README.md +1 -1
  111. scitex/scholar/__init__.py +8 -0
  112. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  113. scitex/scholar/core/Scholar.py +63 -1700
  114. scitex/scholar/core/_mixins/__init__.py +36 -0
  115. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  116. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  117. scitex/scholar/core/_mixins/_loaders.py +103 -0
  118. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  119. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  120. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  121. scitex/scholar/core/_mixins/_savers.py +69 -0
  122. scitex/scholar/core/_mixins/_search.py +103 -0
  123. scitex/scholar/core/_mixins/_services.py +88 -0
  124. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  125. scitex/scholar/crossref_scitex.py +367 -0
  126. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  127. scitex/scholar/examples/00_run_all.sh +120 -0
  128. scitex/scholar/jobs/_executors.py +27 -3
  129. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  130. scitex/scholar/pdf_download/_cli.py +154 -0
  131. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  132. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  133. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  134. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  135. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  136. scitex/scholar/pipelines/_single_steps.py +71 -36
  137. scitex/scholar/storage/_LibraryManager.py +97 -1695
  138. scitex/scholar/storage/_mixins/__init__.py +30 -0
  139. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  140. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  141. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  142. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  143. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  144. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  145. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  146. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  147. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  148. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  149. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  150. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  151. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  152. scitex/security/README.md +3 -3
  153. scitex/session/README.md +1 -1
  154. scitex/sh/README.md +1 -1
  155. scitex/social/__init__.py +153 -0
  156. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  157. scitex/template/README.md +1 -1
  158. scitex/template/clone_writer_directory.py +5 -5
  159. scitex/writer/README.md +1 -1
  160. scitex/writer/_mcp/handlers.py +11 -744
  161. scitex/writer/_mcp/tool_schemas.py +5 -335
  162. scitex-2.15.1.dist-info/METADATA +648 -0
  163. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
  164. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  165. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  166. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  167. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  168. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  169. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  170. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  171. scitex/diagram/_compile.py +0 -312
  172. scitex/diagram/_diagram.py +0 -355
  173. scitex/diagram/_mcp/__init__.py +0 -4
  174. scitex/diagram/_mcp/handlers.py +0 -400
  175. scitex/diagram/_mcp/tool_schemas.py +0 -157
  176. scitex/diagram/_presets.py +0 -173
  177. scitex/diagram/_schema.py +0 -182
  178. scitex/diagram/_split.py +0 -278
  179. scitex/plt/_mcp/__init__.py +0 -4
  180. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  181. scitex/plt/_mcp/_handlers_figure.py +0 -195
  182. scitex/plt/_mcp/_handlers_plot.py +0 -252
  183. scitex/plt/_mcp/_handlers_style.py +0 -219
  184. scitex/plt/_mcp/handlers.py +0 -74
  185. scitex/plt/_mcp/tool_schemas.py +0 -497
  186. scitex/plt/mcp_server.py +0 -231
  187. scitex/scholar/data/.gitkeep +0 -0
  188. scitex/scholar/data/README.md +0 -44
  189. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  190. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  191. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  192. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  193. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  194. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  195. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  196. scitex/scholar/data/bib_files/pac.bib +0 -698
  197. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  198. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  199. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  200. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  201. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  202. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  203. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  204. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  205. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  206. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  207. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  208. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  209. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  210. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  211. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  212. scitex/scholar/data/impact_factor.db +0 -0
  213. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  214. scitex/scholar/examples/dev.py +0 -38
  215. scitex-2.14.0.dist-info/METADATA +0 -1238
  216. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
  217. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
  218. {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(completed=1, message="Completed")
77
+ progress_callback(
78
+ completed=1, message="Completed" if has_pdf else "Completed (no PDF)"
79
+ )
64
80
 
65
81
  return {
66
- "success": True,
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": bool(symlink_path),
94
+ "has_pdf": has_pdf,
71
95
  "timestamp": datetime.now().isoformat(),
72
96
  }
73
97