crossref-local 0.5.0__py3-none-any.whl → 0.5.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.
- crossref_local/__init__.py +7 -1
- crossref_local/_cli/cli.py +15 -138
- crossref_local/_cli/mcp_server.py +59 -15
- crossref_local/_cli/search.py +199 -0
- crossref_local/_core/__init__.py +4 -0
- crossref_local/_core/api.py +3 -1
- crossref_local/_core/export.py +344 -0
- crossref_local/_core/fts.py +20 -1
- crossref_local/_core/models.py +109 -0
- crossref_local/_remote/base.py +25 -3
- crossref_local/_server/models.py +14 -0
- crossref_local/_server/routes_works.py +63 -13
- {crossref_local-0.5.0.dist-info → crossref_local-0.5.1.dist-info}/METADATA +1 -1
- {crossref_local-0.5.0.dist-info → crossref_local-0.5.1.dist-info}/RECORD +16 -14
- {crossref_local-0.5.0.dist-info → crossref_local-0.5.1.dist-info}/WHEEL +0 -0
- {crossref_local-0.5.0.dist-info → crossref_local-0.5.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
"""Work search and retrieval endpoints."""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional, Dict
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, Query, HTTPException
|
|
7
7
|
|
|
8
8
|
from .._core import fts
|
|
9
9
|
from .._core.db import get_db
|
|
10
10
|
from .._core.models import Work
|
|
11
|
-
from .models import
|
|
11
|
+
from .models import (
|
|
12
|
+
BatchRequest,
|
|
13
|
+
BatchResponse,
|
|
14
|
+
SearchResponse,
|
|
15
|
+
WorkResponse,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
router = APIRouter(tags=["works"])
|
|
14
19
|
|
|
20
|
+
# IF cache for performance
|
|
21
|
+
_if_cache: Dict[str, Optional[float]] = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_impact_factor(db, issn: str) -> Optional[float]:
|
|
25
|
+
"""Get impact factor from journals_openalex table."""
|
|
26
|
+
if not issn:
|
|
27
|
+
return None
|
|
28
|
+
if issn in _if_cache:
|
|
29
|
+
return _if_cache[issn]
|
|
30
|
+
try:
|
|
31
|
+
row = db.fetchone(
|
|
32
|
+
"SELECT two_year_mean_citedness FROM journals_openalex WHERE issns LIKE ?",
|
|
33
|
+
(f"%{issn}%",),
|
|
34
|
+
)
|
|
35
|
+
_if_cache[issn] = row["two_year_mean_citedness"] if row else None
|
|
36
|
+
except Exception:
|
|
37
|
+
_if_cache[issn] = None
|
|
38
|
+
return _if_cache[issn]
|
|
39
|
+
|
|
15
40
|
|
|
16
41
|
@router.get("/works", response_model=SearchResponse)
|
|
17
42
|
def search_works(
|
|
18
43
|
q: str = Query(..., description="Search query (FTS5 syntax supported)"),
|
|
19
|
-
limit: int = Query(10, ge=1,
|
|
44
|
+
limit: int = Query(10, ge=1, description="Max results"),
|
|
20
45
|
offset: int = Query(0, ge=0, description="Skip first N results"),
|
|
46
|
+
with_if: bool = Query(False, description="Include impact factor (OpenAlex)"),
|
|
21
47
|
):
|
|
22
48
|
"""
|
|
23
49
|
Full-text search across works.
|
|
@@ -28,7 +54,7 @@ def search_works(
|
|
|
28
54
|
Examples:
|
|
29
55
|
/works?q=machine learning
|
|
30
56
|
/works?q="neural network" AND hippocampus
|
|
31
|
-
/works?q=CRISPR&limit=20
|
|
57
|
+
/works?q=CRISPR&limit=20&with_if=true
|
|
32
58
|
"""
|
|
33
59
|
start = time.perf_counter()
|
|
34
60
|
|
|
@@ -37,14 +63,13 @@ def search_works(
|
|
|
37
63
|
except Exception as e:
|
|
38
64
|
raise HTTPException(status_code=400, detail=f"Search error: {e}")
|
|
39
65
|
|
|
40
|
-
|
|
66
|
+
# Get IF if requested
|
|
67
|
+
db = get_db() if with_if else None
|
|
41
68
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
elapsed_ms=round(elapsed_ms, 2),
|
|
47
|
-
results=[
|
|
69
|
+
work_responses = []
|
|
70
|
+
for w in results.works:
|
|
71
|
+
if_val = _get_impact_factor(db, w.issn) if db and w.issn else None
|
|
72
|
+
work_responses.append(
|
|
48
73
|
WorkResponse(
|
|
49
74
|
doi=w.doi,
|
|
50
75
|
title=w.title,
|
|
@@ -57,9 +82,34 @@ def search_works(
|
|
|
57
82
|
page=w.page,
|
|
58
83
|
abstract=w.abstract,
|
|
59
84
|
citation_count=w.citation_count,
|
|
85
|
+
impact_factor=if_val,
|
|
86
|
+
impact_factor_source="OpenAlex" if if_val else None,
|
|
60
87
|
)
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elapsed_ms = (time.perf_counter() - start) * 1000
|
|
91
|
+
|
|
92
|
+
# Build limit_info from search result
|
|
93
|
+
limit_info = None
|
|
94
|
+
if results.limit_info:
|
|
95
|
+
from .models import LimitInfoResponse
|
|
96
|
+
|
|
97
|
+
limit_info = LimitInfoResponse(
|
|
98
|
+
requested=results.limit_info.requested,
|
|
99
|
+
returned=results.limit_info.returned,
|
|
100
|
+
total_available=results.limit_info.total_available,
|
|
101
|
+
capped=results.limit_info.capped,
|
|
102
|
+
capped_reason=results.limit_info.capped_reason,
|
|
103
|
+
stage=results.limit_info.stage,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return SearchResponse(
|
|
107
|
+
query=q,
|
|
108
|
+
total=results.total,
|
|
109
|
+
returned=len(results.works),
|
|
110
|
+
elapsed_ms=round(elapsed_ms, 2),
|
|
111
|
+
results=work_responses,
|
|
112
|
+
limit_info=limit_info,
|
|
63
113
|
)
|
|
64
114
|
|
|
65
115
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crossref-local
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
crossref_local/__init__.py,sha256=
|
|
1
|
+
crossref_local/__init__.py,sha256=j2HyOmjYFUjiywoLdIgYVqgXuGx9eWjcy4x7VBA4vf0,3792
|
|
2
2
|
crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
|
|
3
3
|
crossref_local/aio.py,sha256=_vDHFoE2n6GqO3j6pGeCaJVVe5I5L3M0tuMlW8IgGx8,1255
|
|
4
4
|
crossref_local/cache.py,sha256=yQpnRRBsHx2pluIPPfJlrakeuCC8TysKXrfA3ohdxSg,13321
|
|
@@ -15,33 +15,35 @@ crossref_local/_cache/utils.py,sha256=D3sg7MoXZs4aC2OvagVgmOVMZjm1Xqy1ulGbs0_F91
|
|
|
15
15
|
crossref_local/_cache/viz.py,sha256=0VAHK-i1xR5StEYrJ_IAs0HSk0MQNVFi4EjPtlZEwTY,8498
|
|
16
16
|
crossref_local/_cli/__init__.py,sha256=NS07Eo93dRAuO5vxGwGwS0ctvZxMMkF_py09bzIk3Hk,175
|
|
17
17
|
crossref_local/_cli/cache.py,sha256=8QykJ7-H313x7oKRw5fJ9Coay27Y8uBWGx5JR1VQ1_Y,6220
|
|
18
|
-
crossref_local/_cli/cli.py,sha256=
|
|
18
|
+
crossref_local/_cli/cli.py,sha256=TFhj7BjSHxs3rWyOifpi39z6-YmHIHt5Jz-cZNtVtEg,12425
|
|
19
19
|
crossref_local/_cli/completion.py,sha256=yzqwMbGbbqXJQHdztuaqji-AdFTd8zDDhHeNGdw4NpU,7341
|
|
20
20
|
crossref_local/_cli/main.py,sha256=NgCGB5-ThofTRPoxxwZpsfCQTo5WIw8oP7zBSKhEiDQ,444
|
|
21
21
|
crossref_local/_cli/mcp.py,sha256=yjUoq3hvu2OofYf_D29_NXoTvkhxz5QbWAuWv6U-H8U,11427
|
|
22
|
-
crossref_local/_cli/mcp_server.py,sha256=
|
|
23
|
-
crossref_local/
|
|
24
|
-
crossref_local/_core/
|
|
22
|
+
crossref_local/_cli/mcp_server.py,sha256=mplCUBph9WM_Eq7b-MXgT-vvUH9hf4h_eeHUHTQ7f5c,12526
|
|
23
|
+
crossref_local/_cli/search.py,sha256=bqsWn4nCCW_Glgb7dSXsntP4P5nHQJJCSgehvnV9Bbc,6327
|
|
24
|
+
crossref_local/_core/__init__.py,sha256=xfun82MI9-SuaBqX0zB5liiLmj3AkY5TlksVYY6IaOg,1034
|
|
25
|
+
crossref_local/_core/api.py,sha256=fYkr2VkiAQur3fdysIXP57qk0njWBf9ngg6dt5t63Do,7880
|
|
25
26
|
crossref_local/_core/citations.py,sha256=4ERCTVkZu6kesZRazaWcTpUUeYz0rs97NwFZAYcLdKA,12765
|
|
26
27
|
crossref_local/_core/config.py,sha256=T-2EX4EDoCLaOAmViBKGQuNvoJP8x7cvZOvmAcNrQms,5475
|
|
27
28
|
crossref_local/_core/db.py,sha256=KLlcdNWLnKSexc0cuRZGsBLmJUnXeEaD_CBcXhbPXLg,3832
|
|
28
|
-
crossref_local/_core/
|
|
29
|
-
crossref_local/_core/
|
|
29
|
+
crossref_local/_core/export.py,sha256=uM_Qb9WXmn7v4tDOQjBlhJNQY0Y0WbefHxNLrRhe5tI,8899
|
|
30
|
+
crossref_local/_core/fts.py,sha256=Jsj3Cm9pihICk7Rvr9vIZbUsoxKzL0OligFFQxtQDVA,5030
|
|
31
|
+
crossref_local/_core/models.py,sha256=tbo0hIViKM9eJQXzp2APFhRvYd1UusUdJHugpPyNAEo,8697
|
|
30
32
|
crossref_local/_impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
|
|
31
33
|
crossref_local/_impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
|
|
32
34
|
crossref_local/_impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
|
|
33
35
|
crossref_local/_remote/__init__.py,sha256=bTA_bed9XsA0HDArm1o0WagsasxN0atzXdRI6I-r7Pk,1412
|
|
34
|
-
crossref_local/_remote/base.py,sha256=
|
|
36
|
+
crossref_local/_remote/base.py,sha256=VlQCPdhRcUynB1IAnJAyfXhxzfsLa0tOSSINAH8lMuI,12160
|
|
35
37
|
crossref_local/_remote/collections.py,sha256=840HkakS_AtZbmRMYP-GpUX4S5oJ9jaJXzjivQgyozU,5022
|
|
36
38
|
crossref_local/_server/__init__.py,sha256=4fTk1r__5uWhC6XABVDV7PTPdglFIV32hr9KZ5WbKc8,3869
|
|
37
39
|
crossref_local/_server/middleware.py,sha256=6tdrfFCOA06i0HIDtrHZthY8DXbKy8rMEWHf-3Ve8-Y,926
|
|
38
|
-
crossref_local/_server/models.py,sha256=
|
|
40
|
+
crossref_local/_server/models.py,sha256=8ylDo3UhNoRGjXg5bmiUvB-BWy_8nahD-PSQSEK_8wk,2965
|
|
39
41
|
crossref_local/_server/routes_citations.py,sha256=DUpxl4FdZS7NheM93vCD6ntP5I1Hgw_D8mkGBEMBV2Y,2742
|
|
40
42
|
crossref_local/_server/routes_collections.py,sha256=NaWNTMcswmh87HTZYn5QpzxwtA_c18N3cqy6CrqCBNo,8135
|
|
41
43
|
crossref_local/_server/routes_compat.py,sha256=kSF4_DKg7lcp67Xii95A1pKDBc3kGcqGD-IPaK6TJA8,2726
|
|
42
|
-
crossref_local/_server/routes_works.py,sha256=
|
|
44
|
+
crossref_local/_server/routes_works.py,sha256=YtDKh-xxTCvbDf8K7kwxWszIYjYz-PZzqJH8q13CoFE,5080
|
|
43
45
|
crossref_local/_server/server.py,sha256=D3SSXQ_cBwMqGd9tLwKK84JBDoisvQwVUuu11yr5zhE,579
|
|
44
|
-
crossref_local-0.5.
|
|
45
|
-
crossref_local-0.5.
|
|
46
|
-
crossref_local-0.5.
|
|
47
|
-
crossref_local-0.5.
|
|
46
|
+
crossref_local-0.5.1.dist-info/METADATA,sha256=RXoY2iFOTFVQcDQq0vPIwBpqz1rYPIiA4Uz-kj0-YZM,10748
|
|
47
|
+
crossref_local-0.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
crossref_local-0.5.1.dist-info/entry_points.txt,sha256=BZbDvHLHzlKzFc-dqLAFwPrWGmGq5yFuD3vslzbmRnk,111
|
|
49
|
+
crossref_local-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|