]> git.openstreetmap.org Git - nominatim.git/commitdiff
switch CLI lookup command to Python implementation
authorSarah Hoffmann <lonvia@denofr.de>
Mon, 3 Apr 2023 12:38:40 +0000 (14:38 +0200)
committerSarah Hoffmann <lonvia@denofr.de>
Mon, 3 Apr 2023 12:40:41 +0000 (14:40 +0200)
nominatim/api/v1/classtypes.py
nominatim/api/v1/format.py
nominatim/api/v1/format_json.py
nominatim/api/v1/format_xml.py
nominatim/api/v1/server_glue.py
nominatim/clicmd/api.py
test/python/cli/test_cmd_api.py

index 27faa1746c3a105a946b7eae4194f23f6660cb41..273fe2f5bf97ec39fb8bfa8dd5861779a8e429cd 100644 (file)
@@ -10,7 +10,7 @@ Hard-coded information about tag catagories.
 These tables have been copied verbatim from the old PHP code. For future
 version a more flexible formatting is required.
 """
-from typing import Tuple, Optional, Mapping
+from typing import Tuple, Optional, Mapping, Union
 
 import nominatim.api as napi
 
@@ -41,7 +41,7 @@ def get_label_tag(category: Tuple[str, str], extratags: Optional[Mapping[str, st
     return label.lower().replace(' ', '_')
 
 
-def bbox_from_result(result: napi.ReverseResult) -> napi.Bbox:
+def bbox_from_result(result: Union[napi.ReverseResult, napi.SearchResult]) -> napi.Bbox:
     """ Compute a bounding box for the result. For ways and relations
         a given boundingbox is used. For all other object, a box is computed
         around the centroid according to dimensions dereived from the
index b50a2346f58d396d462a52853b81edc7678ea278..2e1caa991a91e0ca09eff6c458ed42b5ae8ff99b 100644 (file)
@@ -198,33 +198,33 @@ def _format_reverse_jsonv2(results: napi.ReverseResults,
 
 
 @dispatch.format_func(napi.SearchResults, 'xml')
-def _format_reverse_xml(results: napi.SearchResults, options: Mapping[str, Any]) -> str:
+def _format_search_xml(results: napi.SearchResults, options: Mapping[str, Any]) -> str:
     return format_xml.format_base_xml(results,
                                       options, False, 'searchresults',
                                       {'querystring': 'TODO'})
 
 
 @dispatch.format_func(napi.SearchResults, 'geojson')
-def _format_reverse_geojson(results: napi.SearchResults,
+def _format_search_geojson(results: napi.SearchResults,
                             options: Mapping[str, Any]) -> str:
     return format_json.format_base_geojson(results, options, False)
 
 
 @dispatch.format_func(napi.SearchResults, 'geocodejson')
-def _format_reverse_geocodejson(results: napi.SearchResults,
+def _format_search_geocodejson(results: napi.SearchResults,
                                 options: Mapping[str, Any]) -> str:
     return format_json.format_base_geocodejson(results, options, False)
 
 
 @dispatch.format_func(napi.SearchResults, 'json')
-def _format_reverse_json(results: napi.SearchResults,
+def _format_search_json(results: napi.SearchResults,
                          options: Mapping[str, Any]) -> str:
     return format_json.format_base_json(results, options, False,
                                         class_label='class')
 
 
 @dispatch.format_func(napi.SearchResults, 'jsonv2')
-def _format_reverse_jsonv2(results: napi.SearchResults,
+def _format_search_jsonv2(results: napi.SearchResults,
                            options: Mapping[str, Any]) -> str:
     return format_json.format_base_json(results, options, False,
                                         class_label='category')
index a4fa7655353749e66a5e0668ad22e30dbcfeee7a..c82681e91f7078fc7446644fa800a08a97c28637 100644 (file)
@@ -7,12 +7,14 @@
 """
 Helper functions for output of results in json formats.
 """
-from typing import Mapping, Any, Optional, Tuple
+from typing import Mapping, Any, Optional, Tuple, Union
 
 import nominatim.api as napi
 import nominatim.api.v1.classtypes as cl
 from nominatim.utils.json_writer import JsonWriter
 
+#pylint: disable=too-many-branches
+
 def _write_osm_id(out: JsonWriter, osm_object: Optional[Tuple[str, int]]) -> None:
     if osm_object is not None:
         out.keyval_not_none('osm_type', cl.OSM_TYPE_NAME.get(osm_object[0], None))\
@@ -61,7 +63,7 @@ def _write_geocodejson_address(out: JsonWriter,
         out.keyval('country_code', country_code)
 
 
-def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-branches
+def format_base_json(results: Union[napi.ReverseResults, napi.SearchResults],
                      options: Mapping[str, Any], simple: bool,
                      class_label: str) -> str:
     """ Return the result list as a simple json string in custom Nominatim format.
@@ -141,7 +143,7 @@ def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-bra
     return out()
 
 
-def format_base_geojson(results: napi.ReverseResults,
+def format_base_geojson(results: Union[napi.ReverseResults, napi.SearchResults],
                         options: Mapping[str, Any],
                         simple: bool) -> str:
     """ Return the result list as a geojson string.
@@ -210,7 +212,7 @@ def format_base_geojson(results: napi.ReverseResults,
     return out()
 
 
-def format_base_geocodejson(results: napi.ReverseResults,
+def format_base_geocodejson(results: Union[napi.ReverseResults, napi.SearchResults],
                             options: Mapping[str, Any], simple: bool) -> str:
     """ Return the result list as a geocodejson string.
     """
index 3fe3b7fe7771a428b57062c97a59e9c8f797c79f..1fd0675a36d04f88026ebebdc6730a720605a37d 100644 (file)
@@ -7,13 +7,15 @@
 """
 Helper functions for output of results in XML format.
 """
-from typing import Mapping, Any, Optional
+from typing import Mapping, Any, Optional, Union
 import datetime as dt
 import xml.etree.ElementTree as ET
 
 import nominatim.api as napi
 import nominatim.api.v1.classtypes as cl
 
+#pylint: disable=too-many-branches
+
 def _write_xml_address(root: ET.Element, address: napi.AddressLines,
                        country_code: Optional[str]) -> None:
     parts = {}
@@ -34,7 +36,7 @@ def _write_xml_address(root: ET.Element, address: napi.AddressLines,
         ET.SubElement(root, 'country_code').text = country_code
 
 
-def _create_base_entry(result: napi.ReverseResult, #pylint: disable=too-many-branches
+def _create_base_entry(result: Union[napi.ReverseResult, napi.SearchResult],
                        root: ET.Element, simple: bool,
                        locales: napi.Locales) -> ET.Element:
     if result.address_rows:
@@ -86,7 +88,7 @@ def _create_base_entry(result: napi.ReverseResult, #pylint: disable=too-many-bra
     return place
 
 
-def format_base_xml(results: napi.ReverseResults,
+def format_base_xml(results: Union[napi.ReverseResults, napi.SearchResults],
                     options: Mapping[str, Any],
                     simple: bool, xml_root_tag: str,
                     xml_extra_info: Mapping[str, str]) -> str:
index 40081c039cfdc9478205c4639327631521d6de76..68cf58c285b37858dc90828cde711ac79824743a 100644 (file)
@@ -227,8 +227,11 @@ class ASGIAdaptor(abc.ABC):
 
 
     def parse_geometry_details(self, fmt: str) -> napi.LookupDetails:
+        """ Create details strucutre from the supplied geometry parameters.
+        """
         details = napi.LookupDetails(address_details=True,
-                                     geometry_simplification=self.get_float('polygon_threshold', 0.0))
+                                     geometry_simplification=
+                                       self.get_float('polygon_threshold', 0.0))
         numgeoms = 0
         if self.get_bool('polygon_geojson', False):
             details.geometry_output |= napi.GeometryFormat.GEOJSON
@@ -348,7 +351,7 @@ async def lookup_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> A
     details = params.parse_geometry_details(fmt)
 
     places = []
-    for oid in params.get('osm_ids', '').split(','):
+    for oid in (params.get('osm_ids') or '').split(','):
         oid = oid.strip()
         if len(oid) > 1 and oid[0] in 'RNWrnw' and oid[1:].isdigit():
             places.append(napi.OsmID(oid[0], int(oid[1:])))
index e198e541462335b1e719be37a566a4f7bd4232a7..58edbea4b8776d7b5705ea1e123344a57d8b80bf 100644 (file)
@@ -214,19 +214,31 @@ class APILookup:
 
 
     def run(self, args: NominatimArgs) -> int:
-        params: Dict[str, object] = dict(osm_ids=','.join(args.ids), format=args.format)
+        api = napi.NominatimAPI(args.project_dir)
 
-        for param, _ in EXTRADATA_PARAMS:
-            if getattr(args, param):
-                params[param] = '1'
-        if args.lang:
-            params['accept-language'] = args.lang
-        if args.polygon_output:
-            params['polygon_' + args.polygon_output] = '1'
-        if args.polygon_threshold:
-            params['polygon_threshold'] = args.polygon_threshold
+        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)
 
-        return _run_api('lookup', args, params)
+        output = api_output.format_result(
+                    results,
+                    args.format,
+                    {'locales': args.get_locales(api.config.DEFAULT_LANGUAGE),
+                     'extratags': args.extratags,
+                     'namedetails': args.namedetails,
+                     'addressdetails': args.addressdetails})
+        if args.format != 'xml':
+            # reformat the result, so it is pretty-printed
+            json.dump(json.loads(output), sys.stdout, indent=4, ensure_ascii=False)
+        else:
+            sys.stdout.write(output)
+        sys.stdout.write('\n')
+
+        return 0
 
 
 class APIDetails:
index b794bccdb91a6dc76a2699927b7e60e8268a3e15..e8c447aa5f722732770a09fab26283d0838e13fe 100644 (file)
@@ -23,8 +23,7 @@ def test_no_api_without_phpcgi(endpoint):
 
 
 @pytest.mark.parametrize("params", [('search', '--query', 'new'),
-                                    ('search', '--city', 'Berlin'),
-                                    ('lookup', '--id', 'N1')])
+                                    ('search', '--city', 'Berlin')])
 class TestCliApiCallPhp:
 
     @pytest.fixture(autouse=True)
@@ -156,32 +155,49 @@ class TestCliReverseCall:
         assert out['name'] == 'Nom'
 
 
-QUERY_PARAMS = {
- 'search': ('--query', 'somewhere'),
- 'reverse': ('--lat', '20', '--lon', '30'),
- 'lookup': ('--id', 'R345345'),
- 'details': ('--node', '324')
-}
+class TestCliLookupCall:
+
+    @pytest.fixture(autouse=True)
+    def setup_lookup_mock(self, monkeypatch):
+        result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
+                                    napi.Point(1.0, -3.0),
+                                    names={'name':'Name', 'name:fr': 'Nom'},
+                                    extratags={'extra':'Extra'})
+
+        monkeypatch.setattr(napi.NominatimAPI, 'lookup',
+                            lambda *args: napi.SearchResults([result]))
+
+    def test_lookup_simple(self, cli_call, tmp_path, capsys):
+        result = cli_call('lookup', '--project-dir', str(tmp_path),
+                          '--id', 'N34')
+
+        assert result == 0
+
+        out = json.loads(capsys.readouterr().out)
+        assert len(out) == 1
+        assert out[0]['name'] == 'Name'
+        assert 'address' not in out[0]
+        assert 'extratags' not in out[0]
+        assert 'namedetails' not in out[0]
+
 
-@pytest.mark.parametrize("endpoint", (('search', 'lookup')))
 class TestCliApiCommonParameters:
 
     @pytest.fixture(autouse=True)
-    def setup_website_dir(self, cli_call, project_env, endpoint):
-        self.endpoint = endpoint
+    def setup_website_dir(self, cli_call, project_env):
         self.cli_call = cli_call
         self.project_dir = project_env.project_dir
         (self.project_dir / 'website').mkdir()
 
 
     def expect_param(self, param, expected):
-        (self.project_dir / 'website' / (self.endpoint + '.php')).write_text(f"""<?php
+        (self.project_dir / 'website' / ('search.php')).write_text(f"""<?php
         exit($_GET['{param}']  == '{expected}' ? 0 : 10);
         """)
 
 
     def call_nominatim(self, *params):
-        return self.cli_call(self.endpoint, *QUERY_PARAMS[self.endpoint],
+        return self.cli_call('search', '--query', 'somewhere',
                              '--project-dir', str(self.project_dir), *params)
 
 
@@ -221,7 +237,7 @@ def test_cli_search_param_bounded(cli_call, project_env):
         exit($_GET['bounded']  == '1' ? 0 : 10);
         """)
 
-    assert cli_call('search', *QUERY_PARAMS['search'], '--project-dir', str(project_env.project_dir),
+    assert cli_call('search', '--query', 'somewhere', '--project-dir', str(project_env.project_dir),
                     '--bounded') == 0
 
 
@@ -232,5 +248,5 @@ def test_cli_search_param_dedupe(cli_call, project_env):
         exit($_GET['dedupe']  == '0' ? 0 : 10);
         """)
 
-    assert cli_call('search', *QUERY_PARAMS['search'], '--project-dir', str(project_env.project_dir),
+    assert cli_call('search', '--query', 'somewhere', '--project-dir', str(project_env.project_dir),
                     '--no-dedupe') == 0