crossref-local 0.3.1__py3-none-any.whl → 0.5.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.
Files changed (48) hide show
  1. crossref_local/__init__.py +38 -16
  2. crossref_local/__main__.py +0 -0
  3. crossref_local/_aio/__init__.py +30 -0
  4. crossref_local/_aio/_impl.py +238 -0
  5. crossref_local/_cache/__init__.py +15 -0
  6. crossref_local/_cache/export.py +100 -0
  7. crossref_local/_cache/utils.py +93 -0
  8. crossref_local/_cache/viz.py +296 -0
  9. crossref_local/_cli/__init__.py +9 -0
  10. crossref_local/_cli/cache.py +179 -0
  11. crossref_local/_cli/cli.py +512 -0
  12. crossref_local/_cli/completion.py +245 -0
  13. crossref_local/_cli/main.py +20 -0
  14. crossref_local/_cli/mcp.py +351 -0
  15. crossref_local/_cli/mcp_server.py +413 -0
  16. crossref_local/_core/__init__.py +58 -0
  17. crossref_local/{api.py → _core/api.py} +130 -36
  18. crossref_local/{citations.py → _core/citations.py} +55 -26
  19. crossref_local/{config.py → _core/config.py} +57 -42
  20. crossref_local/{db.py → _core/db.py} +32 -26
  21. crossref_local/{fts.py → _core/fts.py} +18 -14
  22. crossref_local/{models.py → _core/models.py} +11 -6
  23. crossref_local/{impact_factor → _impact_factor}/__init__.py +0 -0
  24. crossref_local/{impact_factor → _impact_factor}/calculator.py +0 -0
  25. crossref_local/{impact_factor → _impact_factor}/journal_lookup.py +0 -0
  26. crossref_local/_remote/__init__.py +56 -0
  27. crossref_local/_remote/base.py +356 -0
  28. crossref_local/_remote/collections.py +175 -0
  29. crossref_local/_server/__init__.py +140 -0
  30. crossref_local/_server/middleware.py +25 -0
  31. crossref_local/_server/models.py +129 -0
  32. crossref_local/_server/routes_citations.py +98 -0
  33. crossref_local/_server/routes_collections.py +282 -0
  34. crossref_local/_server/routes_compat.py +102 -0
  35. crossref_local/_server/routes_works.py +128 -0
  36. crossref_local/_server/server.py +19 -0
  37. crossref_local/aio.py +30 -206
  38. crossref_local/cache.py +466 -0
  39. crossref_local/cli.py +5 -447
  40. crossref_local/jobs.py +169 -0
  41. crossref_local/mcp_server.py +5 -199
  42. crossref_local/remote.py +5 -261
  43. crossref_local/server.py +5 -349
  44. {crossref_local-0.3.1.dist-info → crossref_local-0.5.0.dist-info}/METADATA +88 -24
  45. crossref_local-0.5.0.dist-info/RECORD +47 -0
  46. crossref_local-0.3.1.dist-info/RECORD +0 -20
  47. {crossref_local-0.3.1.dist-info → crossref_local-0.5.0.dist-info}/WHEEL +0 -0
  48. {crossref_local-0.3.1.dist-info → crossref_local-0.5.0.dist-info}/entry_points.txt +0 -0
crossref_local/server.py CHANGED
@@ -1,352 +1,8 @@
1
- """FastAPI server for CrossRef Local with FTS5 search.
1
+ #!/usr/bin/env python3
2
+ """Backward compatibility: re-export from _server."""
2
3
 
3
- This server provides proper full-text search using FTS5 index,
4
- unlike the Django API which only scans a limited subset.
4
+ from ._server import app, run_server, DEFAULT_PORT, DEFAULT_HOST
5
5
 
6
- Usage:
7
- crossref-local api # Run on default port 3333
8
- crossref-local api --port 8080 # Custom port
6
+ __all__ = ["app", "run_server", "DEFAULT_PORT", "DEFAULT_HOST"]
9
7
 
10
- # Or directly:
11
- uvicorn crossref_local.server:app --host 0.0.0.0 --port 3333
12
- """
13
-
14
- import time
15
- from typing import Optional, List
16
-
17
- from fastapi import FastAPI, Query, HTTPException
18
- from fastapi.middleware.cors import CORSMiddleware
19
- from pydantic import BaseModel
20
-
21
- from . import fts
22
- from .db import get_db
23
- from .models import Work
24
-
25
- app = FastAPI(
26
- title="CrossRef Local API",
27
- description="Fast full-text search across 167M+ scholarly works",
28
- version="1.1.0",
29
- )
30
-
31
- # CORS middleware
32
- app.add_middleware(
33
- CORSMiddleware,
34
- allow_origins=["*"],
35
- allow_methods=["*"],
36
- allow_headers=["*"],
37
- )
38
-
39
-
40
- class WorkResponse(BaseModel):
41
- doi: str
42
- title: Optional[str] = None
43
- authors: List[str] = []
44
- year: Optional[int] = None
45
- journal: Optional[str] = None
46
- issn: Optional[str] = None
47
- volume: Optional[str] = None
48
- issue: Optional[str] = None
49
- page: Optional[str] = None
50
- abstract: Optional[str] = None
51
- citation_count: Optional[int] = None
52
-
53
-
54
- class SearchResponse(BaseModel):
55
- query: str
56
- total: int
57
- returned: int
58
- elapsed_ms: float
59
- results: List[WorkResponse]
60
-
61
-
62
- class InfoResponse(BaseModel):
63
- name: str = "CrossRef Local API"
64
- version: str = "1.1.0"
65
- status: str = "running"
66
- mode: str = "local"
67
- total_papers: int
68
- fts_indexed: int
69
- citations: int
70
- database_path: str
71
-
72
-
73
- @app.get("/")
74
- def root():
75
- """API root with endpoint information."""
76
- return {
77
- "name": "CrossRef Local API",
78
- "version": "1.1.0",
79
- "status": "running",
80
- "endpoints": {
81
- "health": "/health",
82
- "info": "/info",
83
- "search": "/works?q=<query>",
84
- "get_by_doi": "/works/{doi}",
85
- "batch": "/works/batch",
86
- },
87
- }
88
-
89
-
90
- @app.get("/health")
91
- def health():
92
- """Health check endpoint."""
93
- db = get_db()
94
- return {
95
- "status": "healthy",
96
- "database_connected": db is not None,
97
- "database_path": str(db.db_path) if db else None,
98
- }
99
-
100
-
101
- @app.get("/info", response_model=InfoResponse)
102
- def info():
103
- """Get database statistics."""
104
- db = get_db()
105
-
106
- row = db.fetchone("SELECT COUNT(*) as count FROM works")
107
- work_count = row["count"] if row else 0
108
-
109
- try:
110
- row = db.fetchone("SELECT COUNT(*) as count FROM works_fts")
111
- fts_count = row["count"] if row else 0
112
- except Exception:
113
- fts_count = 0
114
-
115
- try:
116
- row = db.fetchone("SELECT COUNT(*) as count FROM citations")
117
- citation_count = row["count"] if row else 0
118
- except Exception:
119
- citation_count = 0
120
-
121
- return InfoResponse(
122
- total_papers=work_count,
123
- fts_indexed=fts_count,
124
- citations=citation_count,
125
- database_path=str(db.db_path),
126
- )
127
-
128
-
129
- @app.get("/works", response_model=SearchResponse)
130
- def search_works(
131
- q: str = Query(..., description="Search query (FTS5 syntax supported)"),
132
- limit: int = Query(10, ge=1, le=100, description="Max results"),
133
- offset: int = Query(0, ge=0, description="Skip first N results"),
134
- ):
135
- """
136
- Full-text search across works.
137
-
138
- Uses FTS5 index for fast searching across titles, abstracts, and authors.
139
- Supports FTS5 query syntax like AND, OR, NOT, "exact phrases".
140
-
141
- Examples:
142
- /works?q=machine learning
143
- /works?q="neural network" AND hippocampus
144
- /works?q=CRISPR&limit=20
145
- """
146
- start = time.perf_counter()
147
-
148
- try:
149
- results = fts.search(q, limit=limit, offset=offset)
150
- except Exception as e:
151
- raise HTTPException(status_code=400, detail=f"Search error: {e}")
152
-
153
- elapsed_ms = (time.perf_counter() - start) * 1000
154
-
155
- return SearchResponse(
156
- query=q,
157
- total=results.total,
158
- returned=len(results.works),
159
- elapsed_ms=round(elapsed_ms, 2),
160
- results=[
161
- WorkResponse(
162
- doi=w.doi,
163
- title=w.title,
164
- authors=w.authors,
165
- year=w.year,
166
- journal=w.journal,
167
- issn=w.issn,
168
- volume=w.volume,
169
- issue=w.issue,
170
- page=w.page,
171
- abstract=w.abstract,
172
- citation_count=w.citation_count,
173
- )
174
- for w in results.works
175
- ],
176
- )
177
-
178
-
179
- @app.get("/works/{doi:path}", response_model=Optional[WorkResponse])
180
- def get_work(doi: str):
181
- """
182
- Get work metadata by DOI.
183
-
184
- Examples:
185
- /works/10.1038/nature12373
186
- /works/10.1016/j.cell.2020.01.001
187
- """
188
- db = get_db()
189
- metadata = db.get_metadata(doi)
190
-
191
- if metadata is None:
192
- raise HTTPException(status_code=404, detail=f"DOI not found: {doi}")
193
-
194
- work = Work.from_metadata(doi, metadata)
195
-
196
- return WorkResponse(
197
- doi=work.doi,
198
- title=work.title,
199
- authors=work.authors,
200
- year=work.year,
201
- journal=work.journal,
202
- issn=work.issn,
203
- volume=work.volume,
204
- issue=work.issue,
205
- page=work.page,
206
- abstract=work.abstract,
207
- citation_count=work.citation_count,
208
- )
209
-
210
-
211
- class BatchRequest(BaseModel):
212
- dois: List[str]
213
-
214
-
215
- class BatchResponse(BaseModel):
216
- requested: int
217
- found: int
218
- results: List[WorkResponse]
219
-
220
-
221
- @app.post("/works/batch", response_model=BatchResponse)
222
- def get_works_batch(request: BatchRequest):
223
- """
224
- Get multiple works by DOI.
225
-
226
- Request body: {"dois": ["10.1038/...", "10.1016/..."]}
227
- """
228
- db = get_db()
229
- results = []
230
-
231
- for doi in request.dois:
232
- metadata = db.get_metadata(doi)
233
- if metadata:
234
- work = Work.from_metadata(doi, metadata)
235
- results.append(
236
- WorkResponse(
237
- doi=work.doi,
238
- title=work.title,
239
- authors=work.authors,
240
- year=work.year,
241
- journal=work.journal,
242
- abstract=work.abstract,
243
- citation_count=work.citation_count,
244
- )
245
- )
246
-
247
- return BatchResponse(
248
- requested=len(request.dois),
249
- found=len(results),
250
- results=results,
251
- )
252
-
253
-
254
- # For backwards compatibility with existing API endpoints
255
- @app.get("/api/search/")
256
- def api_search_compat(
257
- title: Optional[str] = None,
258
- q: Optional[str] = None,
259
- doi: Optional[str] = None,
260
- limit: int = 10,
261
- ):
262
- """Backwards-compatible search endpoint."""
263
- query = title or q
264
-
265
- if doi:
266
- # DOI lookup
267
- try:
268
- work = get_work(doi)
269
- return {
270
- "query": {"doi": doi},
271
- "results": [work.model_dump()],
272
- "total": 1,
273
- "returned": 1,
274
- }
275
- except HTTPException:
276
- return {"query": {"doi": doi}, "results": [], "total": 0, "returned": 0}
277
-
278
- if not query:
279
- raise HTTPException(
280
- status_code=400, detail="Specify q, title, or doi parameter"
281
- )
282
-
283
- # Call fts.search directly (not the endpoint function)
284
- results = fts.search(query, limit=limit, offset=0)
285
- return {
286
- "query": {
287
- "title": query,
288
- "doi": None,
289
- "year": None,
290
- "authors": None,
291
- "limit": limit,
292
- },
293
- "results": [
294
- WorkResponse(
295
- doi=w.doi,
296
- title=w.title,
297
- authors=w.authors,
298
- year=w.year,
299
- journal=w.journal,
300
- issn=w.issn,
301
- volume=w.volume,
302
- issue=w.issue,
303
- page=w.page,
304
- abstract=w.abstract,
305
- citation_count=w.citation_count,
306
- ).model_dump()
307
- for w in results.works
308
- ],
309
- "total": results.total,
310
- "returned": len(results.works),
311
- }
312
-
313
-
314
- @app.get("/api/stats/")
315
- def api_stats_compat():
316
- """Backwards-compatible stats endpoint."""
317
- db = get_db()
318
-
319
- row = db.fetchone("SELECT COUNT(*) as count FROM works")
320
- work_count = row["count"] if row else 0
321
-
322
- # Get table names
323
- tables = []
324
- for row in db.fetchall("SELECT name FROM sqlite_master WHERE type='table'"):
325
- tables.append(row["name"])
326
-
327
- # Get index names
328
- indices = []
329
- for row in db.fetchall("SELECT name FROM sqlite_master WHERE type='index'"):
330
- if row["name"]:
331
- indices.append(row["name"])
332
-
333
- return {
334
- "total_papers": work_count,
335
- "database_size_mb": None,
336
- "year_range": None,
337
- "total_journals": 0,
338
- "total_citations": None,
339
- "tables": tables,
340
- "indices": indices,
341
- }
342
-
343
-
344
- def run_server(host: str = "0.0.0.0", port: int = 3333):
345
- """Run the FastAPI server."""
346
- import uvicorn
347
-
348
- uvicorn.run(app, host=host, port=port)
349
-
350
-
351
- if __name__ == "__main__":
352
- run_server()
8
+ # EOF
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crossref-local
3
- Version: 0.3.1
3
+ Version: 0.5.0
4
4
  Summary: Local CrossRef database with 167M+ works and full-text search
5
5
  Project-URL: Homepage, https://github.com/ywatanabe1989/crossref_local
6
6
  Project-URL: Repository, https://github.com/ywatanabe1989/crossref_local
@@ -18,6 +18,7 @@ Classifier: Topic :: Database
18
18
  Classifier: Topic :: Scientific/Engineering
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: click>=8.0
21
+ Requires-Dist: rich>=13.0
21
22
  Provides-Extra: all
22
23
  Requires-Dist: fastapi>=0.100; extra == 'all'
23
24
  Requires-Dist: fastmcp>=0.4; extra == 'all'
@@ -43,11 +44,20 @@ Requires-Dist: networkx>=3.0; extra == 'viz'
43
44
  Requires-Dist: pyvis>=0.3; extra == 'viz'
44
45
  Description-Content-Type: text/markdown
45
46
 
47
+ <!-- ---
48
+ !-- Timestamp: 2026-01-16 19:15:51
49
+ !-- Author: ywatanabe
50
+ !-- File: /home/ywatanabe/proj/crossref-local/README.md
51
+ !-- --- -->
52
+
46
53
  # CrossRef Local
47
54
 
48
55
  Local CrossRef database with 167M+ scholarly works, full-text search, and impact factor calculation.
49
56
 
57
+ [![PyPI version](https://badge.fury.io/py/crossref-local.svg)](https://badge.fury.io/py/crossref-local)
58
+ [![Documentation](https://readthedocs.org/projects/crossref-local/badge/?version=latest)](https://crossref-local.readthedocs.io/en/latest/)
50
59
  [![Tests](https://github.com/ywatanabe1989/crossref-local/actions/workflows/test.yml/badge.svg)](https://github.com/ywatanabe1989/crossref-local/actions/workflows/test.yml)
60
+ [![Coverage](https://codecov.io/gh/ywatanabe1989/crossref-local/branch/main/graph/badge.svg)](https://codecov.io/gh/ywatanabe1989/crossref-local)
51
61
  [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
52
62
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
53
63
 
@@ -55,6 +65,22 @@ Local CrossRef database with 167M+ scholarly works, full-text search, and impact
55
65
  <img src="examples/readme_figure.png" alt="CrossRef Local Demo" width="800"/>
56
66
  </p>
57
67
 
68
+ <details>
69
+ <summary><strong>MCP Demo Video</strong></summary>
70
+
71
+ <p align="center">
72
+ <a href="https://scitex.ai/media/videos/crossref-local-v0.3.1-demo.mp4">
73
+ <img src="examples/demo_mcp_out/crossref-local-v0.3.1-demo-thumbnail_6m55s.png" alt="Demo Video Thumbnail" width="600"/>
74
+ </a>
75
+ </p>
76
+
77
+ Live demonstration of MCP server integration with Claude Code for `epilepsy seizure prediction` literature review:
78
+ - Full-text search on title, abstracts, and keywords across 167M papers (22ms response)
79
+
80
+ 📄 [Full demo documentation](examples/demo_mcp.org) | 📊 [Generated diagrams](examples/demo_mcp_out/)
81
+
82
+ </details>
83
+
58
84
  <details>
59
85
  <summary><strong>Why CrossRef Local?</strong></summary>
60
86
 
@@ -135,9 +161,8 @@ async def main():
135
161
 
136
162
  ```bash
137
163
  crossref-local search "CRISPR genome editing" -n 5
138
- crossref-local get 10.1038/nature12373
139
- crossref-local impact-factor Nature -y 2023 # IF: 54.067
140
- crossref-local info # Database stats
164
+ crossref-local search-by-doi 10.1038/nature12373
165
+ crossref-local status # Configuration and database stats
141
166
  ```
142
167
 
143
168
  With abstracts (`-a` flag):
@@ -160,53 +185,68 @@ Found 4 matches in 128.4ms
160
185
 
161
186
  Start the FastAPI server:
162
187
  ```bash
163
- crossref-local api --host 0.0.0.0 --port 3333
188
+ crossref-local relay --host 0.0.0.0 --port 31291
164
189
  ```
165
190
 
166
191
  Endpoints:
167
192
  ```bash
168
193
  # Search works (FTS5)
169
- curl "http://localhost:3333/works?q=CRISPR&limit=10"
194
+ curl "http://localhost:31291/works?q=CRISPR&limit=10"
170
195
 
171
196
  # Get by DOI
172
- curl "http://localhost:3333/works/10.1038/nature12373"
197
+ curl "http://localhost:31291/works/10.1038/nature12373"
173
198
 
174
199
  # Batch DOI lookup
175
- curl -X POST "http://localhost:3333/works/batch" \
200
+ curl -X POST "http://localhost:31291/works/batch" \
176
201
  -H "Content-Type: application/json" \
177
202
  -d '{"dois": ["10.1038/nature12373", "10.1126/science.aax0758"]}'
178
203
 
204
+ # Citation endpoints
205
+ curl "http://localhost:31291/citations/10.1038/nature12373/citing"
206
+ curl "http://localhost:31291/citations/10.1038/nature12373/cited"
207
+ curl "http://localhost:31291/citations/10.1038/nature12373/count"
208
+
209
+ # Collection endpoints
210
+ curl "http://localhost:31291/collections"
211
+ curl -X POST "http://localhost:31291/collections" \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"name": "my_papers", "query": "CRISPR", "limit": 100}'
214
+ curl "http://localhost:31291/collections/my_papers/download?format=bibtex"
215
+
179
216
  # Database info
180
- curl "http://localhost:3333/info"
217
+ curl "http://localhost:31291/info"
181
218
  ```
182
219
 
183
- Remote access via SSH tunnel:
220
+ HTTP mode (connect to running server):
184
221
  ```bash
185
- # On local machine
186
- ssh -L 3333:127.0.0.1:3333 nas
222
+ # On local machine (if server is remote)
223
+ ssh -L 31291:127.0.0.1:31291 your-server
187
224
 
188
225
  # Python client
189
- from crossref_local import configure_remote
190
- configure_remote("http://localhost:3333")
226
+ from crossref_local import configure_http
227
+ configure_http("http://localhost:31291")
228
+
229
+ # Or via CLI
230
+ crossref-local --http search "CRISPR"
191
231
  ```
192
232
 
193
233
  </details>
194
234
 
195
235
  <details>
196
- <summary><strong>MCP Server (Claude Desktop)</strong></summary>
236
+ <summary><strong>MCP Server</strong></summary>
197
237
 
198
- Run as MCP server for Claude Desktop integration:
238
+ Run as MCP (Model Context Protocol) server:
199
239
  ```bash
200
- crossref-local serve
240
+ crossref-local mcp start
201
241
  ```
202
242
 
203
- Add to Claude Desktop config (`~/.config/claude/claude_desktop_config.json`):
243
+ Local MCP client configuration:
204
244
  ```json
205
245
  {
206
246
  "mcpServers": {
207
247
  "crossref-local": {
208
248
  "command": "crossref-local",
209
- "args": ["serve"],
249
+ "args": ["mcp", "start"],
210
250
  "env": {
211
251
  "CROSSREF_LOCAL_DB": "/path/to/crossref.db"
212
252
  }
@@ -215,12 +255,36 @@ Add to Claude Desktop config (`~/.config/claude/claude_desktop_config.json`):
215
255
  }
216
256
  ```
217
257
 
258
+ Remote MCP via HTTP (recommended):
259
+ ```bash
260
+ # On server: start persistent MCP server
261
+ crossref-local mcp start -t http --host 0.0.0.0 --port 8082
262
+ ```
263
+ ```json
264
+ {
265
+ "mcpServers": {
266
+ "crossref-remote": {
267
+ "url": "http://your-server:8082/mcp"
268
+ }
269
+ }
270
+ }
271
+ ```
272
+
273
+ Diagnose setup:
274
+ ```bash
275
+ crossref-local mcp doctor # Check dependencies and database
276
+ crossref-local mcp list-tools # Show available MCP tools
277
+ crossref-local mcp installation # Show client config examples
278
+ ```
279
+
280
+ See [docs/remote-deployment.md](docs/remote-deployment.md) for systemd and Docker setup.
281
+
218
282
  Available tools:
219
- - `search_works` - Full-text search across 167M+ papers
220
- - `get_work` - Get paper by DOI
221
- - `count_works` - Count matching papers
222
- - `database_info` - Database statistics
223
- - `calculate_impact_factor` - Journal impact factor
283
+ - `search` - Full-text search across 167M+ papers
284
+ - `search_by_doi` - Get paper by DOI
285
+ - `enrich_dois` - Add citation counts and references to DOIs
286
+ - `status` - Database statistics
287
+ - `cache_*` - Paper collection management
224
288
 
225
289
  </details>
226
290
 
@@ -0,0 +1,47 @@
1
+ crossref_local/__init__.py,sha256=bOn6qJob4okwkLzzIoEGQj0I-amIBSr89iWrbkYEJns,3691
2
+ crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
3
+ crossref_local/aio.py,sha256=_vDHFoE2n6GqO3j6pGeCaJVVe5I5L3M0tuMlW8IgGx8,1255
4
+ crossref_local/cache.py,sha256=yQpnRRBsHx2pluIPPfJlrakeuCC8TysKXrfA3ohdxSg,13321
5
+ crossref_local/cli.py,sha256=2pWJK_vnO1IIwUrMB3K8KW94ZWfkEF7TLSxMsaR-LrI,137
6
+ crossref_local/jobs.py,sha256=1tyaFRjyQGVi8xqk5nYga7maIFq2qYTroBGwAeN5DFc,4835
7
+ crossref_local/mcp_server.py,sha256=YmUd3xKqSDnW6sYDC7KRpZ7sSTnbTnxxGw9s_5lqIt8,185
8
+ crossref_local/remote.py,sha256=PmvUq87mC76sM9BL9RczOaXtvwHoqMc_dN5PJCYT18M,239
9
+ crossref_local/server.py,sha256=SKoQ-cOoZjdXm24Sv1CFu3F8UclbD6QstfQb-7l2xtA,215
10
+ crossref_local/_aio/__init__.py,sha256=z5_RNd9PTQUslI5LWOCn0711s8cIRX7N-Y-S-JyutAU,371
11
+ crossref_local/_aio/_impl.py,sha256=uG5r0d92EcPDqB52w4Wjb7kf1GmjHrLEELHINmNXnJc,5626
12
+ crossref_local/_cache/__init__.py,sha256=QSSTVWB_zNU3JQRdnBCzHuIl93WM3udgO2NkRlgm9M0,272
13
+ crossref_local/_cache/export.py,sha256=ZyZS1vWZE9ui9weywejnBFI2FzNV1r1Q_3XXPiFIj3M,3028
14
+ crossref_local/_cache/utils.py,sha256=D3sg7MoXZs4aC2OvagVgmOVMZjm1Xqy1ulGbs0_F914,2502
15
+ crossref_local/_cache/viz.py,sha256=0VAHK-i1xR5StEYrJ_IAs0HSk0MQNVFi4EjPtlZEwTY,8498
16
+ crossref_local/_cli/__init__.py,sha256=NS07Eo93dRAuO5vxGwGwS0ctvZxMMkF_py09bzIk3Hk,175
17
+ crossref_local/_cli/cache.py,sha256=8QykJ7-H313x7oKRw5fJ9Coay27Y8uBWGx5JR1VQ1_Y,6220
18
+ crossref_local/_cli/cli.py,sha256=w7rJ4ZtetSr6TRz2f2vyHqZylq5vXM8O9KB61gScbFM,16903
19
+ crossref_local/_cli/completion.py,sha256=yzqwMbGbbqXJQHdztuaqji-AdFTd8zDDhHeNGdw4NpU,7341
20
+ crossref_local/_cli/main.py,sha256=NgCGB5-ThofTRPoxxwZpsfCQTo5WIw8oP7zBSKhEiDQ,444
21
+ crossref_local/_cli/mcp.py,sha256=yjUoq3hvu2OofYf_D29_NXoTvkhxz5QbWAuWv6U-H8U,11427
22
+ crossref_local/_cli/mcp_server.py,sha256=jZJwhiTavgTHcJctXpafPgy3aIT49-rdIZ-4i88myeU,10989
23
+ crossref_local/_core/__init__.py,sha256=6xKO9y3VOuTF1f83uyQ48HWhszYpPfWZj94t0q0rEgk,940
24
+ crossref_local/_core/api.py,sha256=YogfhBHDIqT-hdEI-iDe_-py13Vp6F4GW8q0psLDRyQ,7781
25
+ crossref_local/_core/citations.py,sha256=4ERCTVkZu6kesZRazaWcTpUUeYz0rs97NwFZAYcLdKA,12765
26
+ crossref_local/_core/config.py,sha256=T-2EX4EDoCLaOAmViBKGQuNvoJP8x7cvZOvmAcNrQms,5475
27
+ crossref_local/_core/db.py,sha256=KLlcdNWLnKSexc0cuRZGsBLmJUnXeEaD_CBcXhbPXLg,3832
28
+ crossref_local/_core/fts.py,sha256=Ti1EdSsrIQMDFTynyoiqC2bi_nHtPZ6uOyj5ERcxiQk,4515
29
+ crossref_local/_core/models.py,sha256=4b8153r7xDBcmTQFsIeSLD1jJm4KvuUkKTLixB7lccM,5562
30
+ crossref_local/_impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
31
+ crossref_local/_impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
32
+ crossref_local/_impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
33
+ crossref_local/_remote/__init__.py,sha256=bTA_bed9XsA0HDArm1o0WagsasxN0atzXdRI6I-r7Pk,1412
34
+ crossref_local/_remote/base.py,sha256=gvvvq7uGrG1IOQEZ5ZhWWuakg9Fl_GdkAaU1AfCmtzY,11258
35
+ crossref_local/_remote/collections.py,sha256=840HkakS_AtZbmRMYP-GpUX4S5oJ9jaJXzjivQgyozU,5022
36
+ crossref_local/_server/__init__.py,sha256=4fTk1r__5uWhC6XABVDV7PTPdglFIV32hr9KZ5WbKc8,3869
37
+ crossref_local/_server/middleware.py,sha256=6tdrfFCOA06i0HIDtrHZthY8DXbKy8rMEWHf-3Ve8-Y,926
38
+ crossref_local/_server/models.py,sha256=gjnE-MK4XtfdN_obMyqT5xjNXPa3F-BPrgMrrg908KY,2580
39
+ crossref_local/_server/routes_citations.py,sha256=DUpxl4FdZS7NheM93vCD6ntP5I1Hgw_D8mkGBEMBV2Y,2742
40
+ crossref_local/_server/routes_collections.py,sha256=NaWNTMcswmh87HTZYn5QpzxwtA_c18N3cqy6CrqCBNo,8135
41
+ crossref_local/_server/routes_compat.py,sha256=kSF4_DKg7lcp67Xii95A1pKDBc3kGcqGD-IPaK6TJA8,2726
42
+ crossref_local/_server/routes_works.py,sha256=RxQnkyzoQZtm2n5XTBpZjxjB6ti8GFs-zIzap6VHSgU,3521
43
+ crossref_local/_server/server.py,sha256=D3SSXQ_cBwMqGd9tLwKK84JBDoisvQwVUuu11yr5zhE,579
44
+ crossref_local-0.5.0.dist-info/METADATA,sha256=T2gU3EHRunTfZKRUck9aOrCZAE9HW6VoxjiDdCzyKQs,10748
45
+ crossref_local-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
46
+ crossref_local-0.5.0.dist-info/entry_points.txt,sha256=BZbDvHLHzlKzFc-dqLAFwPrWGmGq5yFuD3vslzbmRnk,111
47
+ crossref_local-0.5.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- crossref_local/__init__.py,sha256=AJIkriNQBf61SPoi-cfxNr25pdoQMS3KvLhsNoCfJmQ,3316
2
- crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
3
- crossref_local/aio.py,sha256=En2btSn3euRbEYav1919gsmdC8iQaMbgGUso-IThCwo,5490
4
- crossref_local/api.py,sha256=h2lpoA7qsCzxBTv0Na3Etgk0hZCWZFKmalreIYsmOhw,5343
5
- crossref_local/citations.py,sha256=QFahv84upNnXP_89A8bHxEbAdz7wHbh5LEniGcAiHas,12402
6
- crossref_local/cli.py,sha256=WMk7GxJtTf2ZCH1ldkcUmX-643n5DE11kGu_K7AkOrA,15132
7
- crossref_local/config.py,sha256=4LGZJ3CmsA9YRv48FkEqVR2xljuSjl0MYiMrT8ljk14,5050
8
- crossref_local/db.py,sha256=x7dXQXjsFN4LavtkNAKTNw1cUBMG-2h53-Z-Xlq6aoQ,3696
9
- crossref_local/fts.py,sha256=yZMh_vmtFentXKAFGTS4z7ZNNj7p_ItgfFP5i0yQltw,4448
10
- crossref_local/mcp_server.py,sha256=KDcBvVMXrhemO7cS4kBMfEvp0Qb-LDsVbnPhnLaaC-4,5796
11
- crossref_local/models.py,sha256=b_yYb91O6RwEPpEqe2Wmdz12WIfE5itjEus4-fCLxLI,5476
12
- crossref_local/remote.py,sha256=p8P0zotkNYchqqKGOsqcFiHR10qD4pYmJ26-ltyqO4s,8389
13
- crossref_local/server.py,sha256=lEc0EA3jVx31q1EEYOaT4cr9l2_fGpoQZmpYdnoGxFQ,9034
14
- crossref_local/impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
15
- crossref_local/impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
16
- crossref_local/impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
17
- crossref_local-0.3.1.dist-info/METADATA,sha256=Wiqa6MGJXMgMFI3qXss4IcLr0wo20Jsbl9UgAfyXrrU,8480
18
- crossref_local-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
- crossref_local-0.3.1.dist-info/entry_points.txt,sha256=BZbDvHLHzlKzFc-dqLAFwPrWGmGq5yFuD3vslzbmRnk,111
20
- crossref_local-0.3.1.dist-info/RECORD,,