Source code for pydotorg.domains.search.controllers

"""Search domain controllers."""

from __future__ import annotations

from typing import Annotated

from litestar import Controller, Request, get, post
from litestar.params import Body, Parameter
from litestar.response import Template

from pydotorg.core.search import SearchQuery, SearchResult, SearchService


[docs] class SearchAPIController(Controller): """API controller for search operations.""" path = "/api/v1/search" tags = ["Search"] @post("/") async def search_content( self, search_service: SearchService, query: Annotated[SearchQuery, Body(title="Search Query", description="Search query parameters")], ) -> SearchResult: """Search across all content types. Args: search_service: Search service instance. query: Search query parameters. Returns: Search results. """ return await search_service.search(query) @get("/autocomplete") async def autocomplete( self, search_service: SearchService, q: Annotated[str, Parameter(min_length=1, max_length=100, description="Search query")], limit: Annotated[int, Parameter(ge=1, le=10)] = 5, ) -> list[dict[str, str]]: """Get autocomplete suggestions. Args: search_service: Search service instance. q: Search query string. limit: Maximum number of suggestions. Returns: List of autocomplete suggestions. """ query = SearchQuery( query=q, limit=limit, attributes_to_retrieve=["id", "title", "url", "content_type"], ) results = await search_service.search(query) return [ { "id": hit.id, "title": hit.title, "url": hit.url, "type": hit.content_type, } for hit in results.hits ]
[docs] class SearchRenderController(Controller): """Controller for rendering search pages.""" path = "/search" include_in_schema = False @get("/") async def search_page( self, request: Request, search_service: SearchService, q: Annotated[str | None, Parameter(description="Search query")] = None, indexes: Annotated[list[str] | None, Parameter(description="Filter by content types")] = None, limit: Annotated[int, Parameter(ge=1, le=100)] = 20, offset: Annotated[int, Parameter(ge=0)] = 0, ) -> Template: """Render the search page with results. Args: request: The request object. search_service: Search service instance. q: Search query string. indexes: Optional content type filters. limit: Maximum results per page. offset: Results offset for pagination. Returns: Rendered search page template. """ results = None if q: query = SearchQuery( query=q, indexes=indexes, limit=limit, offset=offset, attributes_to_highlight=["title", "description", "content"], ) results = await search_service.search(query) is_htmx = request.headers.get("hx-request") == "true" if is_htmx and results: return Template( template_name="search/partials/results.html.jinja2", context={ "results": results, "query": q, }, ) return Template( template_name="search/index.html.jinja2", context={ "results": results, "query": q, "title": f"Search: {q}" if q else "Search", "description": "Search Python.org content", "indexes": indexes or [], "limit": limit, "offset": offset, }, ) @get("/results") async def search_results( self, request: Request, search_service: SearchService, q: Annotated[str, Parameter(min_length=1, description="Search query")], indexes: Annotated[list[str] | None, Parameter(description="Filter by content types")] = None, limit: Annotated[int, Parameter(ge=1, le=100)] = 20, offset: Annotated[int, Parameter(ge=0)] = 0, ) -> Template: """Render search results (htmx compatible). Args: request: The request object. search_service: Search service instance. q: Search query string. indexes: Optional content type filters. limit: Maximum results per page. offset: Results offset for pagination. Returns: Rendered search results template. """ query = SearchQuery( query=q, indexes=indexes, limit=limit, offset=offset, attributes_to_highlight=["title", "description", "content"], ) results = await search_service.search(query) is_htmx = request.headers.get("hx-request") == "true" if is_htmx: return Template( template_name="search/partials/results.html.jinja2", context={ "results": results, "query": q, }, ) return Template( template_name="search/results.html.jinja2", context={ "results": results, "query": q, "title": f"Search: {q}", "description": "Search results", }, )