From 07e6c5cf6923b99df15d89e587dbdf9109d0836e Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Thu, 12 Oct 2023 15:31:20 +0200 Subject: [PATCH] make details API work with sqlite incl. unit tests --- nominatim/api/lookup.py | 27 ++++++--- nominatim/api/results.py | 22 +++---- nominatim/db/sqlalchemy_functions.py | 43 +++++++++++++- nominatim/db/sqlalchemy_types.py | 68 +++++++++++++++------ test/python/api/test_api_details.py | 89 +++++++++++++++++----------- 5 files changed, 172 insertions(+), 77 deletions(-) diff --git a/nominatim/api/lookup.py b/nominatim/api/lookup.py index e9181f47..b1f05c63 100644 --- a/nominatim/api/lookup.py +++ b/nominatim/api/lookup.py @@ -77,8 +77,8 @@ async def find_in_osmline(conn: SearchConnection, place: ntyp.PlaceRef, sql = sql.where(t.c.osm_id == place.osm_id).limit(1) if place.osm_class and place.osm_class.isdigit(): sql = sql.order_by(sa.func.greatest(0, - sa.func.least(int(place.osm_class) - t.c.endnumber), - t.c.startnumber - int(place.osm_class))) + int(place.osm_class) - t.c.endnumber, + t.c.startnumber - int(place.osm_class))) else: return None @@ -163,11 +163,10 @@ async def get_detailed_place(conn: SearchConnection, place: ntyp.PlaceRef, if details.geometry_output & ntyp.GeometryFormat.GEOJSON: def _add_geometry(sql: SaSelect, column: SaColumn) -> SaSelect: - return sql.add_columns(sa.literal_column(f""" - ST_AsGeoJSON(CASE WHEN ST_NPoints({column.name}) > 5000 - THEN ST_SimplifyPreserveTopology({column.name}, 0.0001) - ELSE {column.name} END) - """).label('geometry_geojson')) + return sql.add_columns(sa.func.ST_AsGeoJSON( + sa.case((sa.func.ST_NPoints(column) > 5000, + sa.func.ST_SimplifyPreserveTopology(column, 0.0001)), + else_=column)).label('geometry_geojson')) else: def _add_geometry(sql: SaSelect, column: SaColumn) -> SaSelect: return sql.add_columns(sa.func.ST_GeometryType(column).label('geometry_type')) @@ -183,6 +182,9 @@ async def get_detailed_place(conn: SearchConnection, place: ntyp.PlaceRef, # add missing details assert result is not None + if 'type' in result.geometry: + result.geometry['type'] = GEOMETRY_TYPE_MAP.get(result.geometry['type'], + result.geometry['type']) indexed_date = getattr(row, 'indexed_date', None) if indexed_date is not None: result.indexed_date = indexed_date.replace(tzinfo=dt.timezone.utc) @@ -236,3 +238,14 @@ async def get_simple_place(conn: SearchConnection, place: ntyp.PlaceRef, await nres.add_result_details(conn, [result], details) return result + + +GEOMETRY_TYPE_MAP = { + 'POINT': 'ST_Point', + 'MULTIPOINT': 'ST_MultiPoint', + 'LINESTRING': 'ST_LineString', + 'MULTILINESTRING': 'ST_MultiLineString', + 'POLYGON': 'ST_Polygon', + 'MULTIPOLYGON': 'ST_MultiPolygon', + 'GEOMETRYCOLLECTION': 'ST_GeometryCollection' +} diff --git a/nominatim/api/results.py b/nominatim/api/results.py index 166f5013..469f06d9 100644 --- a/nominatim/api/results.py +++ b/nominatim/api/results.py @@ -19,7 +19,7 @@ import datetime as dt import sqlalchemy as sa from nominatim.typing import SaSelect, SaRow -from nominatim.db.sqlalchemy_functions import CrosscheckNames +from nominatim.db.sqlalchemy_types import Geometry from nominatim.api.types import Point, Bbox, LookupDetails from nominatim.api.connection import SearchConnection from nominatim.api.logging import log @@ -589,7 +589,7 @@ async def complete_address_details(conn: SearchConnection, results: List[BaseRes if not lookup_ids: return - ltab = sa.func.json_array_elements(sa.type_coerce(lookup_ids, sa.JSON))\ + ltab = sa.func.JsonArrayEach(sa.type_coerce(lookup_ids, sa.JSON))\ .table_valued(sa.column('value', type_=sa.JSON)) # type: ignore[no-untyped-call] t = conn.t.placex @@ -608,7 +608,7 @@ async def complete_address_details(conn: SearchConnection, results: List[BaseRes .order_by('src_place_id')\ .order_by(sa.column('rank_address').desc())\ .order_by((taddr.c.place_id == ltab.c.value['pid'].as_integer()).desc())\ - .order_by(sa.case((CrosscheckNames(t.c.name, ltab.c.value['names']), 2), + .order_by(sa.case((sa.func.CrosscheckNames(t.c.name, ltab.c.value['names']), 2), (taddr.c.isaddress, 0), (sa.and_(taddr.c.fromarea, t.c.geometry.ST_Contains( @@ -652,7 +652,7 @@ async def complete_address_details(conn: SearchConnection, results: List[BaseRes parent_lookup_ids = list(filter(lambda e: e['pid'] != e['lid'], lookup_ids)) if parent_lookup_ids: - ltab = sa.func.json_array_elements(sa.type_coerce(parent_lookup_ids, sa.JSON))\ + ltab = sa.func.JsonArrayEach(sa.type_coerce(parent_lookup_ids, sa.JSON))\ .table_valued(sa.column('value', type_=sa.JSON)) # type: ignore[no-untyped-call] sql = sa.select(ltab.c.value['pid'].as_integer().label('src_place_id'), t.c.place_id, t.c.osm_type, t.c.osm_id, t.c.name, @@ -687,14 +687,10 @@ def _placex_select_address_row(conn: SearchConnection, return sa.select(t.c.place_id, t.c.osm_type, t.c.osm_id, t.c.name, t.c.class_.label('class'), t.c.type, t.c.admin_level, t.c.housenumber, - sa.literal_column("""ST_GeometryType(geometry) in - ('ST_Polygon','ST_MultiPolygon')""").label('fromarea'), + t.c.geometry.is_area().label('fromarea'), t.c.rank_address, - sa.literal_column( - f"""ST_DistanceSpheroid(geometry, - 'SRID=4326;{centroid.to_wkt()}'::geometry, - 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]') - """).label('distance')) + t.c.geometry.distance_spheroid( + sa.bindparam('centroid', value=centroid, type_=Geometry)).label('distance')) async def complete_linked_places(conn: SearchConnection, result: BaseResult) -> None: @@ -728,10 +724,10 @@ async def complete_keywords(conn: SearchConnection, result: BaseResult) -> None: sel = sa.select(t.c.word_id, t.c.word_token, t.c.word) for name_tokens, address_tokens in await conn.execute(sql): - for row in await conn.execute(sel.where(t.c.word_id == sa.any_(name_tokens))): + for row in await conn.execute(sel.where(t.c.word_id.in_(name_tokens))): result.name_keywords.append(WordInfo(*row)) - for row in await conn.execute(sel.where(t.c.word_id == sa.any_(address_tokens))): + for row in await conn.execute(sel.where(t.c.word_id.in_(address_tokens))): result.address_keywords.append(WordInfo(*row)) diff --git a/nominatim/db/sqlalchemy_functions.py b/nominatim/db/sqlalchemy_functions.py index 064fa6a3..f88c801e 100644 --- a/nominatim/db/sqlalchemy_functions.py +++ b/nominatim/db/sqlalchemy_functions.py @@ -10,7 +10,6 @@ Custom functions and expressions for SQLAlchemy. from typing import Any import sqlalchemy as sa -from sqlalchemy.sql.expression import FunctionElement from sqlalchemy.ext.compiler import compiles from nominatim.typing import SaColumn @@ -41,10 +40,11 @@ def select_index_placex_geometry_reverse_lookupplacenode(table: str) -> 'sa.Text f" AND {table}.osm_type = 'N'") -class CrosscheckNames(FunctionElement[Any]): +class CrosscheckNames(sa.sql.functions.GenericFunction[bool]): """ Check if in the given list of names in parameters 1 any of the names from the JSON array in parameter 2 are contained. """ + type = sa.Boolean() name = 'CrosscheckNames' inherit_cache = True @@ -54,3 +54,42 @@ def compile_crosscheck_names(element: SaColumn, arg1, arg2 = list(element.clauses) return "coalesce(avals(%s) && ARRAY(SELECT * FROM json_array_elements_text(%s)), false)" % ( compiler.process(arg1, **kw), compiler.process(arg2, **kw)) + + +@compiles(CrosscheckNames, 'sqlite') # type: ignore[no-untyped-call, misc] +def compile_sqlite_crosscheck_names(element: SaColumn, + compiler: 'sa.Compiled', **kw: Any) -> str: + arg1, arg2 = list(element.clauses) + return "EXISTS(SELECT *"\ + " FROM json_each(%s) as name, json_each(%s) as match_name"\ + " WHERE name.value = match_name.value)"\ + % (compiler.process(arg1, **kw), compiler.process(arg2, **kw)) + + +class JsonArrayEach(sa.sql.functions.GenericFunction[Any]): + """ Return elements of a json array as a set. + """ + name = 'JsonArrayEach' + inherit_cache = True + + +@compiles(JsonArrayEach) # type: ignore[no-untyped-call, misc] +def default_json_array_each(element: SaColumn, compiler: 'sa.Compiled', **kw: Any) -> str: + return "json_array_elements(%s)" % compiler.process(element.clauses, **kw) + + +@compiles(JsonArrayEach, 'sqlite') # type: ignore[no-untyped-call, misc] +def sqlite_json_array_each(element: SaColumn, compiler: 'sa.Compiled', **kw: Any) -> str: + return "json_each(%s)" % compiler.process(element.clauses, **kw) + + +class Greatest(sa.sql.functions.GenericFunction[Any]): + """ Function to compute maximum of all its input parameters. + """ + name = 'greatest' + inherit_cache = True + + +@compiles(Greatest, 'sqlite') # type: ignore[no-untyped-call, misc] +def sqlite_greatest(element: SaColumn, compiler: 'sa.Compiled', **kw: Any) -> str: + return "max(%s)" % compiler.process(element.clauses, **kw) diff --git a/nominatim/db/sqlalchemy_types.py b/nominatim/db/sqlalchemy_types.py index 9d1e48fa..8e8cc9c8 100644 --- a/nominatim/db/sqlalchemy_types.py +++ b/nominatim/db/sqlalchemy_types.py @@ -18,29 +18,26 @@ from nominatim.typing import SaColumn, SaBind #pylint: disable=all -SQLITE_FUNCTION_ALIAS = ( - ('ST_AsEWKB', sa.Text, 'AsEWKB'), - ('ST_AsGeoJSON', sa.Text, 'AsGeoJSON'), - ('ST_AsKML', sa.Text, 'AsKML'), - ('ST_AsSVG', sa.Text, 'AsSVG'), -) - -def _add_function_alias(func: str, ftype: type, alias: str) -> None: - _FuncDef = type(func, (sa.sql.functions.GenericFunction, ), { - "type": ftype, - "name": func, - "identifier": func, - "inherit_cache": True}) +class Geometry_DistanceSpheroid(sa.sql.expression.FunctionElement[float]): + """ Function to compute the spherical distance in meters. + """ + type = sa.Float() + name = 'Geometry_DistanceSpheroid' + inherit_cache = True - func_templ = f"{alias}(%s)" - def _sqlite_impl(element: Any, compiler: Any, **kw: Any) -> Any: - return func_templ % compiler.process(element.clauses, **kw) +@compiles(Geometry_DistanceSpheroid) # type: ignore[no-untyped-call, misc] +def _default_distance_spheroid(element: SaColumn, + compiler: 'sa.Compiled', **kw: Any) -> str: + return "ST_DistanceSpheroid(%s,"\ + " 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]')"\ + % compiler.process(element.clauses, **kw) - compiles(_FuncDef, 'sqlite')(_sqlite_impl) # type: ignore[no-untyped-call] -for alias in SQLITE_FUNCTION_ALIAS: - _add_function_alias(*alias) +@compiles(Geometry_DistanceSpheroid, 'sqlite') # type: ignore[no-untyped-call, misc] +def _spatialite_distance_spheroid(element: SaColumn, + compiler: 'sa.Compiled', **kw: Any) -> str: + return "Distance(%s, true)" % compiler.process(element.clauses, **kw) class Geometry(types.UserDefinedType): # type: ignore[type-arg] @@ -148,6 +145,39 @@ class Geometry(types.UserDefinedType): # type: ignore[type-arg] return sa.func.ST_LineLocatePoint(self, other, type_=sa.Float) + def distance_spheroid(self, other: SaColumn) -> SaColumn: + return Geometry_DistanceSpheroid(self, other) + + @compiles(Geometry, 'sqlite') # type: ignore[no-untyped-call] def get_col_spec(self, *args, **kwargs): # type: ignore[no-untyped-def] return 'GEOMETRY' + + +SQLITE_FUNCTION_ALIAS = ( + ('ST_AsEWKB', sa.Text, 'AsEWKB'), + ('ST_GeomFromEWKT', Geometry, 'GeomFromEWKT'), + ('ST_AsGeoJSON', sa.Text, 'AsGeoJSON'), + ('ST_AsKML', sa.Text, 'AsKML'), + ('ST_AsSVG', sa.Text, 'AsSVG'), +) + +def _add_function_alias(func: str, ftype: type, alias: str) -> None: + _FuncDef = type(func, (sa.sql.functions.GenericFunction, ), { + "type": ftype, + "name": func, + "identifier": func, + "inherit_cache": True}) + + func_templ = f"{alias}(%s)" + + def _sqlite_impl(element: Any, compiler: Any, **kw: Any) -> Any: + return func_templ % compiler.process(element.clauses, **kw) + + compiles(_FuncDef, 'sqlite')(_sqlite_impl) # type: ignore[no-untyped-call] + +for alias in SQLITE_FUNCTION_ALIAS: + _add_function_alias(*alias) + + + diff --git a/test/python/api/test_api_details.py b/test/python/api/test_api_details.py index ca14b93c..596876d4 100644 --- a/test/python/api/test_api_details.py +++ b/test/python/api/test_api_details.py @@ -15,7 +15,7 @@ import nominatim.api as napi @pytest.mark.parametrize('idobj', (napi.PlaceID(332), napi.OsmID('W', 4), napi.OsmID('W', 4, 'highway'))) -def test_lookup_in_placex(apiobj, idobj): +def test_lookup_in_placex(apiobj, frontend, idobj): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', @@ -31,7 +31,8 @@ def test_lookup_in_placex(apiobj, idobj): indexed_date=import_date, geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') - result = apiobj.api.details(idobj) + api = frontend(apiobj, options={'details'}) + result = api.details(idobj) assert result is not None @@ -69,7 +70,7 @@ def test_lookup_in_placex(apiobj, idobj): assert result.geometry == {'type': 'ST_LineString'} -def test_lookup_in_placex_minimal_info(apiobj): +def test_lookup_in_placex_minimal_info(apiobj, frontend): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', @@ -79,7 +80,8 @@ def test_lookup_in_placex_minimal_info(apiobj): indexed_date=import_date, geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') - result = apiobj.api.details(napi.PlaceID(332)) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332)) assert result is not None @@ -117,16 +119,17 @@ def test_lookup_in_placex_minimal_info(apiobj): assert result.geometry == {'type': 'ST_LineString'} -def test_lookup_in_placex_with_geometry(apiobj): +def test_lookup_in_placex_with_geometry(apiobj, frontend): apiobj.add_placex(place_id=332, geometry='LINESTRING(23 34, 23.1 34)') - result = apiobj.api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON) assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'} -def test_lookup_placex_with_address_details(apiobj): +def test_lookup_placex_with_address_details(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', name='Street', country_code='pl', @@ -143,7 +146,8 @@ def test_lookup_placex_with_address_details(apiobj): country_code='pl', rank_search=17, rank_address=16) - result = apiobj.api.details(napi.PlaceID(332), address_details=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), address_details=True) assert result.address_rows == [ napi.AddressLine(place_id=332, osm_object=('W', 4), @@ -172,18 +176,19 @@ def test_lookup_placex_with_address_details(apiobj): ] -def test_lookup_place_with_linked_places_none_existing(apiobj): +def test_lookup_place_with_linked_places_none_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', name='Street', country_code='pl', linked_place_id=45, rank_search=27, rank_address=26) - result = apiobj.api.details(napi.PlaceID(332), linked_places=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), linked_places=True) assert result.linked_rows == [] -def test_lookup_place_with_linked_places_existing(apiobj): +def test_lookup_place_with_linked_places_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', name='Street', country_code='pl', linked_place_id=45, @@ -197,7 +202,8 @@ def test_lookup_place_with_linked_places_existing(apiobj): country_code='pl', linked_place_id=332, rank_search=27, rank_address=26) - result = apiobj.api.details(napi.PlaceID(332), linked_places=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), linked_places=True) assert result.linked_rows == [ napi.AddressLine(place_id=1001, osm_object=('W', 5), @@ -213,18 +219,19 @@ def test_lookup_place_with_linked_places_existing(apiobj): ] -def test_lookup_place_with_parented_places_not_existing(apiobj): +def test_lookup_place_with_parented_places_not_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', name='Street', country_code='pl', parent_place_id=45, rank_search=27, rank_address=26) - result = apiobj.api.details(napi.PlaceID(332), parented_places=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), parented_places=True) assert result.parented_rows == [] -def test_lookup_place_with_parented_places_existing(apiobj): +def test_lookup_place_with_parented_places_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, class_='highway', type='residential', name='Street', country_code='pl', parent_place_id=45, @@ -238,7 +245,8 @@ def test_lookup_place_with_parented_places_existing(apiobj): country_code='pl', parent_place_id=332, rank_search=27, rank_address=26) - result = apiobj.api.details(napi.PlaceID(332), parented_places=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(332), parented_places=True) assert result.parented_rows == [ napi.AddressLine(place_id=1001, osm_object=('N', 5), @@ -250,7 +258,7 @@ def test_lookup_place_with_parented_places_existing(apiobj): @pytest.mark.parametrize('idobj', (napi.PlaceID(4924), napi.OsmID('W', 9928))) -def test_lookup_in_osmline(apiobj, idobj): +def test_lookup_in_osmline(apiobj, frontend, idobj): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_osmline(place_id=4924, osm_id=9928, parent_place_id=12, @@ -260,7 +268,8 @@ def test_lookup_in_osmline(apiobj, idobj): indexed_date=import_date, geometry='LINESTRING(23 34, 23 35)') - result = apiobj.api.details(idobj) + api = frontend(apiobj, options={'details'}) + result = api.details(idobj) assert result is not None @@ -298,7 +307,7 @@ def test_lookup_in_osmline(apiobj, idobj): assert result.geometry == {'type': 'ST_LineString'} -def test_lookup_in_osmline_split_interpolation(apiobj): +def test_lookup_in_osmline_split_interpolation(apiobj, frontend): apiobj.add_osmline(place_id=1000, osm_id=9, startnumber=2, endnumber=4, step=1) apiobj.add_osmline(place_id=1001, osm_id=9, @@ -306,18 +315,19 @@ def test_lookup_in_osmline_split_interpolation(apiobj): apiobj.add_osmline(place_id=1002, osm_id=9, startnumber=11, endnumber=20, step=1) + api = frontend(apiobj, options={'details'}) for i in range(1, 6): - result = apiobj.api.details(napi.OsmID('W', 9, str(i))) + result = api.details(napi.OsmID('W', 9, str(i))) assert result.place_id == 1000 for i in range(7, 11): - result = apiobj.api.details(napi.OsmID('W', 9, str(i))) + result = api.details(napi.OsmID('W', 9, str(i))) assert result.place_id == 1001 for i in range(12, 22): - result = apiobj.api.details(napi.OsmID('W', 9, str(i))) + result = api.details(napi.OsmID('W', 9, str(i))) assert result.place_id == 1002 -def test_lookup_osmline_with_address_details(apiobj): +def test_lookup_osmline_with_address_details(apiobj, frontend): apiobj.add_osmline(place_id=9000, osm_id=9, startnumber=2, endnumber=4, step=1, parent_place_id=332) @@ -337,7 +347,8 @@ def test_lookup_osmline_with_address_details(apiobj): country_code='pl', rank_search=17, rank_address=16) - result = apiobj.api.details(napi.PlaceID(9000), address_details=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(9000), address_details=True) assert result.address_rows == [ napi.AddressLine(place_id=332, osm_object=('W', 4), @@ -366,7 +377,7 @@ def test_lookup_osmline_with_address_details(apiobj): ] -def test_lookup_in_tiger(apiobj): +def test_lookup_in_tiger(apiobj, frontend): apiobj.add_tiger(place_id=4924, parent_place_id=12, startnumber=1, endnumber=4, step=1, @@ -377,7 +388,8 @@ def test_lookup_in_tiger(apiobj): osm_type='W', osm_id=6601223, geometry='LINESTRING(23 34, 23 35)') - result = apiobj.api.details(napi.PlaceID(4924)) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(4924)) assert result is not None @@ -415,7 +427,7 @@ def test_lookup_in_tiger(apiobj): assert result.geometry == {'type': 'ST_LineString'} -def test_lookup_tiger_with_address_details(apiobj): +def test_lookup_tiger_with_address_details(apiobj, frontend): apiobj.add_tiger(place_id=9000, startnumber=2, endnumber=4, step=1, parent_place_id=332) @@ -435,7 +447,8 @@ def test_lookup_tiger_with_address_details(apiobj): country_code='us', rank_search=17, rank_address=16) - result = apiobj.api.details(napi.PlaceID(9000), address_details=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(9000), address_details=True) assert result.address_rows == [ napi.AddressLine(place_id=332, osm_object=('W', 4), @@ -464,7 +477,7 @@ def test_lookup_tiger_with_address_details(apiobj): ] -def test_lookup_in_postcode(apiobj): +def test_lookup_in_postcode(apiobj, frontend): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_postcode(place_id=554, parent_place_id=152, @@ -474,7 +487,8 @@ def test_lookup_in_postcode(apiobj): indexed_date=import_date, geometry='POINT(-9.45 5.6)') - result = apiobj.api.details(napi.PlaceID(554)) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(554)) assert result is not None @@ -512,7 +526,7 @@ def test_lookup_in_postcode(apiobj): assert result.geometry == {'type': 'ST_Point'} -def test_lookup_postcode_with_address_details(apiobj): +def test_lookup_postcode_with_address_details(apiobj, frontend): apiobj.add_postcode(place_id=9000, parent_place_id=332, postcode='34 425', @@ -528,7 +542,8 @@ def test_lookup_postcode_with_address_details(apiobj): country_code='gb', rank_search=17, rank_address=16) - result = apiobj.api.details(napi.PlaceID(9000), address_details=True) + api = frontend(apiobj, options={'details'}) + result = api.details(napi.PlaceID(9000), address_details=True) assert result.address_rows == [ napi.AddressLine(place_id=9000, osm_object=None, @@ -559,18 +574,20 @@ def test_lookup_postcode_with_address_details(apiobj): @pytest.mark.parametrize('objid', [napi.PlaceID(1736), napi.OsmID('W', 55), napi.OsmID('N', 55, 'amenity')]) -def test_lookup_missing_object(apiobj, objid): +def test_lookup_missing_object(apiobj, frontend, objid): apiobj.add_placex(place_id=1, osm_type='N', osm_id=55, class_='place', type='suburb') - assert apiobj.api.details(objid) is None + api = frontend(apiobj, options={'details'}) + assert api.details(objid) is None @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML, napi.GeometryFormat.SVG, napi.GeometryFormat.TEXT)) -def test_lookup_unsupported_geometry(apiobj, gtype): +def test_lookup_unsupported_geometry(apiobj, frontend, gtype): apiobj.add_placex(place_id=332) + api = frontend(apiobj, options={'details'}) with pytest.raises(ValueError): - apiobj.api.details(napi.PlaceID(332), geometry_output=gtype) + api.details(napi.PlaceID(332), geometry_output=gtype) -- 2.39.5