mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-26 02:58:13 +00:00
Merge pull request #3981 from Itz-Agasta/test
Implement Lazy Loading Search Endpoint
This commit is contained in:
@@ -184,6 +184,10 @@ class APIMiddleware:
|
||||
formatter = load_format_dispatcher('v1', self.api.config.project_dir)
|
||||
for name, func in await api_impl.get_routes(self.api):
|
||||
endpoint = EndpointWrapper(name, func, self.api, formatter)
|
||||
# If func is a LazySearchEndpoint, give it a reference to wrapper
|
||||
# so it can replace wrapper.func dynamically
|
||||
if hasattr(func, 'set_wrapper'):
|
||||
func.set_wrapper(endpoint)
|
||||
self.app.add_route(f"/{name}", endpoint)
|
||||
if legacy_urls:
|
||||
self.app.add_route(f"/{name}.php", endpoint)
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import Optional, Any, Type, Dict, cast, Sequence, Tuple
|
||||
from functools import reduce
|
||||
import dataclasses
|
||||
from urllib.parse import urlencode
|
||||
import asyncio
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
@@ -124,6 +125,12 @@ def parse_geometry_details(adaptor: ASGIAdaptor, fmt: str) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def has_search_name(conn: sa.engine.Connection) -> bool:
|
||||
""" Check if the search_name table exists in the database.
|
||||
"""
|
||||
return sa.inspect(conn).has_table('search_name')
|
||||
|
||||
|
||||
async def status_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
|
||||
""" Server glue for /status endpoint. See API docs for details.
|
||||
"""
|
||||
@@ -441,6 +448,61 @@ async def polygons_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
|
||||
return build_response(params, params.formatting().format_result(results, fmt, {}))
|
||||
|
||||
|
||||
async def search_unavailable_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
|
||||
""" Server glue for /search endpoint in reverse-only mode.
|
||||
Returns 404 when search functionality is not available.
|
||||
"""
|
||||
params.raise_error('Search not available (reverse-only mode)', 404)
|
||||
|
||||
|
||||
class LazySearchEndpoint:
|
||||
"""
|
||||
Lazy-loading search endpoint that replaces itself after first successful check.
|
||||
|
||||
- Falcon: EndpointWrapper stores this instance in wrapper.func
|
||||
On first request, replace wrapper.func directly with real endpoint
|
||||
|
||||
- Starlette: _wrap_endpoint wraps this instance in a callback
|
||||
store a delegate function and call it on subsequent requests
|
||||
"""
|
||||
def __init__(self, api: NominatimAPIAsync, real_endpoint: EndpointFunc):
|
||||
self.api = api
|
||||
self.real_endpoint = real_endpoint
|
||||
self._lock = asyncio.Lock()
|
||||
self._wrapper: Any = None # Store reference to Falcon's EndpointWrapper
|
||||
self._delegate: Optional[EndpointFunc] = None
|
||||
|
||||
def set_wrapper(self, wrapper: Any) -> None:
|
||||
self._wrapper = wrapper
|
||||
|
||||
async def __call__(self, api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
|
||||
if self._delegate is None:
|
||||
async with self._lock:
|
||||
# Double-check after acquiring lock (thread safety)
|
||||
if self._delegate is None:
|
||||
try:
|
||||
async with api.begin() as conn:
|
||||
has_table = await conn.connection.run_sync(
|
||||
has_search_name)
|
||||
|
||||
if has_table:
|
||||
# For Starlette
|
||||
self._delegate = self.real_endpoint
|
||||
# For Falcon
|
||||
if self._wrapper is not None:
|
||||
self._wrapper.func = self.real_endpoint
|
||||
else:
|
||||
self._delegate = search_unavailable_endpoint
|
||||
if self._wrapper is not None:
|
||||
self._wrapper.func = search_unavailable_endpoint
|
||||
|
||||
except (PGCORE_ERROR, sa.exc.OperationalError, OSError):
|
||||
# No _delegate set, so retry on next request
|
||||
params.raise_error('Search temporarily unavailable', 503)
|
||||
|
||||
return await self._delegate(api, params)
|
||||
|
||||
|
||||
async def get_routes(api: NominatimAPIAsync) -> Sequence[Tuple[str, EndpointFunc]]:
|
||||
routes = [
|
||||
('status', status_endpoint),
|
||||
@@ -451,15 +513,13 @@ async def get_routes(api: NominatimAPIAsync) -> Sequence[Tuple[str, EndpointFunc
|
||||
('polygons', polygons_endpoint),
|
||||
]
|
||||
|
||||
def has_search_name(conn: sa.engine.Connection) -> bool:
|
||||
insp = sa.inspect(conn)
|
||||
return insp.has_table('search_name')
|
||||
|
||||
try:
|
||||
async with api.begin() as conn:
|
||||
if await conn.connection.run_sync(has_search_name):
|
||||
routes.append(('search', search_endpoint))
|
||||
except (PGCORE_ERROR, sa.exc.OperationalError):
|
||||
pass # ignored
|
||||
else:
|
||||
routes.append(('search', search_unavailable_endpoint))
|
||||
except (PGCORE_ERROR, sa.exc.OperationalError, OSError):
|
||||
routes.append(('search', LazySearchEndpoint(api, search_endpoint)))
|
||||
|
||||
return routes
|
||||
|
||||
Reference in New Issue
Block a user