Skip to content

Commit cf017c0

Browse files
authored
remove web.py partials code (#11895)
1 parent a35ae10 commit cf017c0

2 files changed

Lines changed: 27 additions & 158 deletions

File tree

openlibrary/plugins/openlibrary/deprecated_handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,5 @@ def DELETE(self, *args):
9898
'/reading-goal',
9999
'(/subjects/[^/]+)',
100100
'(/publishers/[^/]+)',
101+
'(/partials/[^/]+)',
101102
]

openlibrary/plugins/openlibrary/partials.py

Lines changed: 26 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import json
21
from abc import ABC, abstractmethod
32
from datetime import datetime
4-
from typing import cast
53
from urllib.parse import parse_qs
64

75
import web
86
from pydantic import BaseModel
9-
from typing_extensions import deprecated
107

11-
from infogami.utils import delegate
128
from infogami.utils.view import render_template
139
from openlibrary.accounts import get_current_user
1410
from openlibrary.core.fulltext import fulltext_search
@@ -24,10 +20,6 @@
2420
from openlibrary.views.loanstats import get_trending_books
2521

2622

27-
class PartialResolutionError(Exception):
28-
pass
29-
30-
3123
class PartialDataHandler(ABC):
3224
"""Base class for partial data handlers.
3325
@@ -44,11 +36,8 @@ def generate(self) -> dict:
4436
class ReadingGoalProgressPartial(PartialDataHandler):
4537
"""Handler for reading goal progress."""
4638

47-
def __init__(self, year: int | None = None):
48-
if year is None:
49-
self.year = web.input(year=None).year
50-
else:
51-
self.year = year
39+
def __init__(self, year: int):
40+
self.year = year
5241

5342
def generate(self) -> dict:
5443
year = self.year or datetime.now().year
@@ -93,43 +82,14 @@ class CarouselLoadMoreParams(BaseModel):
9382
layout: str | None = None
9483
published_in: str = ""
9584

96-
@classmethod
97-
def from_web_input(cls) -> "CarouselLoadMoreParams":
98-
"""Construct from web.py's web.input(), handling string-encoded booleans."""
99-
# TODO: Delete this complicated code after we switch to FastAPI
100-
i = web.input(
101-
queryType="",
102-
q="",
103-
limit=18,
104-
page=1,
105-
sorts="",
106-
subject="",
107-
hasFulltextOnly=False,
108-
key="",
109-
layout=None,
110-
published_in="",
111-
)
112-
return cls(
113-
queryType=i.queryType,
114-
q=i.q,
115-
limit=int(i.limit),
116-
page=int(i.page),
117-
sorts=i.sorts,
118-
subject=i.subject,
119-
hasFulltextOnly=bool(i.hasFulltextOnly),
120-
key=i.key,
121-
layout=i.layout,
122-
published_in=i.published_in,
123-
)
124-
12585

12686
class CarouselCardPartial(PartialDataHandler):
12787
"""Handler for carousel "load_more" requests"""
12888

12989
MAX_VISIBLE_CARDS = 5
13090

131-
def __init__(self, params: CarouselLoadMoreParams | None = None):
132-
self.params = params or CarouselLoadMoreParams.from_web_input()
91+
def __init__(self, params: CarouselLoadMoreParams):
92+
self.params = params
13393

13494
def generate(self) -> dict:
13595
p = self.params
@@ -233,18 +193,14 @@ def _do_subjects_query(self, params: CarouselLoadMoreParams) -> list:
233193
class AffiliateLinksPartial(PartialDataHandler):
234194
"""Handler for affiliate links"""
235195

236-
def __init__(self, data: dict | None = None):
237-
if data is None:
238-
i = web.input(data=None)
239-
self.data = json.loads(i.data) if i.data else {}
240-
else:
241-
self.data = data
196+
def __init__(self, data: dict):
197+
self.data = data
242198

243199
def generate(self) -> dict:
244200
args = self.data.get("args", [])
245201

246202
if len(args) < 2:
247-
raise PartialResolutionError("Unexpected amount of arguments")
203+
raise ValueError("Unexpected amount of arguments")
248204

249205
macro = web.template.Template.globals['macros'].AffiliateLinks(args[0], args[1])
250206
return {"partials": str(macro)}
@@ -253,17 +209,13 @@ def generate(self) -> dict:
253209
class SearchFacetsPartial(PartialDataHandler):
254210
"""Handler for search facets sidebar and "selected facets" affordances."""
255211

256-
def __init__(self, data: dict | None = None, sfw: bool = False):
212+
def __init__(self, data: dict, sfw: bool = False):
257213
self.sfw = sfw
214+
self.data = data
258215
user = get_current_user()
259216
self.show_merge_authors = user and (
260217
user.is_librarian() or user.is_super_librarian() or user.is_admin()
261218
)
262-
if data is None:
263-
i = web.input(data=None)
264-
self.data = json.loads(i.data) if i.data else {}
265-
else:
266-
self.data = data
267219

268220
def generate(self) -> dict:
269221
path = self.data.get('path')
@@ -311,25 +263,15 @@ def generate(self) -> dict:
311263
class FullTextSuggestionsPartial(PartialDataHandler):
312264
"""Handler for rendering full-text search suggestions."""
313265

314-
def __init__(self, query: str | None = None):
315-
if query is None:
316-
i = web.input(data=None)
317-
self.query = i.get("data", "")
318-
self.webpy_mode = True
319-
else:
320-
self.query = query or ""
321-
self.webpy_mode = False
266+
def __init__(self, query: str):
267+
self.query = query or ""
322268
self.has_error: bool = False
323269

324270
def generate(self) -> dict:
325271
query = self.query
326272
data = fulltext_search(query)
327273
# Add caching headers only if there were no errors in the search results
328274
self.has_error = "error" in data
329-
if not self.has_error and self.webpy_mode:
330-
# Cache for 5 minutes (300 seconds)
331-
# TODO: remove when we rip out the old partials endpoints
332-
web.header('Cache-Control', 'public, max-age=300')
333275
hits = data.get('hits', [])
334276
if not hits['hits']:
335277
macro = '<div></div>'
@@ -343,15 +285,9 @@ def generate(self) -> dict:
343285
class BookPageListsPartial(PartialDataHandler):
344286
"""Handler for rendering the book page "Lists" section"""
345287

346-
def __init__(self, workId: str = "", editionId: str = ""):
347-
if not workId and not editionId:
348-
# Only read from web.input if no params provided
349-
i = web.input(workId="", editionId="")
350-
self.workId = i.workId
351-
self.editionId = i.editionId
352-
else:
353-
self.workId = workId
354-
self.editionId = editionId
288+
def __init__(self, workId: str, editionId: str):
289+
self.workId = workId
290+
self.editionId = editionId
355291

356292
def generate(self) -> dict:
357293
results: dict = {"partials": []}
@@ -388,98 +324,30 @@ class LazyCarouselParams(BaseModel):
388324
layout: str = "carousel"
389325
fallback: str | None = None
390326

391-
@classmethod
392-
def from_web_input(cls) -> "LazyCarouselParams":
393-
"""Construct from web.py's web.input(), handling string-encoded booleans."""
394-
i = web.input(
395-
query="",
396-
title=None,
397-
sort="new",
398-
key="",
399-
limit=20,
400-
search="false",
401-
has_fulltext_only=True,
402-
url=None,
403-
layout="carousel",
404-
fallback=None,
405-
)
406-
return cls(
407-
query=i.query,
408-
title=i.title,
409-
sort=i.sort,
410-
key=i.key,
411-
limit=int(i.limit),
412-
search=i.search != "false",
413-
has_fulltext_only=i.has_fulltext_only != "false",
414-
url=i.url,
415-
layout=i.layout,
416-
fallback=i.fallback,
417-
)
418-
419327

420328
class LazyCarouselPartial(PartialDataHandler):
421329
"""Handler for lazily-loaded query carousels."""
422330

423-
def __init__(self, params: LazyCarouselParams | None = None):
424-
self.i = params or LazyCarouselParams.from_web_input()
331+
def __init__(self, params: LazyCarouselParams):
332+
self.params = params
425333

426334
def generate(self) -> dict:
427335
macro = web.template.Template.globals['macros'].CacheableMacro(
428336
"RawQueryCarousel",
429-
self.i.query,
337+
self.params.query,
430338
lazy=False,
431-
title=self.i.title,
432-
sort=self.i.sort,
433-
key=self.i.key,
434-
limit=self.i.limit,
435-
search=self.i.search,
436-
has_fulltext_only=self.i.has_fulltext_only,
437-
url=self.i.url,
438-
layout=self.i.layout,
439-
fallback=self.i.fallback,
339+
title=self.params.title,
340+
sort=self.params.sort,
341+
key=self.params.key,
342+
limit=self.params.limit,
343+
search=self.params.search,
344+
has_fulltext_only=self.params.has_fulltext_only,
345+
url=self.params.url,
346+
layout=self.params.layout,
347+
fallback=self.params.fallback,
440348
)
441349
return {"partials": str(macro)}
442350

443351

444-
class PartialRequestResolver:
445-
# Maps `_component` values to PartialDataHandler subclasses
446-
component_mapping = { # noqa: RUF012
447-
"CarouselLoadMore": CarouselCardPartial,
448-
"AffiliateLinks": AffiliateLinksPartial,
449-
"SearchFacets": SearchFacetsPartial,
450-
"FulltextSearchSuggestion": FullTextSuggestionsPartial,
451-
"BPListsSection": BookPageListsPartial,
452-
"LazyCarousel": LazyCarouselPartial,
453-
"MyBooksDropperLists": MyBooksDropperListsPartial,
454-
"ReadingGoalProgress": ReadingGoalProgressPartial,
455-
}
456-
457-
@staticmethod
458-
def resolve(component: str) -> dict:
459-
"""Gets an instantiated PartialDataHandler and returns its generated dict"""
460-
handler = PartialRequestResolver.get_handler(component)
461-
return handler.generate()
462-
463-
@classmethod
464-
def get_handler(cls, component: str) -> PartialDataHandler:
465-
"""Instantiates and returns the requested handler"""
466-
if klass := cls.component_mapping.get(component):
467-
concrete_class = cast(type[PartialDataHandler], klass)
468-
return concrete_class()
469-
raise PartialResolutionError(f'No handler found for key "{component}"')
470-
471-
472-
@deprecated("migrated to fastapi")
473-
class Partials(delegate.page):
474-
path = r'/partials/([A-Za-z]+)'
475-
encoding = 'json'
476-
477-
def GET(self, component):
478-
return delegate.RawText(
479-
json.dumps(PartialRequestResolver.resolve(component)),
480-
content_type='application/json',
481-
)
482-
483-
484352
def setup():
485353
pass

0 commit comments

Comments
 (0)