Skip to content

Commit 563127d

Browse files
committed
finalize fastapi search json endpoint
1 parent 7bf4617 commit 563127d

File tree

2 files changed

+104
-82
lines changed

2 files changed

+104
-82
lines changed

openlibrary/fastapi/search.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
from __future__ import annotations
22

3-
from fastapi import APIRouter, Query, Request
3+
from typing import Annotated
4+
5+
from fastapi import APIRouter, Depends, Query, Request
46
from fastapi.responses import JSONResponse
57

68
from openlibrary.plugins.worksearch.code import work_search_async
7-
from openlibrary.plugins.worksearch.schemes.works import WorkSearchScheme
9+
from openlibrary.plugins.worksearch.schemes.works import (
10+
WorkSearchAllFields,
11+
WorkSearchFacetFields,
12+
WorkSearchScheme,
13+
)
814

915
router = APIRouter()
1016

1117

1218
@router.get("/search.json", response_class=JSONResponse)
1319
async def search_json(
1420
request: Request,
21+
facets: Annotated[WorkSearchFacetFields, Depends(WorkSearchFacetFields)],
22+
all_fields: Annotated[WorkSearchAllFields, Depends(WorkSearchAllFields)],
1523
q: str | None = Query("", description="The search query."),
1624
page: int | None = Query(
1725
1, ge=1, description="The page number of results to return."
@@ -22,7 +30,6 @@ async def search_json(
2230
sort: str | None = Query(None, description="The sort order of results."),
2331
offset: int | None = Query(None, description="The offset of results to return."),
2432
fields: str | None = Query(None, description="The fields to return."),
25-
# facet: bool = Query(False, description="Whether to return facets."),
2633
spellcheck_count: int | None = Query(
2734
3, description="The number of spellcheck suggestions."
2835
),
@@ -31,16 +38,21 @@ async def search_json(
3138
Performs a search for documents based on the provided query.
3239
"""
3340

34-
kwargs = dict(request.query_params)
3541
# Call the underlying search logic
3642
_fields = WorkSearchScheme.default_fetched_fields
3743
if fields:
3844
_fields = fields.split(',') # type: ignore
3945

4046
query = {"q": q, "page": page, "limit": limit}
47+
4148
query.update(
42-
kwargs
43-
) # This is a hack until we define all the params we expect above.
49+
{key: value for key, value in facets.dict().items() if value is not None}
50+
)
51+
52+
query.update(
53+
{key: value for key, value in all_fields.dict().items() if value is not None}
54+
)
55+
4456
response = await work_search_async(
4557
query,
4658
sort=sort,
@@ -53,11 +65,13 @@ async def search_json(
5365
facet=False,
5466
spellcheck_count=spellcheck_count,
5567
lang=request.state.lang,
68+
request_label='BOOK_SEARCH_API',
5669
)
5770

5871
# Add extra metadata to the response, similar to the original
5972
response['q'] = q
6073
response['documentation_url'] = "https://openlibrary.org/dev/docs/api/search"
74+
response['offset'] = offset
6175

6276
# Reorder keys to have 'docs' at the end, as in the original code
6377
docs = response.pop('docs', [])

openlibrary/plugins/worksearch/schemes/works.py

Lines changed: 84 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import luqum.tree
1111
import web
12+
from pydantic import BaseModel
1213

1314
import infogami
1415
from openlibrary.plugins.upstream.utils import convert_iso_to_marc
@@ -40,90 +41,97 @@
4041
re_author_key = re.compile(r'(OL\d+A)')
4142

4243

44+
class WorkSearchFacetFields(BaseModel):
45+
"""Facet fields that can be used when searching"""
46+
47+
# Facets
48+
has_fulltext: bool | None = None
49+
author_facet: str | None = None
50+
language: str | None = None
51+
first_publish_year: str | None = None
52+
publisher_facet: str | None = None
53+
subject_facet: str | None = None
54+
person_facet: str | None = None
55+
place_facet: str | None = None
56+
time_facet: str | None = None
57+
public_scan_b: bool | None = None
58+
59+
60+
class WorkSearchAllFields(BaseModel):
61+
"""All fields that can be used when searching"""
62+
63+
# Unclear why some facets like subject_facet, person_facet, place_facet, time_facet, public_scan_b are not included
64+
65+
key: str | None = None
66+
redirects: str | None = None
67+
title: str | None = None
68+
subtitle: str | None = None
69+
alternative_title: str | None = None
70+
alternative_subtitle: str | None = None
71+
cover_i: str | None = None
72+
ebook_access: str | None = None
73+
ebook_provider: str | None = None
74+
edition_count: str | None = None
75+
edition_key: str | None = None
76+
format: str | None = None
77+
by_statement: str | None = None
78+
publish_date: str | None = None
79+
lccn: str | None = None
80+
lexile: str | None = None
81+
ia: str | None = None
82+
oclc: str | None = None
83+
isbn: str | None = None
84+
contributor: str | None = None
85+
publish_place: str | None = None
86+
publisher: str | None = None
87+
first_sentence: str | None = None
88+
author_key: str | None = None
89+
author_name: str | None = None
90+
author_alternative_name: str | None = None
91+
subject: str | None = None
92+
person: str | None = None
93+
place: str | None = None
94+
time: str | None = None
95+
has_fulltext: bool | None = None
96+
title_suggest: str | None = None
97+
publish_year: str | None = None
98+
language: str | None = None
99+
number_of_pages_median: int | None = None
100+
ia_count: int | None = None
101+
publisher_facet: str | None = None
102+
author_facet: str | None = None
103+
first_publish_year: str | None = None
104+
ratings_count: int | None = None
105+
readinglog_count: int | None = None
106+
want_to_read_count: int | None = None
107+
currently_reading_count: int | None = None
108+
already_read_count: int | None = None
109+
# Subjects
110+
subject_key: str | None = None
111+
person_key: str | None = None
112+
place_key: str | None = None
113+
time_key: str | None = None
114+
# Classifications
115+
lcc: str | None = None
116+
ddc: str | None = None
117+
lcc_sort: str | None = None
118+
ddc_sort: str | None = None
119+
osp_count: int | None = None
120+
# Trending
121+
trending_score_hourly_sum: int | None = None
122+
trending_z_score: int | None = None
123+
124+
43125
class WorkSearchScheme(SearchScheme):
44126
universe = frozenset(['type:work'])
45-
all_fields = frozenset(
46-
{
47-
"key",
48-
"redirects",
49-
"title",
50-
"subtitle",
51-
"alternative_title",
52-
"alternative_subtitle",
53-
"cover_i",
54-
"ebook_access",
55-
"ebook_provider",
56-
"edition_count",
57-
"edition_key",
58-
"format",
59-
"by_statement",
60-
"publish_date",
61-
"lccn",
62-
"lexile",
63-
"ia",
64-
"oclc",
65-
"isbn",
66-
"contributor",
67-
"publish_place",
68-
"publisher",
69-
"first_sentence",
70-
"author_key",
71-
"author_name",
72-
"author_alternative_name",
73-
"subject",
74-
"person",
75-
"place",
76-
"time",
77-
"has_fulltext",
78-
"title_suggest",
79-
"publish_year",
80-
"language",
81-
"number_of_pages_median",
82-
"ia_count",
83-
"publisher_facet",
84-
"author_facet",
85-
"first_publish_year",
86-
"ratings_count",
87-
"readinglog_count",
88-
"want_to_read_count",
89-
"currently_reading_count",
90-
"already_read_count",
91-
# Subjects
92-
"subject_key",
93-
"person_key",
94-
"place_key",
95-
"time_key",
96-
# Classifications
97-
"lcc",
98-
"ddc",
99-
"lcc_sort",
100-
"ddc_sort",
101-
"osp_count",
102-
# Trending
103-
"trending_score_hourly_sum",
104-
"trending_z_score",
105-
}
106-
)
127+
all_fields = frozenset(WorkSearchAllFields.model_fields.keys())
107128
non_solr_fields = frozenset(
108129
{
109130
'description',
110131
'providers',
111132
}
112133
)
113-
facet_fields = frozenset(
114-
{
115-
"has_fulltext",
116-
"author_facet",
117-
"language",
118-
"first_publish_year",
119-
"publisher_facet",
120-
"subject_facet",
121-
"person_facet",
122-
"place_facet",
123-
"time_facet",
124-
"public_scan_b",
125-
}
126-
)
134+
facet_fields = frozenset(WorkSearchFacetFields.model_fields.keys())
127135
field_name_map = MappingProxyType(
128136
{
129137
'author': 'author_name',

0 commit comments

Comments
 (0)