scitex 2.15.3__py3-none-any.whl → 2.15.4__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,212 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/_mcp/openalex_handlers.py
4
+ """OpenAlex-SciTeX handler implementations via openalex-local delegation.
5
+
6
+ These handlers delegate to openalex-local for fast access to 250M+ papers.
7
+ Branded as openalex-scitex to distinguish from official OpenAlex API.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from datetime import datetime
14
+
15
+ __all__ = [
16
+ "openalex_search_handler",
17
+ "openalex_get_handler",
18
+ "openalex_count_handler",
19
+ "openalex_info_handler",
20
+ ]
21
+
22
+
23
+ def _ensure_openalex():
24
+ """Ensure openalex_scitex module is available."""
25
+ try:
26
+ from scitex.scholar import openalex_scitex
27
+
28
+ return openalex_scitex
29
+ except ImportError as e:
30
+ raise RuntimeError(
31
+ "openalex-local not installed. Install with: pip install openalex-local"
32
+ ) from e
33
+
34
+
35
+ async def openalex_search_handler(
36
+ query: str,
37
+ limit: int = 20,
38
+ offset: int = 0,
39
+ year_min: int | None = None,
40
+ year_max: int | None = None,
41
+ ) -> dict:
42
+ """Search OpenAlex database (250M+ papers) via openalex-local.
43
+
44
+ Args:
45
+ query: Search query string (full-text search)
46
+ limit: Maximum number of results (default: 20)
47
+ offset: Number of results to skip for pagination
48
+ year_min: Minimum publication year filter
49
+ year_max: Maximum publication year filter
50
+ """
51
+ try:
52
+ openalex = _ensure_openalex()
53
+ loop = asyncio.get_running_loop()
54
+
55
+ def do_search():
56
+ # Fetch more results for filtering
57
+ fetch_limit = limit * 2 if (year_min or year_max) else limit
58
+ results = openalex.search(query, limit=fetch_limit, offset=offset)
59
+
60
+ papers = []
61
+ for work in results:
62
+ # Apply year filters
63
+ if year_min and work.year and work.year < year_min:
64
+ continue
65
+ if year_max and work.year and work.year > year_max:
66
+ continue
67
+
68
+ papers.append(
69
+ {
70
+ "doi": work.doi,
71
+ "title": work.title,
72
+ "authors": work.authors[:10] if work.authors else [],
73
+ "year": work.year,
74
+ "journal": work.journal,
75
+ "abstract": (
76
+ work.abstract[:500] + "..."
77
+ if work.abstract and len(work.abstract) > 500
78
+ else work.abstract
79
+ ),
80
+ "citation_count": getattr(work, "citation_count", None),
81
+ "reference_count": getattr(work, "reference_count", None),
82
+ "type": getattr(work, "type", None),
83
+ "openalex_id": getattr(work, "openalex_id", None),
84
+ }
85
+ )
86
+ if len(papers) >= limit:
87
+ break
88
+
89
+ return papers, getattr(results, "total", len(papers))
90
+
91
+ papers, total = await loop.run_in_executor(None, do_search)
92
+
93
+ return {
94
+ "success": True,
95
+ "query": query,
96
+ "total": total,
97
+ "count": len(papers),
98
+ "offset": offset,
99
+ "limit": limit,
100
+ "papers": papers,
101
+ "source": "openalex_local",
102
+ "timestamp": datetime.now().isoformat(),
103
+ }
104
+
105
+ except Exception as e:
106
+ return {"success": False, "error": str(e)}
107
+
108
+
109
+ async def openalex_get_handler(
110
+ doi: str = None,
111
+ openalex_id: str = None,
112
+ ) -> dict:
113
+ """Get a paper by DOI or OpenAlex ID from OpenAlex database.
114
+
115
+ Args:
116
+ doi: DOI of the paper (e.g., '10.1038/nature12373')
117
+ openalex_id: OpenAlex ID (e.g., 'W2100837269')
118
+ """
119
+ if not doi and not openalex_id:
120
+ return {"success": False, "error": "Must provide either doi or openalex_id"}
121
+
122
+ try:
123
+ openalex = _ensure_openalex()
124
+ loop = asyncio.get_running_loop()
125
+
126
+ def do_get():
127
+ identifier = doi or openalex_id
128
+ work = openalex.get(identifier)
129
+ if not work:
130
+ return None
131
+
132
+ result = {
133
+ "doi": work.doi,
134
+ "title": work.title,
135
+ "authors": work.authors,
136
+ "year": work.year,
137
+ "journal": work.journal,
138
+ "abstract": work.abstract,
139
+ "citation_count": getattr(work, "citation_count", None),
140
+ "reference_count": getattr(work, "reference_count", None),
141
+ "type": getattr(work, "type", None),
142
+ "openalex_id": getattr(work, "openalex_id", None),
143
+ "publisher": getattr(work, "publisher", None),
144
+ "url": getattr(work, "url", None),
145
+ }
146
+
147
+ return result
148
+
149
+ result = await loop.run_in_executor(None, do_get)
150
+
151
+ if result is None:
152
+ identifier = doi or openalex_id
153
+ return {
154
+ "success": False,
155
+ "error": f"Paper not found: {identifier}",
156
+ "identifier": identifier,
157
+ }
158
+
159
+ return {
160
+ "success": True,
161
+ "paper": result,
162
+ "source": "openalex_local",
163
+ "timestamp": datetime.now().isoformat(),
164
+ }
165
+
166
+ except Exception as e:
167
+ return {"success": False, "error": str(e)}
168
+
169
+
170
+ async def openalex_count_handler(query: str) -> dict:
171
+ """Count papers matching a search query.
172
+
173
+ Args:
174
+ query: Search query string
175
+ """
176
+ try:
177
+ openalex = _ensure_openalex()
178
+ loop = asyncio.get_running_loop()
179
+
180
+ count = await loop.run_in_executor(None, openalex.count, query)
181
+
182
+ return {
183
+ "success": True,
184
+ "query": query,
185
+ "count": count,
186
+ "source": "openalex_local",
187
+ "timestamp": datetime.now().isoformat(),
188
+ }
189
+
190
+ except Exception as e:
191
+ return {"success": False, "error": str(e)}
192
+
193
+
194
+ async def openalex_info_handler() -> dict:
195
+ """Get information about OpenAlex database configuration and status."""
196
+ try:
197
+ openalex = _ensure_openalex()
198
+ loop = asyncio.get_running_loop()
199
+
200
+ info = await loop.run_in_executor(None, openalex.info)
201
+
202
+ return {
203
+ "success": True,
204
+ "info": info,
205
+ "timestamp": datetime.now().isoformat(),
206
+ }
207
+
208
+ except Exception as e:
209
+ return {"success": False, "error": str(e)}
210
+
211
+
212
+ # EOF
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/_mcp/openalex_tool_schemas.py
4
+ """OpenAlex-Local tool schemas for MCP server.
5
+
6
+ Provides access to 250M+ papers via openalex-local database.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import mcp.types as types
12
+
13
+ __all__ = ["get_openalex_tool_schemas"]
14
+
15
+
16
+ def get_openalex_tool_schemas() -> list[types.Tool]:
17
+ """Return OpenAlex-Local tool schemas."""
18
+ return [
19
+ types.Tool(
20
+ name="openalex_search",
21
+ description=(
22
+ "Search OpenAlex database (250M+ papers) via openalex-local. "
23
+ "Fast full-text search with year filtering. Includes citation data."
24
+ ),
25
+ inputSchema={
26
+ "type": "object",
27
+ "properties": {
28
+ "query": {
29
+ "type": "string",
30
+ "description": "Search query string (full-text search)",
31
+ },
32
+ "limit": {
33
+ "type": "integer",
34
+ "description": "Maximum number of results",
35
+ "default": 20,
36
+ },
37
+ "offset": {
38
+ "type": "integer",
39
+ "description": "Number of results to skip for pagination",
40
+ "default": 0,
41
+ },
42
+ "year_min": {
43
+ "type": "integer",
44
+ "description": "Minimum publication year filter",
45
+ },
46
+ "year_max": {
47
+ "type": "integer",
48
+ "description": "Maximum publication year filter",
49
+ },
50
+ },
51
+ "required": ["query"],
52
+ },
53
+ ),
54
+ types.Tool(
55
+ name="openalex_get",
56
+ description="Get a paper by DOI or OpenAlex ID from OpenAlex database.",
57
+ inputSchema={
58
+ "type": "object",
59
+ "properties": {
60
+ "doi": {
61
+ "type": "string",
62
+ "description": "DOI of the paper (e.g., '10.1038/nature12373')",
63
+ },
64
+ "openalex_id": {
65
+ "type": "string",
66
+ "description": "OpenAlex ID (e.g., 'W2100837269')",
67
+ },
68
+ },
69
+ },
70
+ ),
71
+ types.Tool(
72
+ name="openalex_count",
73
+ description="Count papers matching a search query in OpenAlex database.",
74
+ inputSchema={
75
+ "type": "object",
76
+ "properties": {
77
+ "query": {
78
+ "type": "string",
79
+ "description": "Search query string",
80
+ },
81
+ },
82
+ "required": ["query"],
83
+ },
84
+ ),
85
+ types.Tool(
86
+ name="openalex_info",
87
+ description="Get OpenAlex database configuration and status.",
88
+ inputSchema={
89
+ "type": "object",
90
+ "properties": {},
91
+ },
92
+ ),
93
+ ]
94
+
95
+
96
+ # EOF
@@ -3,9 +3,14 @@
3
3
  # File: src/scitex/scholar/_mcp.tool_schemas.py
4
4
  # ----------------------------------------
5
5
  """Tool schemas for the scitex-scholar MCP server."""
6
+
6
7
  from __future__ import annotations
8
+
7
9
  import mcp.types as types
10
+
8
11
  __all__ = ["get_tool_schemas"]
12
+
13
+
9
14
  def get_tool_schemas() -> list[types.Tool]:
10
15
  """Return all tool schemas for the Scholar MCP server."""
11
16
  base_schemas = [
@@ -500,6 +505,16 @@ def get_tool_schemas() -> list[types.Tool]:
500
505
  },
501
506
  ),
502
507
  ]
508
+ from .crossref_tool_schemas import get_crossref_tool_schemas
503
509
  from .job_tool_schemas import get_job_tool_schemas
504
- return base_schemas + get_job_tool_schemas()
510
+ from .openalex_tool_schemas import get_openalex_tool_schemas
511
+
512
+ return (
513
+ base_schemas
514
+ + get_job_tool_schemas()
515
+ + get_crossref_tool_schemas()
516
+ + get_openalex_tool_schemas()
517
+ )
518
+
519
+
505
520
  # EOF
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/__init__.py
4
+ """Local database integrations for scitex.scholar.
5
+
6
+ This package provides access to local scholarly databases:
7
+ - crossref_scitex: CrossRef database (167M+ papers via crossref-local)
8
+ - openalex_scitex: OpenAlex database (284M+ works via openalex-local)
9
+
10
+ Both modules delegate directly to their respective external packages
11
+ without any additional logic.
12
+
13
+ Usage:
14
+ >>> from scitex.scholar.local_dbs import crossref_scitex
15
+ >>> results = crossref_scitex.search("machine learning")
16
+
17
+ >>> from scitex.scholar.local_dbs import openalex_scitex
18
+ >>> results = openalex_scitex.search("neural networks")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from . import crossref_scitex, openalex_scitex
24
+
25
+ __all__ = [
26
+ "crossref_scitex",
27
+ "openalex_scitex",
28
+ ]
29
+
30
+
31
+ # EOF
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/crossref_scitex.py
4
+ """CrossRef-SciTeX: Minimal API for crossref-local.
5
+
6
+ Usage:
7
+ >>> from scitex.scholar.local_dbs import crossref_scitex
8
+ >>> results = crossref_scitex.search("machine learning")
9
+ >>> work = crossref_scitex.get("10.1038/nature12373")
10
+ """
11
+
12
+ try:
13
+ from crossref_local import (
14
+ SearchResult,
15
+ # Classes
16
+ Work,
17
+ count,
18
+ get,
19
+ info,
20
+ # Core functions
21
+ search,
22
+ )
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "crossref-local not installed. Install with: pip install crossref-local"
26
+ ) from e
27
+
28
+ __all__ = ["search", "get", "count", "info", "Work", "SearchResult"]
29
+
30
+ # EOF
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/openalex_scitex.py
4
+ """OpenAlex-SciTeX: Minimal API for openalex-local.
5
+
6
+ Usage:
7
+ >>> from scitex.scholar.local_dbs import openalex_scitex
8
+ >>> results = openalex_scitex.search("machine learning")
9
+ >>> work = openalex_scitex.get("10.1038/nature12373")
10
+ """
11
+
12
+ try:
13
+ from openalex_local import (
14
+ SearchResult,
15
+ # Classes
16
+ Work,
17
+ count,
18
+ get,
19
+ info,
20
+ # Core functions
21
+ search,
22
+ )
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "openalex-local not installed. Install with: pip install openalex-local"
26
+ ) from e
27
+
28
+ __all__ = ["search", "get", "count", "info", "Work", "SearchResult"]
29
+
30
+ # EOF
@@ -3,8 +3,17 @@
3
3
  # File: src/scitex/scholar/mcp_server.py
4
4
  # ----------------------------------------
5
5
 
6
- """
7
- MCP Server for SciTeX Scholar - Scientific Literature Management
6
+ """MCP Server for SciTeX Scholar - Scientific Literature Management.
7
+
8
+ .. deprecated::
9
+ This standalone server is deprecated. Use the unified scitex MCP server instead:
10
+
11
+ CLI: scitex serve
12
+ Python: from scitex.mcp_server import run_server
13
+
14
+ The unified server includes all scholar tools plus other scitex tools.
15
+ Scholar tools are prefixed with 'scholar_' (e.g., scholar_search_papers).
16
+ Scholar resources are available at scholar://library and scholar://bibtex.
8
17
 
9
18
  Provides tools for:
10
19
  - Searching papers across multiple databases
@@ -16,6 +25,15 @@ Provides tools for:
16
25
 
17
26
  from __future__ import annotations
18
27
 
28
+ import warnings
29
+
30
+ warnings.warn(
31
+ "scitex.scholar.mcp_server is deprecated. Use 'scitex serve' or "
32
+ "'from scitex.mcp_server import run_server' for the unified MCP server.",
33
+ DeprecationWarning,
34
+ stacklevel=2,
35
+ )
36
+
19
37
  import asyncio
20
38
  import os
21
39
  from datetime import datetime
@@ -67,11 +85,18 @@ class ScholarServer:
67
85
 
68
86
  self._scholar_instance = Scholar()
69
87
  except ImportError as e:
70
- raise RuntimeError(f"Scholar module not available: {e}")
88
+ raise RuntimeError(f"Scholar module not available: {e}") from e
71
89
  return self._scholar_instance
72
90
 
73
91
  def setup_handlers(self):
74
92
  """Set up MCP server handlers."""
93
+ from ._mcp.crossref_handlers import (
94
+ crossref_citations_handler,
95
+ crossref_count_handler,
96
+ crossref_get_handler,
97
+ crossref_info_handler,
98
+ crossref_search_handler,
99
+ )
75
100
  from ._mcp.handlers import (
76
101
  add_papers_to_project_handler,
77
102
  authenticate_handler,
@@ -186,6 +211,36 @@ class ScholarServer:
186
211
  elif name == "get_job_result":
187
212
  return await self._wrap_result(get_job_result_handler(**arguments))
188
213
 
214
+ # CrossRef-Local Tools
215
+ elif name == "crossref_search":
216
+ return await self._wrap_result(crossref_search_handler(**arguments))
217
+ elif name == "crossref_get":
218
+ return await self._wrap_result(crossref_get_handler(**arguments))
219
+ elif name == "crossref_count":
220
+ return await self._wrap_result(crossref_count_handler(**arguments))
221
+ elif name == "crossref_citations":
222
+ return await self._wrap_result(crossref_citations_handler(**arguments))
223
+ elif name == "crossref_info":
224
+ return await self._wrap_result(crossref_info_handler(**arguments))
225
+
226
+ # OpenAlex-Local Tools
227
+ elif name == "openalex_search":
228
+ from ._mcp.openalex_handlers import openalex_search_handler
229
+
230
+ return await self._wrap_result(openalex_search_handler(**arguments))
231
+ elif name == "openalex_get":
232
+ from ._mcp.openalex_handlers import openalex_get_handler
233
+
234
+ return await self._wrap_result(openalex_get_handler(**arguments))
235
+ elif name == "openalex_count":
236
+ from ._mcp.openalex_handlers import openalex_count_handler
237
+
238
+ return await self._wrap_result(openalex_count_handler(**arguments))
239
+ elif name == "openalex_info":
240
+ from ._mcp.openalex_handlers import openalex_info_handler
241
+
242
+ return await self._wrap_result(openalex_info_handler(**arguments))
243
+
189
244
  else:
190
245
  raise ValueError(f"Unknown tool: {name}")
191
246
 
@@ -340,7 +395,7 @@ async def _run_server():
340
395
 
341
396
 
342
397
  def main():
343
- """Main entry point for the MCP server."""
398
+ """Run the MCP server."""
344
399
  if not MCP_AVAILABLE:
345
400
  import sys
346
401
 
@@ -3,8 +3,12 @@
3
3
  # File: src/scitex/stats/mcp_server.py
4
4
  # ----------------------------------------
5
5
 
6
- """
7
- MCP Server for SciTeX Stats - Statistical Testing Framework
6
+ """MCP Server for SciTeX Stats - Statistical Testing Framework.
7
+
8
+ .. deprecated::
9
+ This standalone server is deprecated. Use the unified scitex MCP server:
10
+ CLI: scitex serve
11
+ Python: from scitex.mcp_server import run_server
8
12
 
9
13
  Provides tools for:
10
14
  - Recommending appropriate statistical tests
@@ -20,6 +24,15 @@ Provides tools for:
20
24
 
21
25
  from __future__ import annotations
22
26
 
27
+ import warnings
28
+
29
+ warnings.warn(
30
+ "scitex.stats.mcp_server is deprecated. Use 'scitex serve' or "
31
+ "'from scitex.mcp_server import run_server' for the unified MCP server.",
32
+ DeprecationWarning,
33
+ stacklevel=2,
34
+ )
35
+
23
36
  import asyncio
24
37
 
25
38
  # Graceful MCP dependency handling
@@ -380,7 +393,7 @@ async def _run_server():
380
393
 
381
394
 
382
395
  def main():
383
- """Main entry point for the MCP server."""
396
+ """Run the MCP server."""
384
397
  if not MCP_AVAILABLE:
385
398
  import sys
386
399
 
@@ -3,8 +3,12 @@
3
3
  # File: src/scitex/template/mcp_server.py
4
4
  # ----------------------------------------
5
5
 
6
- """
7
- MCP Server for SciTeX Template - Project Scaffolding Framework
6
+ """MCP Server for SciTeX Template - Project Scaffolding Framework.
7
+
8
+ .. deprecated::
9
+ This standalone server is deprecated. Use the unified scitex MCP server:
10
+ CLI: scitex serve
11
+ Python: from scitex.mcp_server import run_server
8
12
 
9
13
  Provides tools for:
10
14
  - Listing available project templates
@@ -15,6 +19,15 @@ Provides tools for:
15
19
 
16
20
  from __future__ import annotations
17
21
 
22
+ import warnings
23
+
24
+ warnings.warn(
25
+ "scitex.template.mcp_server is deprecated. Use 'scitex serve' or "
26
+ "'from scitex.mcp_server import run_server' for the unified MCP server.",
27
+ DeprecationWarning,
28
+ stacklevel=2,
29
+ )
30
+
18
31
  import asyncio
19
32
 
20
33
  # Graceful MCP dependency handling
@@ -161,7 +174,7 @@ async def _run_server():
161
174
 
162
175
 
163
176
  def main():
164
- """Main entry point for the MCP server."""
177
+ """Run the MCP server."""
165
178
  if not MCP_AVAILABLE:
166
179
  import sys
167
180
 
scitex/ui/mcp_server.py CHANGED
@@ -2,14 +2,27 @@
2
2
  # Timestamp: "2026-01-13 (ywatanabe)"
3
3
  # File: /home/ywatanabe/proj/scitex-code/src/scitex/ui/mcp_server.py
4
4
 
5
- """
6
- MCP Server for SciTeX Notifications - Multi-backend alert system.
5
+ """MCP Server for SciTeX Notifications - Multi-backend alert system.
6
+
7
+ .. deprecated::
8
+ This standalone server is deprecated. Use the unified scitex MCP server:
9
+ CLI: scitex serve
10
+ Python: from scitex.mcp_server import run_server
7
11
 
8
12
  Supports: audio, desktop, email, matplotlib, playwright, webhook backends.
9
13
  """
10
14
 
11
15
  from __future__ import annotations
12
16
 
17
+ import warnings
18
+
19
+ warnings.warn(
20
+ "scitex.ui.mcp_server is deprecated. Use 'scitex serve' or "
21
+ "'from scitex.mcp_server import run_server' for the unified MCP server.",
22
+ DeprecationWarning,
23
+ stacklevel=2,
24
+ )
25
+
13
26
  import asyncio
14
27
  from datetime import datetime
15
28
 
@@ -126,7 +139,7 @@ async def _run_server():
126
139
 
127
140
 
128
141
  def main():
129
- """Main entry point for the MCP server."""
142
+ """Run the MCP server."""
130
143
  if not MCP_AVAILABLE:
131
144
  import sys
132
145
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scitex
3
- Version: 2.15.3
3
+ Version: 2.15.4
4
4
  Summary: A comprehensive Python library for scientific computing and data analysis
5
5
  Project-URL: Homepage, https://github.com/ywatanabe1989/scitex-python
6
6
  Project-URL: Documentation, https://scitex.readthedocs.io