]> git.openstreetmap.org Git - nominatim.git/commitdiff
switch API parameters to keyword arguments
authorSarah Hoffmann <lonvia@denofr.de>
Thu, 18 May 2023 15:42:23 +0000 (17:42 +0200)
committerSarah Hoffmann <lonvia@denofr.de>
Thu, 18 May 2023 15:42:23 +0000 (17:42 +0200)
This switches the input parameters for API calls to a generic
keyword argument catch-all which is then loaded into a dataclass
where the parameters are checked and forwarded to internal
function.

The dataclass gives more flexibility with the parameters and makes
it easier to reuse common parameters for the different API calls.

nominatim/api/__init__.py
nominatim/api/core.py
nominatim/api/reverse.py
nominatim/api/types.py
nominatim/api/v1/server_glue.py
nominatim/clicmd/api.py
test/python/api/test_api_details.py
test/python/api/test_api_lookup.py
test/python/api/test_api_reverse.py
test/python/cli/test_cmd_api.py

index 9f3623799ac9c763c43ab6389f7818dab97e6c74..794cd96c054d2f68c2d888b7ec54bc9818ea0a9d 100644 (file)
@@ -23,7 +23,6 @@ from .types import (PlaceID as PlaceID,
                     Point as Point,
                     Bbox as Bbox,
                     GeometryFormat as GeometryFormat,
-                    LookupDetails as LookupDetails,
                     DataLayer as DataLayer)
 from .results import (SourceTable as SourceTable,
                       AddressLine as AddressLine,
index 29325b08089603106cb6dfae44dd731ae0908727..b69946c5c048212e7b546ba61832d5a890a0a86e 100644 (file)
@@ -22,7 +22,7 @@ from nominatim.api.connection import SearchConnection
 from nominatim.api.status import get_status, StatusResult
 from nominatim.api.lookup import get_detailed_place, get_simple_place
 from nominatim.api.reverse import ReverseGeocoder
-from nominatim.api.types import PlaceRef, LookupDetails, AnyPoint, DataLayer
+import nominatim.api.types as ntyp
 from nominatim.api.results import DetailedResult, ReverseResult, SearchResults
 
 
@@ -130,32 +130,28 @@ class NominatimAPIAsync:
         return status
 
 
-    async def details(self, place: PlaceRef,
-                      details: Optional[LookupDetails] = None) -> Optional[DetailedResult]:
+    async def details(self, place: ntyp.PlaceRef, **params: Any) -> Optional[DetailedResult]:
         """ Get detailed information about a place in the database.
 
             Returns None if there is no entry under the given ID.
         """
         async with self.begin() as conn:
-            return await get_detailed_place(conn, place, details or LookupDetails())
+            return await get_detailed_place(conn, place,
+                                            ntyp.LookupDetails.from_kwargs(params))
 
 
-    async def lookup(self, places: Sequence[PlaceRef],
-                      details: Optional[LookupDetails] = None) -> SearchResults:
+    async def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults:
         """ Get simple information about a list of places.
 
             Returns a list of place information for all IDs that were found.
         """
-        if details is None:
-            details = LookupDetails()
+        details = ntyp.LookupDetails.from_kwargs(params)
         async with self.begin() as conn:
             return SearchResults(filter(None,
                                         [await get_simple_place(conn, p, details) for p in places]))
 
 
-    async def reverse(self, coord: AnyPoint, max_rank: Optional[int] = None,
-                      layer: Optional[DataLayer] = None,
-                      details: Optional[LookupDetails] = None) -> Optional[ReverseResult]:
+    async def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]:
         """ Find a place by its coordinates. Also known as reverse geocoding.
 
             Returns the closest result that can be found or None if
@@ -166,14 +162,8 @@ class NominatimAPIAsync:
             # There are no results to be expected outside valid coordinates.
             return None
 
-        if layer is None:
-            layer = DataLayer.ADDRESS | DataLayer.POI
-
-        max_rank = max(0, min(max_rank or 30, 30))
-
         async with self.begin() as conn:
-            geocoder = ReverseGeocoder(conn, max_rank, layer,
-                                       details or LookupDetails())
+            geocoder = ReverseGeocoder(conn, ntyp.ReverseDetails.from_kwargs(params))
             return await geocoder.lookup(coord)
 
 
@@ -208,29 +198,24 @@ class NominatimAPI:
         return self._loop.run_until_complete(self._async_api.status())
 
 
-    def details(self, place: PlaceRef,
-                details: Optional[LookupDetails] = None) -> Optional[DetailedResult]:
+    def details(self, place: ntyp.PlaceRef, **params: Any) -> Optional[DetailedResult]:
         """ Get detailed information about a place in the database.
         """
-        return self._loop.run_until_complete(self._async_api.details(place, details))
+        return self._loop.run_until_complete(self._async_api.details(place, **params))
 
 
-    def lookup(self, places: Sequence[PlaceRef],
-               details: Optional[LookupDetails] = None) -> SearchResults:
+    def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults:
         """ Get simple information about a list of places.
 
             Returns a list of place information for all IDs that were found.
         """
-        return self._loop.run_until_complete(self._async_api.lookup(places, details))
+        return self._loop.run_until_complete(self._async_api.lookup(places, **params))
 
 
-    def reverse(self, coord: AnyPoint, max_rank: Optional[int] = None,
-                layer: Optional[DataLayer] = None,
-                details: Optional[LookupDetails] = None) -> Optional[ReverseResult]:
+    def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]:
         """ Find a place by its coordinates. Also known as reverse geocoding.
 
             Returns the closest result that can be found or None if
             no place matches the given criteria.
         """
-        return self._loop.run_until_complete(
-                   self._async_api.reverse(coord, max_rank, layer, details))
+        return self._loop.run_until_complete(self._async_api.reverse(coord, **params))
index 42fe8f36af7deb613d3db15d3d850e05048e1a75..ccd1758cf345b8d0c7ffbbd593e377440aaf092c 100644 (file)
@@ -16,7 +16,7 @@ from nominatim.typing import SaColumn, SaSelect, SaFromClause, SaLabel, SaRow
 from nominatim.api.connection import SearchConnection
 import nominatim.api.results as nres
 from nominatim.api.logging import log
-from nominatim.api.types import AnyPoint, DataLayer, LookupDetails, GeometryFormat, Bbox
+from nominatim.api.types import AnyPoint, DataLayer, ReverseDetails, GeometryFormat, Bbox
 
 # In SQLAlchemy expression which compare with NULL need to be expressed with
 # the equal sign.
@@ -87,23 +87,34 @@ class ReverseGeocoder:
         coordinate.
     """
 
-    def __init__(self, conn: SearchConnection, max_rank: int, layer: DataLayer,
-                 details: LookupDetails) -> None:
+    def __init__(self, conn: SearchConnection, params: ReverseDetails) -> None:
         self.conn = conn
-        self.max_rank = max_rank
-        self.layer = layer
-        self.details = details
+        self.params = params
+
+
+    @property
+    def max_rank(self) -> int:
+        """ Return the maximum configured rank.
+        """
+        return self.params.max_rank
+
+
+    def has_geometries(self) -> bool:
+        """ Check if any geometries are requested.
+        """
+        return bool(self.params.geometry_output)
+
 
     def layer_enabled(self, *layer: DataLayer) -> bool:
         """ Return true when any of the given layer types are requested.
         """
-        return any(self.layer & l for l in layer)
+        return any(self.params.layers & l for l in layer)
 
 
     def layer_disabled(self, *layer: DataLayer) -> bool:
         """ Return true when none of the given layer types is requested.
         """
-        return not any(self.layer & l for l in layer)
+        return not any(self.params.layers & l for l in layer)
 
 
     def has_feature_layers(self) -> bool:
@@ -112,21 +123,21 @@ class ReverseGeocoder:
         return self.layer_enabled(DataLayer.RAILWAY, DataLayer.MANMADE, DataLayer.NATURAL)
 
     def _add_geometry_columns(self, sql: SaSelect, col: SaColumn) -> SaSelect:
-        if not self.details.geometry_output:
+        if not self.has_geometries():
             return sql
 
         out = []
 
-        if self.details.geometry_simplification > 0.0:
-            col = col.ST_SimplifyPreserveTopology(self.details.geometry_simplification)
+        if self.params.geometry_simplification > 0.0:
+            col = col.ST_SimplifyPreserveTopology(self.params.geometry_simplification)
 
-        if self.details.geometry_output & GeometryFormat.GEOJSON:
+        if self.params.geometry_output & GeometryFormat.GEOJSON:
             out.append(col.ST_AsGeoJSON().label('geometry_geojson'))
-        if self.details.geometry_output & GeometryFormat.TEXT:
+        if self.params.geometry_output & GeometryFormat.TEXT:
             out.append(col.ST_AsText().label('geometry_text'))
-        if self.details.geometry_output & GeometryFormat.KML:
+        if self.params.geometry_output & GeometryFormat.KML:
             out.append(col.ST_AsKML().label('geometry_kml'))
-        if self.details.geometry_output & GeometryFormat.SVG:
+        if self.params.geometry_output & GeometryFormat.SVG:
             out.append(col.ST_AsSVG().label('geometry_svg'))
 
         return sql.add_columns(*out)
@@ -233,7 +244,7 @@ class ReverseGeocoder:
                         inner.c.postcode, inner.c.country_code,
                         inner.c.distance)
 
-        if self.details.geometry_output:
+        if self.has_geometries():
             sub = sql.subquery()
             sql = self._add_geometry_columns(sql, sub.c.centroid)
 
@@ -263,7 +274,7 @@ class ReverseGeocoder:
                         inner.c.postcode,
                         inner.c.distance)
 
-        if self.details.geometry_output:
+        if self.has_geometries():
             sub = sql.subquery()
             sql = self._add_geometry_columns(sql, sub.c.centroid)
 
@@ -514,9 +525,7 @@ class ReverseGeocoder:
         """ Look up a single coordinate. Returns the place information,
             if a place was found near the coordinates or None otherwise.
         """
-        log().function('reverse_lookup',
-                       coord=coord, max_rank=self.max_rank,
-                       layer=self.layer, details=self.details)
+        log().function('reverse_lookup', coord=coord, params=self.params)
 
 
         wkt = WKTElement(f'POINT({coord[0]} {coord[1]})', srid=4326)
@@ -539,6 +548,6 @@ class ReverseGeocoder:
             result.distance = row.distance
             if hasattr(row, 'bbox'):
                 result.bbox = Bbox.from_wkb(row.bbox.data)
-            await nres.add_result_details(self.conn, result, self.details)
+            await nres.add_result_details(self.conn, result, self.params)
 
         return result
index e262935a9cd2ce3fa5736e25f59d17841dc40055..0e4340fee02970a20e486f6d62c3d76e11609461 100644 (file)
@@ -7,11 +7,13 @@
 """
 Complex datatypes used by the Nominatim API.
 """
-from typing import Optional, Union, Tuple, NamedTuple
+from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, Any
 import dataclasses
 import enum
 from struct import unpack
 
+from nominatim.errors import UsageError
+
 @dataclasses.dataclass
 class PlaceID:
     """ Reference an object by Nominatim's internal ID.
@@ -164,10 +166,22 @@ class GeometryFormat(enum.Flag):
     TEXT = enum.auto()
 
 
+class DataLayer(enum.Flag):
+    """ Layer types that can be selected for reverse and forward search.
+    """
+    POI = enum.auto()
+    ADDRESS = enum.auto()
+    RAILWAY = enum.auto()
+    MANMADE = enum.auto()
+    NATURAL = enum.auto()
+
+
+TParam = TypeVar('TParam', bound='LookupDetails') # pylint: disable=invalid-name
+
 @dataclasses.dataclass
 class LookupDetails:
     """ Collection of parameters that define the amount of details
-        returned with a search result.
+        returned with a lookup or details result.
     """
     geometry_output: GeometryFormat = GeometryFormat.NONE
     """ Add the full geometry of the place to the result. Multiple
@@ -194,12 +208,39 @@ class LookupDetails:
         more the geometry gets simplified.
     """
 
+    @classmethod
+    def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam:
+        """ Load the data fields of the class from a dictionary.
+            Unknown entries in the dictionary are ignored, missing ones
+            get the default setting.
 
-class DataLayer(enum.Flag):
-    """ Layer types that can be selected for reverse and forward search.
+            The function supports type checking and throws a UsageError
+            when the value does not fit.
+        """
+        def _check_field(v: Any, field: 'dataclasses.Field[Any]') -> Any:
+            if v is None:
+                return field.default_factory() \
+                       if field.default_factory != dataclasses.MISSING \
+                       else field.default
+            if field.metadata and 'transform' in field.metadata:
+                return field.metadata['transform'](v)
+            if not isinstance(v, field.type):
+                raise UsageError(f"Parameter '{field.name}' needs to be of {field.type!s}.")
+            return v
+
+        return cls(**{f.name: _check_field(kwargs[f.name], f)
+                      for f in dataclasses.fields(cls) if f.name in kwargs})
+
+
+@dataclasses.dataclass
+class ReverseDetails(LookupDetails):
+    """ Collection of parameters for the reverse call.
+    """
+    max_rank: int = dataclasses.field(default=30,
+                                      metadata={'transform': lambda v: max(0, min(v, 30))}
+                                     )
+    """ Highest address rank to return.
+    """
+    layers: DataLayer = DataLayer.ADDRESS | DataLayer.POI
+    """ Filter which kind of data to include.
     """
-    POI = enum.auto()
-    ADDRESS = enum.auto()
-    RAILWAY = enum.auto()
-    MANMADE = enum.auto()
-    NATURAL = enum.auto()
index 68cf58c285b37858dc90828cde711ac79824743a..fb91a0cd1b11737edb184cd981dd41379ffddbc3 100644 (file)
@@ -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, Callable, NoReturn, cast
+from typing import Optional, Any, Type, Callable, NoReturn, Dict, cast
 from functools import reduce
 import abc
 import math
@@ -226,31 +226,32 @@ class ASGIAdaptor(abc.ABC):
         return fmt
 
 
-    def parse_geometry_details(self, fmt: str) -> napi.LookupDetails:
+    def parse_geometry_details(self, fmt: str) -> Dict[str, Any]:
         """ Create details strucutre from the supplied geometry parameters.
         """
-        details = napi.LookupDetails(address_details=True,
-                                     geometry_simplification=
-                                       self.get_float('polygon_threshold', 0.0))
         numgeoms = 0
+        output = napi.GeometryFormat.NONE
         if self.get_bool('polygon_geojson', False):
-            details.geometry_output |= napi.GeometryFormat.GEOJSON
+            output |= napi.GeometryFormat.GEOJSON
             numgeoms += 1
         if fmt not in ('geojson', 'geocodejson'):
             if self.get_bool('polygon_text', False):
-                details.geometry_output |= napi.GeometryFormat.TEXT
+                output |= napi.GeometryFormat.TEXT
                 numgeoms += 1
             if self.get_bool('polygon_kml', False):
-                details.geometry_output |= napi.GeometryFormat.KML
+                output |= napi.GeometryFormat.KML
                 numgeoms += 1
             if self.get_bool('polygon_svg', False):
-                details.geometry_output |= napi.GeometryFormat.SVG
+                output |= napi.GeometryFormat.SVG
                 numgeoms += 1
 
         if numgeoms > self.config().get_int('POLYGON_OUTPUT_MAX_TYPES'):
             self.raise_error('Too many polgyon output options selected.')
 
-        return details
+        return {'address_details': True,
+                'geometry_simplification': self.get_float('polygon_threshold', 0.0),
+                'geometry_output': output
+               }
 
 
 async def status_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> Any:
@@ -285,17 +286,17 @@ async def details_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) ->
 
     debug = params.setup_debugging()
 
-    details = napi.LookupDetails(address_details=params.get_bool('addressdetails', False),
-                                 linked_places=params.get_bool('linkedplaces', False),
-                                 parented_places=params.get_bool('hierarchy', False),
-                                 keywords=params.get_bool('keywords', False))
-
-    if params.get_bool('polygon_geojson', False):
-        details.geometry_output = napi.GeometryFormat.GEOJSON
-
     locales = napi.Locales.from_accept_languages(params.get_accepted_languages())
 
-    result = await api.details(place, details)
+    result = await api.details(place,
+                               address_details=params.get_bool('addressdetails', False),
+                               linked_places=params.get_bool('linkedplaces', False),
+                               parented_places=params.get_bool('hierarchy', False),
+                               keywords=params.get_bool('keywords', False),
+                               geometry_output = napi.GeometryFormat.GEOJSON
+                                                 if params.get_bool('polygon_geojson', False)
+                                                 else napi.GeometryFormat.NONE
+                              )
 
     if debug:
         return params.build_response(loglib.get_and_disable())
@@ -318,15 +319,13 @@ async def reverse_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) ->
     debug = params.setup_debugging()
     coord = napi.Point(params.get_float('lon'), params.get_float('lat'))
     locales = napi.Locales.from_accept_languages(params.get_accepted_languages())
-    details = params.parse_geometry_details(fmt)
-
     zoom = max(0, min(18, params.get_int('zoom', 18)))
 
+    details = params.parse_geometry_details(fmt)
+    details['max_rank'] = REVERSE_MAX_RANKS[zoom]
+    details['layers'] = params.get_layers()
 
-    result = await api.reverse(coord, REVERSE_MAX_RANKS[zoom],
-                               params.get_layers() or
-                                 napi.DataLayer.ADDRESS | napi.DataLayer.POI,
-                               details)
+    result = await api.reverse(coord, **details)
 
     if debug:
         return params.build_response(loglib.get_and_disable())
@@ -357,7 +356,7 @@ async def lookup_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> A
             places.append(napi.OsmID(oid[0], int(oid[1:])))
 
     if places:
-        results = await api.lookup(places, details)
+        results = await api.lookup(places, **details)
     else:
         results = napi.SearchResults()
 
index 58edbea4b8776d7b5705ea1e123344a57d8b80bf..c4120599167479a6d9099b480d2ff05e38504b8a 100644 (file)
@@ -163,14 +163,12 @@ class APIReverse:
     def run(self, args: NominatimArgs) -> int:
         api = napi.NominatimAPI(args.project_dir)
 
-        details = napi.LookupDetails(address_details=True, # needed for display name
-                                     geometry_output=args.get_geometry_output(),
-                                     geometry_simplification=args.polygon_threshold or 0.0)
-
         result = api.reverse(napi.Point(args.lon, args.lat),
-                             REVERSE_MAX_RANKS[max(0, min(18, args.zoom or 18))],
-                             args.get_layers(napi.DataLayer.ADDRESS | napi.DataLayer.POI),
-                             details)
+                             max_rank=REVERSE_MAX_RANKS[max(0, min(18, args.zoom or 18))],
+                             layers=args.get_layers(napi.DataLayer.ADDRESS | napi.DataLayer.POI),
+                             address_details=True, # needed for display name
+                             geometry_output=args.get_geometry_output(),
+                             geometry_simplification=args.polygon_threshold)
 
         if result:
             output = api_output.format_result(
@@ -216,13 +214,12 @@ class APILookup:
     def run(self, args: NominatimArgs) -> int:
         api = napi.NominatimAPI(args.project_dir)
 
-        details = napi.LookupDetails(address_details=True, # needed for display name
-                                     geometry_output=args.get_geometry_output(),
-                                     geometry_simplification=args.polygon_threshold or 0.0)
-
         places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids]
 
-        results = api.lookup(places, details)
+        results = api.lookup(places,
+                             address_details=True, # needed for display name
+                             geometry_output=args.get_geometry_output(),
+                             geometry_simplification=args.polygon_threshold or 0.0)
 
         output = api_output.format_result(
                     results,
@@ -297,14 +294,15 @@ class APIDetails:
 
         api = napi.NominatimAPI(args.project_dir)
 
-        details = napi.LookupDetails(address_details=args.addressdetails,
-                                     linked_places=args.linkedplaces,
-                                     parented_places=args.hierarchy,
-                                     keywords=args.keywords)
-        if args.polygon_geojson:
-            details.geometry_output = napi.GeometryFormat.GEOJSON
+        result = api.details(place,
+                             address_details=args.addressdetails,
+                             linked_places=args.linkedplaces,
+                             parented_places=args.hierarchy,
+                             keywords=args.keywords,
+                             geometry_output=napi.GeometryFormat.GEOJSON
+                                             if args.polygon_geojson
+                                             else napi.GeometryFormat.NONE)
 
-        result = api.details(place, details)
 
         if result:
             output = api_output.format_result(
index 625c4e7afdc131b6452551fbf30e05d5db9b3e1e..101dfd13429439e87212013a15e6308a595477c4 100644 (file)
@@ -31,7 +31,7 @@ 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, napi.LookupDetails())
+    result = apiobj.api.details(idobj)
 
     assert result is not None
 
@@ -79,7 +79,7 @@ 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), napi.LookupDetails())
+    result = apiobj.api.details(napi.PlaceID(332))
 
     assert result is not None
 
@@ -121,8 +121,7 @@ def test_lookup_in_placex_with_geometry(apiobj):
     apiobj.add_placex(place_id=332,
                       geometry='LINESTRING(23 34, 23.1 34)')
 
-    result = apiobj.api.details(napi.PlaceID(332),
-                               napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON))
+    result = apiobj.api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
 
     assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
 
@@ -144,8 +143,7 @@ def test_lookup_placex_with_address_details(apiobj):
                               country_code='pl',
                               rank_search=17, rank_address=16)
 
-    result = apiobj.api.details(napi.PlaceID(332),
-                               napi.LookupDetails(address_details=True))
+    result = apiobj.api.details(napi.PlaceID(332), address_details=True)
 
     assert result.address_rows == [
                napi.AddressLine(place_id=332, osm_object=('W', 4),
@@ -177,8 +175,7 @@ def test_lookup_place_with_linked_places_none_existing(apiobj):
                      country_code='pl', linked_place_id=45,
                      rank_search=27, rank_address=26)
 
-    result = apiobj.api.details(napi.PlaceID(332),
-                               napi.LookupDetails(linked_places=True))
+    result = apiobj.api.details(napi.PlaceID(332), linked_places=True)
 
     assert result.linked_rows == []
 
@@ -197,8 +194,7 @@ 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),
-                               napi.LookupDetails(linked_places=True))
+    result = apiobj.api.details(napi.PlaceID(332), linked_places=True)
 
     assert result.linked_rows == [
                napi.AddressLine(place_id=1001, osm_object=('W', 5),
@@ -220,8 +216,7 @@ def test_lookup_place_with_parented_places_not_existing(apiobj):
                      country_code='pl', parent_place_id=45,
                      rank_search=27, rank_address=26)
 
-    result = apiobj.api.details(napi.PlaceID(332),
-                               napi.LookupDetails(parented_places=True))
+    result = apiobj.api.details(napi.PlaceID(332), parented_places=True)
 
     assert result.parented_rows == []
 
@@ -240,8 +235,7 @@ 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),
-                               napi.LookupDetails(parented_places=True))
+    result = apiobj.api.details(napi.PlaceID(332), parented_places=True)
 
     assert result.parented_rows == [
                napi.AddressLine(place_id=1001, osm_object=('N', 5),
@@ -263,7 +257,7 @@ def test_lookup_in_osmline(apiobj, idobj):
                        indexed_date=import_date,
                        geometry='LINESTRING(23 34, 23 35)')
 
-    result = apiobj.api.details(idobj, napi.LookupDetails())
+    result = apiobj.api.details(idobj)
 
     assert result is not None
 
@@ -310,13 +304,13 @@ def test_lookup_in_osmline_split_interpolation(apiobj):
                        startnumber=11, endnumber=20, step=1)
 
     for i in range(1, 6):
-        result = apiobj.api.details(napi.OsmID('W', 9, str(i)), napi.LookupDetails())
+        result = apiobj.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)), napi.LookupDetails())
+        result = apiobj.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)), napi.LookupDetails())
+        result = apiobj.api.details(napi.OsmID('W', 9, str(i)))
         assert result.place_id == 1002
 
 
@@ -340,8 +334,7 @@ def test_lookup_osmline_with_address_details(apiobj):
                               country_code='pl',
                               rank_search=17, rank_address=16)
 
-    result = apiobj.api.details(napi.PlaceID(9000),
-                               napi.LookupDetails(address_details=True))
+    result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
 
     assert result.address_rows == [
                napi.AddressLine(place_id=None, osm_object=None,
@@ -383,7 +376,7 @@ 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), napi.LookupDetails())
+    result = apiobj.api.details(napi.PlaceID(4924))
 
     assert result is not None
 
@@ -441,8 +434,7 @@ def test_lookup_tiger_with_address_details(apiobj):
                               country_code='us',
                               rank_search=17, rank_address=16)
 
-    result = apiobj.api.details(napi.PlaceID(9000),
-                               napi.LookupDetails(address_details=True))
+    result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
 
     assert result.address_rows == [
                napi.AddressLine(place_id=None, osm_object=None,
@@ -483,7 +475,7 @@ def test_lookup_in_postcode(apiobj):
                         indexed_date=import_date,
                         geometry='POINT(-9.45 5.6)')
 
-    result = apiobj.api.details(napi.PlaceID(554), napi.LookupDetails())
+    result = apiobj.api.details(napi.PlaceID(554))
 
     assert result is not None
 
@@ -537,8 +529,7 @@ def test_lookup_postcode_with_address_details(apiobj):
                               country_code='gb',
                               rank_search=17, rank_address=16)
 
-    result = apiobj.api.details(napi.PlaceID(9000),
-                               napi.LookupDetails(address_details=True))
+    result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
 
     assert result.address_rows == [
                napi.AddressLine(place_id=332, osm_object=('N', 3333),
@@ -570,7 +561,7 @@ def test_lookup_missing_object(apiobj, objid):
     apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
                       class_='place', type='suburb')
 
-    assert apiobj.api.details(objid, napi.LookupDetails()) is None
+    assert apiobj.api.details(objid) is None
 
 
 @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
@@ -580,5 +571,4 @@ def test_lookup_unsupported_geometry(apiobj, gtype):
     apiobj.add_placex(place_id=332)
 
     with pytest.raises(ValueError):
-        apiobj.api.details(napi.PlaceID(332),
-                          napi.LookupDetails(geometry_output=gtype))
+        apiobj.api.details(napi.PlaceID(332), geometry_output=gtype)
index 6aafa29eed5a6fd41e00f8d4ca2fbf5541f07e02..619bc74710df52a8b241d50c7f908264f26c79ad 100644 (file)
@@ -95,7 +95,7 @@ def test_lookup_multiple_places(apiobj):
 
     result = apiobj.api.lookup((napi.OsmID('W', 1),
                                 napi.OsmID('W', 4),
-                                napi.OsmID('W', 9928)), napi.LookupDetails())
+                                napi.OsmID('W', 9928)))
 
     assert len(result) == 2
 
index d1d47f8454e10f3fa91c237d23b4e378393a0115..3296e98fdd6055ad0beb2571e4da2877b6d37f7d 100644 (file)
@@ -84,7 +84,7 @@ def test_reverse_rank_30_layers(apiobj, y, layer, place_id):
                       rank_search=30,
                       centroid=(1.3, 0.70005))
 
-    assert apiobj.api.reverse((1.3, y), layer=layer).place_id == place_id
+    assert apiobj.api.reverse((1.3, y), layers=layer).place_id == place_id
 
 
 def test_reverse_poi_layer_with_no_pois(apiobj):
@@ -95,7 +95,7 @@ def test_reverse_poi_layer_with_no_pois(apiobj):
                       centroid=(1.3, 0.70001))
 
     assert apiobj.api.reverse((1.3, 0.70001), max_rank=29,
-                              layer=napi.DataLayer.POI) is None
+                              layers=napi.DataLayer.POI) is None
 
 
 def test_reverse_housenumber_on_street(apiobj):
@@ -245,7 +245,7 @@ def test_reverse_larger_area_layers(apiobj, layer, place_id):
                       rank_search=16,
                       centroid=(1.3, 0.70005))
 
-    assert apiobj.api.reverse((1.3, 0.7), layer=layer).place_id == place_id
+    assert apiobj.api.reverse((1.3, 0.7), layers=layer).place_id == place_id
 
 
 def test_reverse_country_lookup_no_objects(apiobj):
@@ -296,10 +296,8 @@ def test_reverse_geometry_output_placex(apiobj, gtype):
                       country_code='xx',
                       centroid=(0.5, 0.5))
 
-    details = napi.LookupDetails(geometry_output=gtype)
-
-    assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
-    assert apiobj.api.reverse((0.5, 0.5), details=details).place_id == 1003
+    assert apiobj.api.reverse((59.3, 80.70001), geometry_output=gtype).place_id == 1001
+    assert apiobj.api.reverse((0.5, 0.5), geometry_output=gtype).place_id == 1003
 
 
 def test_reverse_simplified_geometry(apiobj):
@@ -309,9 +307,9 @@ def test_reverse_simplified_geometry(apiobj):
                       rank_search=30,
                       centroid=(59.3, 80.70001))
 
-    details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON,
-                                 geometry_simplification=0.1)
-    assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
+    details = dict(geometry_output=napi.GeometryFormat.GEOJSON,
+                   geometry_simplification=0.1)
+    assert apiobj.api.reverse((59.3, 80.70001), **details).place_id == 1001
 
 
 def test_reverse_interpolation_geometry(apiobj):
@@ -321,8 +319,7 @@ def test_reverse_interpolation_geometry(apiobj):
                        centroid=(10.0, 10.00001),
                        geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
-    details = napi.LookupDetails(geometry_output=napi.GeometryFormat.TEXT)
-    assert apiobj.api.reverse((10.0, 10.0), details=details)\
+    assert apiobj.api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)\
                      .geometry['text'] == 'POINT(10 10.00001)'
 
 
@@ -339,8 +336,8 @@ def test_reverse_tiger_geometry(apiobj):
                      centroid=(10.0, 10.00001),
                      geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
 
-    details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON)
-    output = apiobj.api.reverse((10.0, 10.0), details=details).geometry['geojson']
+    output = apiobj.api.reverse((10.0, 10.0),
+                                geometry_output=napi.GeometryFormat.GEOJSON).geometry['geojson']
 
     assert json.loads(output) == {'coordinates': [10, 10.00001], 'type': 'Point'}
 
index e8c447aa5f722732770a09fab26283d0838e13fe..2d7897a3221bbc36d348ab64d5ac0c64fc8055a2 100644 (file)
@@ -81,7 +81,7 @@ class TestCliDetailsCall:
                                      napi.Point(1.0, -3.0))
 
         monkeypatch.setattr(napi.NominatimAPI, 'details',
-                            lambda *args: result)
+                            lambda *args, **kwargs: result)
 
     @pytest.mark.parametrize("params", [('--node', '1'),
                                         ('--way', '1'),
@@ -106,7 +106,7 @@ class TestCliReverseCall:
                                     extratags={'extra':'Extra'})
 
         monkeypatch.setattr(napi.NominatimAPI, 'reverse',
-                            lambda *args: result)
+                            lambda *args, **kwargs: result)
 
 
     def test_reverse_simple(self, cli_call, tmp_path, capsys):
@@ -165,7 +165,7 @@ class TestCliLookupCall:
                                     extratags={'extra':'Extra'})
 
         monkeypatch.setattr(napi.NominatimAPI, 'lookup',
-                            lambda *args: napi.SearchResults([result]))
+                            lambda *args, **kwargs: napi.SearchResults([result]))
 
     def test_lookup_simple(self, cli_call, tmp_path, capsys):
         result = cli_call('lookup', '--project-dir', str(tmp_path),