From 20d0fb35ce9d4d7c006a0e77dcf25edc2e8509b3 Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Wed, 13 Nov 2024 22:53:33 +0100 Subject: [PATCH] enable search endpoint only when search table is available --- src/nominatim_api/server/falcon/server.py | 2 +- src/nominatim_api/server/starlette/server.py | 7 ++-- src/nominatim_api/v1/__init__.py | 2 +- src/nominatim_api/v1/server_glue.py | 36 ++++++++++++++------ 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/nominatim_api/server/falcon/server.py b/src/nominatim_api/server/falcon/server.py index e252e3c8..13e79311 100644 --- a/src/nominatim_api/server/falcon/server.py +++ b/src/nominatim_api/server/falcon/server.py @@ -172,7 +172,7 @@ class APIMiddleware: assert self.app is not None legacy_urls = self.api.config.get_bool('SERVE_LEGACY_URLS') formatter = load_format_dispatcher('v1', self.api.config.project_dir) - for name, func in api_impl.ROUTES: + for name, func in await api_impl.get_routes(self.api): endpoint = EndpointWrapper(name, func, self.api, formatter) self.app.add_route(f"/{name}", endpoint) if legacy_urls: diff --git a/src/nominatim_api/server/starlette/server.py b/src/nominatim_api/server/starlette/server.py index 9d014920..e6c97693 100644 --- a/src/nominatim_api/server/starlette/server.py +++ b/src/nominatim_api/server/starlette/server.py @@ -7,7 +7,8 @@ """ Server implementation using the starlette webserver framework. """ -from typing import Any, Optional, Mapping, Callable, cast, Coroutine, Dict, Awaitable +from typing import Any, Optional, Mapping, Callable, cast, Coroutine, Dict, \ + Awaitable, AsyncIterator from pathlib import Path import datetime as dt import asyncio @@ -150,12 +151,12 @@ def get_application(project_dir: Path, } @contextlib.asynccontextmanager - async def lifespan(app: Starlette) -> None: + async def lifespan(app: Starlette) -> AsyncIterator[Any]: app.state.API = NominatimAPIAsync(project_dir, environ) config = app.state.API.config legacy_urls = config.get_bool('SERVE_LEGACY_URLS') - for name, func in api_impl.ROUTES: + for name, func in await api_impl.get_routes(app.state.API): endpoint = _wrap_endpoint(func) app.routes.append(Route(f"/{name}", endpoint=endpoint)) if legacy_urls: diff --git a/src/nominatim_api/v1/__init__.py b/src/nominatim_api/v1/__init__.py index f49de2a2..2304994f 100644 --- a/src/nominatim_api/v1/__init__.py +++ b/src/nominatim_api/v1/__init__.py @@ -8,4 +8,4 @@ Implementation of API version v1 (aka the legacy version). """ -from .server_glue import ROUTES as ROUTES +from .server_glue import get_routes as get_routes diff --git a/src/nominatim_api/v1/server_glue.py b/src/nominatim_api/v1/server_glue.py index ee502d8b..a6450bf2 100644 --- a/src/nominatim_api/v1/server_glue.py +++ b/src/nominatim_api/v1/server_glue.py @@ -8,7 +8,7 @@ Generic part of the server implementation of the v1 API. Combine with the scaffolding provided for the various Python ASGI frameworks. """ -from typing import Optional, Any, Type, Dict, cast +from typing import Optional, Any, Type, Dict, cast, Sequence, Tuple from functools import reduce import dataclasses from urllib.parse import urlencode @@ -25,7 +25,8 @@ from ..results import DetailedResult, ReverseResults, SearchResult, SearchResult from ..localization import Locales from . import helpers from ..server import content_types as ct -from ..server.asgi_adaptor import ASGIAdaptor +from ..server.asgi_adaptor import ASGIAdaptor, EndpointFunc +from ..sql.async_core_library import PGCORE_ERROR def build_response(adaptor: ASGIAdaptor, output: str, status: int = 200, @@ -417,12 +418,25 @@ async def polygons_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any: return build_response(params, params.formatting().format_result(results, fmt, {})) -ROUTES = [ - ('status', status_endpoint), - ('details', details_endpoint), - ('reverse', reverse_endpoint), - ('lookup', lookup_endpoint), - ('search', search_endpoint), - ('deletable', deletable_endpoint), - ('polygons', polygons_endpoint), -] +async def get_routes(api: NominatimAPIAsync) -> Sequence[Tuple[str, EndpointFunc]]: + routes = [ + ('status', status_endpoint), + ('details', details_endpoint), + ('reverse', reverse_endpoint), + ('lookup', lookup_endpoint), + ('deletable', deletable_endpoint), + ('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 + + return routes