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

View File

@@ -2,7 +2,7 @@
#
# 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.
"""
Complex datatypes used by the Nominatim API.
@@ -11,6 +11,7 @@ from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, \
Any, List, Sequence
from collections import abc
import dataclasses
import datetime as dt
import enum
import math
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]:
""" 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
@@ -412,6 +456,11 @@ class LookupDetails:
0.0 means the original geometry is kept. The higher the value, the
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
def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam: