introduce parameter for saving query statistics

This commit is contained in:
Sarah Hoffmann
2025-09-10 10:24:20 +02:00
parent 355cbcc7b8
commit 0b7bde2500
2 changed files with 105 additions and 45 deletions

View File

@@ -217,11 +217,13 @@ class NominatimAPIAsync:
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.LookupDetails.from_kwargs(params) details = ntyp.LookupDetails.from_kwargs(params)
async with self.begin(abs_timeout=timeout.abs) as conn: with details.query_stats as qs:
conn.set_query_timeout(self.query_timeout) async with self.begin(abs_timeout=timeout.abs) as conn:
if details.keywords: qs.log_time('start_query')
await nsearch.make_query_analyzer(conn) conn.set_query_timeout(self.query_timeout)
return await get_detailed_place(conn, place, details) if details.keywords:
await nsearch.make_query_analyzer(conn)
return await get_detailed_place(conn, place, details)
async def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults: async def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults:
""" Get simple information about a list of places. """ Get simple information about a list of places.
@@ -230,11 +232,13 @@ class NominatimAPIAsync:
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.LookupDetails.from_kwargs(params) details = ntyp.LookupDetails.from_kwargs(params)
async with self.begin(abs_timeout=timeout.abs) as conn: with details.query_stats as qs:
conn.set_query_timeout(self.query_timeout) async with self.begin(abs_timeout=timeout.abs) as conn:
if details.keywords: qs.log_time('start_query')
await nsearch.make_query_analyzer(conn) conn.set_query_timeout(self.query_timeout)
return await get_places(conn, places, details) if details.keywords:
await nsearch.make_query_analyzer(conn)
return await get_places(conn, places, details)
async def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]: async def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]:
""" Find a place by its coordinates. Also known as reverse geocoding. """ Find a place by its coordinates. Also known as reverse geocoding.
@@ -249,28 +253,32 @@ class NominatimAPIAsync:
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.ReverseDetails.from_kwargs(params) details = ntyp.ReverseDetails.from_kwargs(params)
async with self.begin(abs_timeout=timeout.abs) as conn: with details.query_stats as qs:
conn.set_query_timeout(self.query_timeout) async with self.begin(abs_timeout=timeout.abs) as conn:
if details.keywords: qs.log_time('start_query')
await nsearch.make_query_analyzer(conn) conn.set_query_timeout(self.query_timeout)
geocoder = ReverseGeocoder(conn, details, if details.keywords:
self.reverse_restrict_to_country_area) await nsearch.make_query_analyzer(conn)
return await geocoder.lookup(coord) geocoder = ReverseGeocoder(conn, details,
self.reverse_restrict_to_country_area)
return await geocoder.lookup(coord)
async def search(self, query: str, **params: Any) -> SearchResults: async def search(self, query: str, **params: Any) -> SearchResults:
""" Find a place by free-text search. Also known as forward geocoding. """ Find a place by free-text search. Also known as forward geocoding.
""" """
query = query.strip()
if not query:
raise UsageError('Nothing to search for.')
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
async with self.begin(abs_timeout=timeout.abs) as conn: details = ntyp.SearchDetails.from_kwargs(params)
conn.set_query_timeout(self.query_timeout) with details.query_stats as qs:
geocoder = nsearch.ForwardGeocoder(conn, ntyp.SearchDetails.from_kwargs(params), query = query.strip()
timeout) if not query:
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p.strip()) for p in query.split(',')] raise UsageError('Nothing to search for.')
return await geocoder.lookup(phrases)
async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout)
geocoder = nsearch.ForwardGeocoder(conn, details, timeout)
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p.strip()) for p in query.split(',')]
return await geocoder.lookup(phrases)
async def search_address(self, amenity: Optional[str] = None, async def search_address(self, amenity: Optional[str] = None,
street: Optional[str] = None, street: Optional[str] = None,
@@ -283,10 +291,8 @@ class NominatimAPIAsync:
""" Find an address using structured search. """ Find an address using structured search.
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
async with self.begin(abs_timeout=timeout.abs) as conn: details = ntyp.SearchDetails.from_kwargs(params)
conn.set_query_timeout(self.query_timeout) with details.query_stats as qs:
details = ntyp.SearchDetails.from_kwargs(params)
phrases: List[nsearch.Phrase] = [] phrases: List[nsearch.Phrase] = []
if amenity: if amenity:
@@ -325,6 +331,9 @@ class NominatimAPIAsync:
if amenity: if amenity:
details.layers |= ntyp.DataLayer.POI details.layers |= ntyp.DataLayer.POI
async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout)
geocoder = nsearch.ForwardGeocoder(conn, details, timeout) geocoder = nsearch.ForwardGeocoder(conn, details, timeout)
return await geocoder.lookup(phrases) return await geocoder.lookup(phrases)
@@ -335,22 +344,24 @@ class NominatimAPIAsync:
The near place may either be given as an unstructured search The near place may either be given as an unstructured search
query in itself or as coordinates. query in itself or as coordinates.
""" """
if not categories:
return SearchResults()
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.SearchDetails.from_kwargs(params) details = ntyp.SearchDetails.from_kwargs(params)
async with self.begin(abs_timeout=timeout.abs) as conn: with details.query_stats as qs:
conn.set_query_timeout(self.query_timeout) if not categories:
if near_query: return SearchResults()
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p) for p in near_query.split(',')]
else:
phrases = []
if details.keywords:
await nsearch.make_query_analyzer(conn)
geocoder = nsearch.ForwardGeocoder(conn, details, timeout) async with self.begin(abs_timeout=timeout.abs) as conn:
return await geocoder.lookup_pois(categories, phrases) qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout)
if near_query:
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p) for p in near_query.split(',')]
else:
phrases = []
if details.keywords:
await nsearch.make_query_analyzer(conn)
geocoder = nsearch.ForwardGeocoder(conn, details, timeout)
return await geocoder.lookup_pois(categories, phrases)
class NominatimAPI: class NominatimAPI:

View File

@@ -2,7 +2,7 @@
# #
# This file is part of Nominatim. (https://nominatim.org) # This file is part of Nominatim. (https://nominatim.org)
# #
# Copyright (C) 2024 by the Nominatim developer community. # Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log. # For a full list of authors see the git log.
""" """
Complex datatypes used by the Nominatim API. Complex datatypes used by the Nominatim API.
@@ -11,6 +11,7 @@ from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, \
Any, List, Sequence Any, List, Sequence
from collections import abc from collections import abc
import dataclasses import dataclasses
import datetime as dt
import enum import enum
import math import math
from struct import unpack from struct import unpack
@@ -334,6 +335,49 @@ class DataLayer(enum.Flag):
""" """
class QueryStatistics(dict[str, Any]):
""" A specialised dictionary for collecting query statistics.
"""
def __enter__(self) -> 'QueryStatistics':
self.log_time('start_function')
return self
def __exit__(self, *_: Any) -> None:
self.log_time('end_function')
self['total_time'] = (self['end_function'] - self['start_function']) \
/ dt.timedelta(microseconds=1)
if 'start_query' in self:
self['wait_time'] = (self['start_query'] - self['start_function']) \
/ dt.timedelta(microseconds=1)
else:
self['wait_time'] = 0
self['query_time'] = self['total_time'] - self['wait_time']
def __missing__(self, key: str) -> str:
return ''
def log_time(self, key: str) -> None:
self[key] = dt.datetime.now(tz=dt.timezone.utc)
class NoQueryStats:
""" Null object to use, when no query statistics are requested.
"""
def __enter__(self) -> 'NoQueryStats':
return self
def __exit__(self, *_: Any) -> None:
pass
def __setitem__(self, key: str, value: Any) -> None:
pass
def log_time(self, key: str) -> None:
pass
def format_country(cc: Any) -> List[str]: def format_country(cc: Any) -> List[str]:
""" Extract a list of country codes from the input which may be either """ Extract a list of country codes from the input which may be either
a string or list of strings. Filters out all values that are not a string or list of strings. Filters out all values that are not
@@ -412,6 +456,11 @@ class LookupDetails:
0.0 means the original geometry is kept. The higher the value, the 0.0 means the original geometry is kept. The higher the value, the
more the geometry gets simplified. more the geometry gets simplified.
""" """
query_stats: Union[QueryStatistics, NoQueryStats] = \
dataclasses.field(default_factory=NoQueryStats)
""" Optional QueryStatistics object collecting information about
runtime behaviour of the call.
"""
@classmethod @classmethod
def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam: def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam: