switch CLI lookup command to Python implementation

This commit is contained in:
Sarah Hoffmann
2023-04-03 14:38:40 +02:00
parent 86c4897c9b
commit 1dce2b98b4
7 changed files with 77 additions and 42 deletions

View 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

View 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')

View 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.
"""

View 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:

View 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:])))

View 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)
return _run_api('lookup', args, params)
places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids]
results = api.lookup(places, details)
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:

View 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