From 00e3a752c9a749d2e2a0b8a406dd1b7e8e204765 Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Tue, 14 Mar 2023 14:21:35 +0100 Subject: [PATCH] split SearchResult type Use adapted types for the different result types. This makes it easier to have adapted output formatting and means there are only result fields that are filled. --- nominatim/api/__init__.py | 2 +- nominatim/api/core.py | 6 +- nominatim/api/lookup.py | 62 +++--- nominatim/api/results.py | 223 +++++++++++-------- nominatim/api/v1/format.py | 4 +- nominatim/api/v1/server_glue.py | 3 +- nominatim/clicmd/api.py | 2 +- test/python/api/test_result_formatting_v1.py | 54 ++--- test/python/api/test_results.py | 84 +++++++ test/python/cli/test_cmd_api.py | 4 +- 10 files changed, 285 insertions(+), 159 deletions(-) create mode 100644 test/python/api/test_results.py diff --git a/nominatim/api/__init__.py b/nominatim/api/__init__.py index d5d69755..9494d453 100644 --- a/nominatim/api/__init__.py +++ b/nominatim/api/__init__.py @@ -28,5 +28,5 @@ from .results import (SourceTable as SourceTable, AddressLines as AddressLines, WordInfo as WordInfo, WordInfos as WordInfos, - SearchResult as SearchResult) + DetailedResult as DetailedResult) from .localization import (Locales as Locales) diff --git a/nominatim/api/core.py b/nominatim/api/core.py index 415cd0aa..c94b5ecb 100644 --- a/nominatim/api/core.py +++ b/nominatim/api/core.py @@ -22,7 +22,7 @@ from nominatim.api.connection import SearchConnection from nominatim.api.status import get_status, StatusResult from nominatim.api.lookup import get_place_by_id from nominatim.api.types import PlaceRef, LookupDetails -from nominatim.api.results import SearchResult +from nominatim.api.results import DetailedResult class NominatimAPIAsync: @@ -127,7 +127,7 @@ class NominatimAPIAsync: async def lookup(self, place: PlaceRef, - details: LookupDetails) -> Optional[SearchResult]: + details: LookupDetails) -> Optional[DetailedResult]: """ Get detailed information about a place in the database. Returns None if there is no entry under the given ID. @@ -168,7 +168,7 @@ class NominatimAPI: def lookup(self, place: PlaceRef, - details: LookupDetails) -> Optional[SearchResult]: + details: LookupDetails) -> Optional[DetailedResult]: """ Get detailed information about a place in the database. """ return self._loop.run_until_complete(self._async_api.lookup(place, details)) diff --git a/nominatim/api/lookup.py b/nominatim/api/lookup.py index c42bf0c2..de06441d 100644 --- a/nominatim/api/lookup.py +++ b/nominatim/api/lookup.py @@ -8,6 +8,7 @@ Implementation of place lookup by ID. """ from typing import Optional +import datetime as dt import sqlalchemy as sa @@ -137,7 +138,7 @@ async def find_in_postcode(conn: SearchConnection, place: ntyp.PlaceRef, async def get_place_by_id(conn: SearchConnection, place: ntyp.PlaceRef, - details: ntyp.LookupDetails) -> Optional[nres.SearchResult]: + details: ntyp.LookupDetails) -> Optional[nres.DetailedResult]: """ Retrieve a place with additional details from the database. """ log().function('get_place_by_id', place=place, details=details) @@ -146,32 +147,35 @@ async def get_place_by_id(conn: SearchConnection, place: ntyp.PlaceRef, raise ValueError("lookup only supports geojosn polygon output.") row = await find_in_placex(conn, place, details) + log().var_dump('Result (placex)', row) if row is not None: - result = nres.create_from_placex_row(row) - log().var_dump('Result', result) - await nres.add_result_details(conn, result, details) - return result - - row = await find_in_osmline(conn, place, details) - if row is not None: - result = nres.create_from_osmline_row(row) - log().var_dump('Result', result) - await nres.add_result_details(conn, result, details) - return result - - row = await find_in_postcode(conn, place, details) - if row is not None: - result = nres.create_from_postcode_row(row) - log().var_dump('Result', result) - await nres.add_result_details(conn, result, details) - return result - - row = await find_in_tiger(conn, place, details) - if row is not None: - result = nres.create_from_tiger_row(row) - log().var_dump('Result', result) - await nres.add_result_details(conn, result, details) - return result - - # Nothing found under this ID. - return None + result = nres.create_from_placex_row(row, nres.DetailedResult) + else: + row = await find_in_osmline(conn, place, details) + log().var_dump('Result (osmline)', row) + if row is not None: + result = nres.create_from_osmline_row(row, nres.DetailedResult) + else: + row = await find_in_postcode(conn, place, details) + log().var_dump('Result (postcode)', row) + if row is not None: + result = nres.create_from_postcode_row(row, nres.DetailedResult) + else: + row = await find_in_tiger(conn, place, details) + log().var_dump('Result (tiger)', row) + if row is not None: + result = nres.create_from_tiger_row(row, nres.DetailedResult) + else: + return None + + # add missing details + assert result is not None + result.parent_place_id = row.parent_place_id + result.linked_place_id = getattr(row, 'linked_place_id', None) + indexed_date = getattr(row, 'indexed_date', None) + if indexed_date is not None: + result.indexed_date = indexed_date.replace(tzinfo=dt.timezone.utc) + + await nres.add_result_details(conn, result, details) + + return result diff --git a/nominatim/api/results.py b/nominatim/api/results.py index 10f03393..a8d6588a 100644 --- a/nominatim/api/results.py +++ b/nominatim/api/results.py @@ -11,7 +11,7 @@ Data classes are part of the public API while the functions are for internal use only. That's why they are implemented as free-standing functions instead of member functions. """ -from typing import Optional, Tuple, Dict, Sequence +from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type import enum import dataclasses import datetime as dt @@ -69,16 +69,15 @@ WordInfos = Sequence[WordInfo] @dataclasses.dataclass -class SearchResult: - """ Data class collecting all available information about a search result. +class BaseResult: + """ Data class collecting information common to all + types of search results. """ source_table: SourceTable category: Tuple[str, str] centroid: Point place_id : Optional[int] = None - parent_place_id: Optional[int] = None - linked_place_id: Optional[int] = None osm_object: Optional[Tuple[str, int]] = None admin_level: int = 15 @@ -96,8 +95,6 @@ class SearchResult: country_code: Optional[str] = None - indexed_date: Optional[dt.datetime] = None - address_rows: Optional[AddressLines] = None linked_rows: Optional[AddressLines] = None parented_rows: Optional[AddressLines] = None @@ -106,10 +103,6 @@ class SearchResult: geometry: Dict[str, str] = dataclasses.field(default_factory=dict) - def __post_init__(self) -> None: - if self.indexed_date is not None and self.indexed_date.tzinfo is None: - self.indexed_date = self.indexed_date.replace(tzinfo=dt.timezone.utc) - @property def lat(self) -> float: """ Get the latitude (or y) of the center point of the place. @@ -131,93 +124,138 @@ class SearchResult: """ return self.importance or (0.7500001 - (self.rank_search/40.0)) +BaseResultT = TypeVar('BaseResultT', bound=BaseResult) + +@dataclasses.dataclass +class DetailedResult(BaseResult): + """ A search result with more internal information from the database + added. + """ + parent_place_id: Optional[int] = None + linked_place_id: Optional[int] = None + indexed_date: Optional[dt.datetime] = None + def _filter_geometries(row: SaRow) -> Dict[str, str]: return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212 if k.startswith('geometry_')} -def create_from_placex_row(row: SaRow) -> SearchResult: - """ Construct a new SearchResult and add the data from the result row - from the placex table. +def create_from_placex_row(row: Optional[SaRow], + class_type: Type[BaseResultT]) -> Optional[BaseResultT]: + """ Construct a new result and add the data from the result row + from the placex table. 'class_type' defines the type of result + to return. Returns None if the row is None. """ - return SearchResult(source_table=SourceTable.PLACEX, - place_id=row.place_id, - parent_place_id=row.parent_place_id, - linked_place_id=row.linked_place_id, - osm_object=(row.osm_type, row.osm_id), - category=(row.class_, row.type), - admin_level=row.admin_level, - names=row.name, - address=row.address, - extratags=row.extratags, - housenumber=row.housenumber, - postcode=row.postcode, - wikipedia=row.wikipedia, - rank_address=row.rank_address, - rank_search=row.rank_search, - importance=row.importance, - country_code=row.country_code, - indexed_date=getattr(row, 'indexed_date'), - centroid=Point.from_wkb(row.centroid.data), - geometry=_filter_geometries(row)) - - -def create_from_osmline_row(row: SaRow) -> SearchResult: - """ Construct a new SearchResult and add the data from the result row - from the osmline table. + if row is None: + return None + + return class_type(source_table=SourceTable.PLACEX, + place_id=row.place_id, + osm_object=(row.osm_type, row.osm_id), + category=(row.class_, row.type), + admin_level=row.admin_level, + names=row.name, + address=row.address, + extratags=row.extratags, + housenumber=row.housenumber, + postcode=row.postcode, + wikipedia=row.wikipedia, + rank_address=row.rank_address, + rank_search=row.rank_search, + importance=row.importance, + country_code=row.country_code, + centroid=Point.from_wkb(row.centroid.data), + geometry=_filter_geometries(row)) + + +def create_from_osmline_row(row: Optional[SaRow], + class_type: Type[BaseResultT]) -> Optional[BaseResultT]: + """ Construct a new result and add the data from the result row + from the address interpolation table osmline. 'class_type' defines + the type of result to return. Returns None if the row is None. + + If the row contains a housenumber, then the housenumber is filled out. + Otherwise the result contains the interpolation information in extratags. """ - return SearchResult(source_table=SourceTable.OSMLINE, - place_id=row.place_id, - parent_place_id=row.parent_place_id, - osm_object=('W', row.osm_id), - category=('place', 'houses'), - address=row.address, - postcode=row.postcode, - extratags={'startnumber': str(row.startnumber), - 'endnumber': str(row.endnumber), - 'step': str(row.step)}, - country_code=row.country_code, - indexed_date=getattr(row, 'indexed_date'), - centroid=Point.from_wkb(row.centroid.data), - geometry=_filter_geometries(row)) - - -def create_from_tiger_row(row: SaRow) -> SearchResult: - """ Construct a new SearchResult and add the data from the result row - from the Tiger table. + if row is None: + return None + + hnr = getattr(row, 'housenumber', None) + + res = class_type(source_table=SourceTable.OSMLINE, + place_id=row.place_id, + osm_object=('W', row.osm_id), + category=('place', 'houses' if hnr is None else 'house'), + address=row.address, + postcode=row.postcode, + country_code=row.country_code, + centroid=Point.from_wkb(row.centroid.data), + geometry=_filter_geometries(row)) + + if hnr is None: + res.extratags = {'startnumber': str(row.startnumber), + 'endnumber': str(row.endnumber), + 'step': str(row.step)} + else: + res.housenumber = str(hnr) + + return res + + +def create_from_tiger_row(row: Optional[SaRow], + class_type: Type[BaseResultT]) -> Optional[BaseResultT]: + """ Construct a new result and add the data from the result row + from the Tiger data interpolation table. 'class_type' defines + the type of result to return. Returns None if the row is None. + + If the row contains a housenumber, then the housenumber is filled out. + Otherwise the result contains the interpolation information in extratags. """ - return SearchResult(source_table=SourceTable.TIGER, - place_id=row.place_id, - parent_place_id=row.parent_place_id, - category=('place', 'houses'), - postcode=row.postcode, - extratags={'startnumber': str(row.startnumber), - 'endnumber': str(row.endnumber), - 'step': str(row.step)}, - country_code='us', - centroid=Point.from_wkb(row.centroid.data), - geometry=_filter_geometries(row)) - - -def create_from_postcode_row(row: SaRow) -> SearchResult: - """ Construct a new SearchResult and add the data from the result row - from the postcode centroid table. + if row is None: + return None + + hnr = getattr(row, 'housenumber', None) + + res = class_type(source_table=SourceTable.TIGER, + place_id=row.place_id, + category=('place', 'houses' if hnr is None else 'house'), + postcode=row.postcode, + country_code='us', + centroid=Point.from_wkb(row.centroid.data), + geometry=_filter_geometries(row)) + + if hnr is None: + res.extratags = {'startnumber': str(row.startnumber), + 'endnumber': str(row.endnumber), + 'step': str(row.step)} + else: + res.housenumber = str(hnr) + + return res + + +def create_from_postcode_row(row: Optional[SaRow], + class_type: Type[BaseResultT]) -> Optional[BaseResultT]: + """ Construct a new result and add the data from the result row + from the postcode table. 'class_type' defines + the type of result to return. Returns None if the row is None. """ - return SearchResult(source_table=SourceTable.POSTCODE, - place_id=row.place_id, - parent_place_id=row.parent_place_id, - category=('place', 'postcode'), - names={'ref': row.postcode}, - rank_search=row.rank_search, - rank_address=row.rank_address, - country_code=row.country_code, - centroid=Point.from_wkb(row.centroid.data), - indexed_date=row.indexed_date, - geometry=_filter_geometries(row)) - - -async def add_result_details(conn: SearchConnection, result: SearchResult, + if row is None: + return None + + return class_type(source_table=SourceTable.POSTCODE, + place_id=row.place_id, + category=('place', 'postcode'), + names={'ref': row.postcode}, + rank_search=row.rank_search, + rank_address=row.rank_address, + country_code=row.country_code, + centroid=Point.from_wkb(row.centroid.data), + geometry=_filter_geometries(row)) + + +async def add_result_details(conn: SearchConnection, result: BaseResult, details: LookupDetails) -> None: """ Retrieve more details from the database according to the parameters specified in 'details'. @@ -262,7 +300,7 @@ def _result_row_to_address_row(row: SaRow) -> AddressLine: distance=row.distance) -async def complete_address_details(conn: SearchConnection, result: SearchResult) -> None: +async def complete_address_details(conn: SearchConnection, result: BaseResult) -> None: """ Retrieve information about places that make up the address of the result. """ housenumber = -1 @@ -292,6 +330,7 @@ async def complete_address_details(conn: SearchConnection, result: SearchResult) for row in await conn.execute(sql): result.address_rows.append(_result_row_to_address_row(row)) + # pylint: disable=consider-using-f-string def _placex_select_address_row(conn: SearchConnection, centroid: Point) -> SaSelect: @@ -308,7 +347,7 @@ def _placex_select_address_row(conn: SearchConnection, """ % centroid).label('distance')) -async def complete_linked_places(conn: SearchConnection, result: SearchResult) -> None: +async def complete_linked_places(conn: SearchConnection, result: BaseResult) -> None: """ Retrieve information about places that link to the result. """ result.linked_rows = [] @@ -322,7 +361,7 @@ async def complete_linked_places(conn: SearchConnection, result: SearchResult) - result.linked_rows.append(_result_row_to_address_row(row)) -async def complete_keywords(conn: SearchConnection, result: SearchResult) -> None: +async def complete_keywords(conn: SearchConnection, result: BaseResult) -> None: """ Retrieve information about the search terms used for this place. """ t = conn.t.search_name @@ -342,7 +381,7 @@ async def complete_keywords(conn: SearchConnection, result: SearchResult) -> Non result.address_keywords.append(WordInfo(*row)) -async def complete_parented_places(conn: SearchConnection, result: SearchResult) -> None: +async def complete_parented_places(conn: SearchConnection, result: BaseResult) -> None: """ Retrieve information about places that the result provides the address for. """ diff --git a/nominatim/api/v1/format.py b/nominatim/api/v1/format.py index 3f26f903..64892d66 100644 --- a/nominatim/api/v1/format.py +++ b/nominatim/api/v1/format.py @@ -92,8 +92,8 @@ def _add_parent_rows_grouped(writer: JsonWriter, rows: napi.AddressLines, writer.end_object().next() -@dispatch.format_func(napi.SearchResult, 'details-json') -def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -> str: +@dispatch.format_func(napi.DetailedResult, 'json') +def _format_search_json(result: napi.DetailedResult, options: Mapping[str, Any]) -> str: locales = options.get('locales', napi.Locales()) geom = result.geometry.get('geojson') centroid = result.centroid.to_geojson() diff --git a/nominatim/api/v1/server_glue.py b/nominatim/api/v1/server_glue.py index 35028526..64f76c43 100644 --- a/nominatim/api/v1/server_glue.py +++ b/nominatim/api/v1/server_glue.py @@ -210,8 +210,7 @@ async def details_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> raise params.error('No place with that OSM ID found.', status=404) output = formatting.format_result( - result, - 'details-json', + result, 'json', {'locales': locales, 'group_hierarchy': params.get_bool('group_hierarchy', False), 'icon_base_url': params.config().MAPICON_URL}) diff --git a/nominatim/clicmd/api.py b/nominatim/clicmd/api.py index 523013a6..a59002a9 100644 --- a/nominatim/clicmd/api.py +++ b/nominatim/clicmd/api.py @@ -282,7 +282,7 @@ class APIDetails: if result: output = api_output.format_result( result, - 'details-json', + 'json', {'locales': locales, 'group_hierarchy': args.group_hierarchy}) # reformat the result, so it is pretty-printed diff --git a/test/python/api/test_result_formatting_v1.py b/test/python/api/test_result_formatting_v1.py index 6b8a6b04..3c35e625 100644 --- a/test/python/api/test_result_formatting_v1.py +++ b/test/python/api/test_result_formatting_v1.py @@ -59,14 +59,14 @@ def test_status_format_json_full(): assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, ) -# SearchResult +# DetailedResult def test_search_details_minimal(): - search = napi.SearchResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0)) + search = napi.DetailedResult(napi.SourceTable.PLACEX, + ('place', 'thing'), + napi.Point(1.0, 2.0)) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) assert json.loads(result) == \ {'category': 'place', @@ -83,8 +83,8 @@ def test_search_details_minimal(): def test_search_details_full(): - import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0) - search = napi.SearchResult( + import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc) + search = napi.DetailedResult( source_table=napi.SourceTable.PLACEX, category=('amenity', 'bank'), centroid=napi.Point(56.947, -87.44), @@ -106,7 +106,7 @@ def test_search_details_full(): indexed_date = import_date ) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) assert json.loads(result) == \ {'place_id': 37563, @@ -140,12 +140,12 @@ def test_search_details_full(): ('ST_Polygon', True), ('ST_MultiPolygon', True)]) def test_search_details_no_geometry(gtype, isarea): - search = napi.SearchResult(napi.SourceTable.PLACEX, + search = napi.DetailedResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0), geometry={'type': gtype}) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) js = json.loads(result) assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]} @@ -153,12 +153,12 @@ def test_search_details_no_geometry(gtype, isarea): def test_search_details_with_geometry(): - search = napi.SearchResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0), - geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'}) + search = napi.DetailedResult(napi.SourceTable.PLACEX, + ('place', 'thing'), + napi.Point(1.0, 2.0), + geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'}) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) js = json.loads(result) assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]} @@ -166,10 +166,10 @@ def test_search_details_with_geometry(): def test_search_details_with_address_minimal(): - search = napi.SearchResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0), - address_rows=[ + search = napi.DetailedResult(napi.SourceTable.PLACEX, + ('place', 'thing'), + napi.Point(1.0, 2.0), + address_rows=[ napi.AddressLine(place_id=None, osm_object=None, category=('bnd', 'note'), @@ -180,9 +180,9 @@ def test_search_details_with_address_minimal(): isaddress=False, rank_address=10, distance=0.0) - ]) + ]) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) js = json.loads(result) assert js['address'] == [{'localname': '', @@ -194,10 +194,10 @@ def test_search_details_with_address_minimal(): def test_search_details_with_address_full(): - search = napi.SearchResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0), - address_rows=[ + search = napi.DetailedResult(napi.SourceTable.PLACEX, + ('place', 'thing'), + napi.Point(1.0, 2.0), + address_rows=[ napi.AddressLine(place_id=3498, osm_object=('R', 442), category=('bnd', 'note'), @@ -209,9 +209,9 @@ def test_search_details_with_address_full(): isaddress=True, rank_address=10, distance=0.034) - ]) + ]) - result = api_impl.format_result(search, 'details-json', {}) + result = api_impl.format_result(search, 'json', {}) js = json.loads(result) assert js['address'] == [{'localname': 'Trespass', diff --git a/test/python/api/test_results.py b/test/python/api/test_results.py new file mode 100644 index 00000000..7ea1fb1f --- /dev/null +++ b/test/python/api/test_results.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of Nominatim. (https://nominatim.org) +# +# Copyright (C) 2023 by the Nominatim developer community. +# For a full list of authors see the git log. +""" +Tests for result datatype helper functions. +""" +import struct + +import pytest +import pytest_asyncio +import sqlalchemy as sa + + +from nominatim.api import SourceTable, DetailedResult, Point +import nominatim.api.results as nresults + +class FakeCentroid: + def __init__(self, x, y): + self.data = struct.pack("=biidd", 1, 0x20000001, 4326, + x, y) + +class FakeRow: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + self._mapping = kwargs + + +def test_minimal_detailed_result(): + res = DetailedResult(SourceTable.PLACEX, + ('amenity', 'post_box'), + Point(23.1, 0.5)) + + assert res.lon == 23.1 + assert res.lat == 0.5 + assert res.calculated_importance() == pytest.approx(0.0000001) + +def test_detailed_result_custom_importance(): + res = DetailedResult(SourceTable.PLACEX, + ('amenity', 'post_box'), + Point(23.1, 0.5), + importance=0.4563) + + assert res.calculated_importance() == 0.4563 + + +@pytest.mark.parametrize('func', (nresults.create_from_placex_row, + nresults.create_from_osmline_row, + nresults.create_from_tiger_row, + nresults.create_from_postcode_row)) +def test_create_row_none(func): + assert func(None, DetailedResult) is None + + +@pytest.mark.parametrize('func', (nresults.create_from_osmline_row, + nresults.create_from_tiger_row)) +def test_create_row_with_housenumber(func): + row = FakeRow(place_id = 2345, osm_id = 111, housenumber = 4, + address = None, postcode = '99900', country_code = 'xd', + centroid = FakeCentroid(0, 0)) + + res = func(row, DetailedResult) + + assert res.housenumber == '4' + assert res.extratags is None + assert res.category == ('place', 'house') + + +@pytest.mark.parametrize('func', (nresults.create_from_osmline_row, + nresults.create_from_tiger_row)) +def test_create_row_without_housenumber(func): + row = FakeRow(place_id=2345, osm_id=111, + startnumber=1, endnumber=11, step=2, + address=None, postcode='99900', country_code='xd', + centroid=FakeCentroid(0, 0)) + + res = func(row, DetailedResult) + + assert res.housenumber is None + assert res.extratags == {'startnumber': '1', 'endnumber': '11', 'step': '2'} + assert res.category == ('place', 'houses') diff --git a/test/python/cli/test_cmd_api.py b/test/python/cli/test_cmd_api.py index 0b5dccfb..6ca96827 100644 --- a/test/python/cli/test_cmd_api.py +++ b/test/python/cli/test_cmd_api.py @@ -79,8 +79,8 @@ class TestCliDetailsCall: @pytest.fixture(autouse=True) def setup_status_mock(self, monkeypatch): - result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), - napi.Point(1.0, -3.0)) + result = napi.DetailedResult(napi.SourceTable.PLACEX, ('place', 'thing'), + napi.Point(1.0, -3.0)) monkeypatch.setattr(napi.NominatimAPI, 'lookup', lambda *args: result) -- 2.39.5