Skip to content

Commit d774d7e

Browse files
committed
feat: Dynamically allow and pass WorkSearchScheme fields as query parameters to the search endpoint, including a new test.
1 parent af4d6ce commit d774d7e

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

openlibrary/fastapi/search.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from fastapi import APIRouter, Depends, Query, Request
77
from fastapi.responses import JSONResponse
8-
from pydantic import BaseModel
8+
from pydantic import BaseModel, create_model
99

1010
from openlibrary.plugins.worksearch.code import (
1111
default_spellcheck_count,
@@ -40,6 +40,20 @@ def pagination(
4040
return Pagination(limit=limit, offset=offset, page=page)
4141

4242

43+
field_names = set(WorkSearchScheme.all_fields) | set(
44+
# WorkSearchScheme.field_name_map.keys()
45+
# Temporarily commented out because field names can't start with underscores but we have _ia_collection
46+
)
47+
48+
49+
# Dynamically create the model
50+
AllAllowedParams = create_model(
51+
'AllAllowedParams',
52+
__base__=BaseModel,
53+
**{name: (str | None, Query(None)) for name in field_names},
54+
)
55+
56+
4357
@router.get("/search.json", response_class=JSONResponse)
4458
async def search_json( # noqa: PLR0913
4559
request: Request,
@@ -51,8 +65,12 @@ async def search_json( # noqa: PLR0913
5165
first_publish_year: ListQuery,
5266
publisher_facet: ListQuery,
5367
language: ListQuery,
54-
public_scan_b: ListQuery, # tbd if this should actually be a query
68+
public_scan_b: ListQuery, # tbd if this should actually be a list
5569
pagination: Annotated[Pagination, Depends(pagination)],
70+
all_allowed_params: Annotated[ # type: ignore[valid-type]
71+
AllAllowedParams,
72+
Depends(),
73+
],
5674
q: str | None = Query("", description="The search query."),
5775
sort: str | None = Query(None, description="The sort order of results."),
5876
fields: str | None = Query(None, description="The fields to return."),
@@ -73,9 +91,7 @@ async def search_json( # noqa: PLR0913
7391
query = json.loads(query_str)
7492
else:
7593
query = {"q": q, "page": pagination.page, "limit": pagination.limit}
76-
query.update(
77-
dict(request.query_params)
78-
) # This is a hack until we define all the params we expect in the route
94+
query.update({k: v for k, v in all_allowed_params.dict().items() if v is not None}) # type: ignore[attr-defined]
7995
query.update(
8096
{
8197
"author_key": author_key,

openlibrary/tests/fastapi/test_search.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,25 @@ def test_pagination_validation_errors(self, client, mock_work_search, params):
175175

176176
# Should return a validation error
177177
assert response.status_code == 422
178+
179+
def test_query_params_passed_down(self, client, mock_work_search):
180+
"""Test that arbitrary query parameters like osp_count are passed down correctly."""
181+
mock_work_search.return_value = {
182+
'numFound': 1,
183+
'start': 0,
184+
'docs': [{'key': '/works/OL1W', 'title': 'Test Work'}],
185+
}
186+
187+
# Make a request with osp_count parameter
188+
response = search(client, q='test', osp_count='5')
189+
190+
assert response.status_code == 200
191+
192+
# Verify work_search_async was called
193+
mock_work_search.assert_called_once()
194+
call_args = mock_work_search.call_args
195+
196+
# The query dict should contain the osp_count parameter
197+
query_arg = call_args[0][0] # First positional argument
198+
assert 'osp_count' in query_arg
199+
assert query_arg['osp_count'] == '5'

0 commit comments

Comments
 (0)