From 8e8f7a641b8b2eadd99e8e3198f762a3c3ebfd67 Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Fri, 16 Aug 2024 19:30:57 +0200 Subject: [PATCH] use custom result formatters in CLI commands --- src/nominatim_api/__init__.py | 3 +- src/nominatim_api/v1/__init__.py | 6 - src/nominatim_db/clicmd/api.py | 214 +++++++++++------- src/nominatim_db/clicmd/args.py | 1 + test/python/api/test_result_formatting_v1.py | 38 ++-- .../api/test_result_formatting_v1_reverse.py | 26 +-- test/python/cli/test_cmd_api.py | 9 + 7 files changed, 177 insertions(+), 120 deletions(-) diff --git a/src/nominatim_api/__init__.py b/src/nominatim_api/__init__.py index 50f99701..242ff892 100644 --- a/src/nominatim_api/__init__.py +++ b/src/nominatim_api/__init__.py @@ -39,6 +39,7 @@ from .results import (SourceTable as SourceTable, SearchResult as SearchResult, SearchResults as SearchResults) from .localization import (Locales as Locales) -from .result_formatting import (FormatDispatcher as FormatDispatcher) +from .result_formatting import (FormatDispatcher as FormatDispatcher, + load_format_dispatcher as load_format_dispatcher) from .version import NOMINATIM_API_VERSION as __version__ diff --git a/src/nominatim_api/v1/__init__.py b/src/nominatim_api/v1/__init__.py index c7f150f0..4f684a91 100644 --- a/src/nominatim_api/v1/__init__.py +++ b/src/nominatim_api/v1/__init__.py @@ -11,9 +11,3 @@ Implementation of API version v1 (aka the legacy version). #pylint: disable=useless-import-alias from .server_glue import ROUTES as ROUTES - -from . import format as _format - -list_formats = _format.dispatch.list_formats -supports_format = _format.dispatch.supports_format -format_result = _format.dispatch.format_result diff --git a/src/nominatim_db/clicmd/api.py b/src/nominatim_db/clicmd/api.py index fac88bdd..3deb7e45 100644 --- a/src/nominatim_db/clicmd/api.py +++ b/src/nominatim_db/clicmd/api.py @@ -7,7 +7,7 @@ """ Subcommand definitions for API calls from the command line. """ -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional, Type, Mapping import argparse import logging import json @@ -15,9 +15,8 @@ import sys from functools import reduce import nominatim_api as napi -import nominatim_api.v1 as api_output from nominatim_api.v1.helpers import zoom_to_rank, deduplicate_results -from nominatim_api.v1.format import dispatch as formatting +from nominatim_api.server.content_types import CONTENT_JSON import nominatim_api.logging as loglib from ..errors import UsageError from .args import NominatimArgs @@ -44,11 +43,16 @@ EXTRADATA_PARAMS = ( ('namedetails', 'Include a list of alternative names') ) +def _add_list_format(parser: argparse.ArgumentParser) -> None: + group = parser.add_argument_group('Other options') + group.add_argument('--list-formats', action='store_true', + help='List supported output formats and exit.') + + def _add_api_output_arguments(parser: argparse.ArgumentParser) -> None: - group = parser.add_argument_group('Output arguments') - group.add_argument('--format', default='jsonv2', - choices=formatting.list_formats(napi.SearchResults) + ['debug'], - help='Format of result') + group = parser.add_argument_group('Output formatting') + group.add_argument('--format', type=str, default='jsonv2', + help='Format of result (use --list-format to see supported formats)') for name, desc in EXTRADATA_PARAMS: group.add_argument('--' + name, action='store_true', help=desc) @@ -105,6 +109,24 @@ def _get_layers(args: NominatimArgs, default: napi.DataLayer) -> Optional[napi.D (napi.DataLayer[s.upper()] for s in args.layers)) +def _list_formats(formatter: napi.FormatDispatcher, rtype: Type[Any]) -> int: + for fmt in formatter.list_formats(rtype): + print(fmt) + print('debug') + + return 0 + + +def _print_output(formatter: napi.FormatDispatcher, result: Any, + fmt: str, options: Mapping[str, Any]) -> None: + output = formatter.format_result(result, fmt, options) + if formatter.get_content_type(fmt) == CONTENT_JSON: + # 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') + class APISearch: """\ Execute a search query. @@ -135,18 +157,24 @@ class APISearch: help='Preferred area to find search results') group.add_argument('--bounded', action='store_true', help='Strictly restrict results to viewbox area') - - group = parser.add_argument_group('Other arguments') group.add_argument('--no-dedupe', action='store_false', dest='dedupe', help='Do not remove duplicates from the result list') + _add_list_format(parser) def run(self, args: NominatimArgs) -> int: + formatter = napi.load_format_dispatcher('v1', args.project_dir) + + if args.list_formats: + return _list_formats(formatter, napi.SearchResults) + if args.format == 'debug': loglib.set_log_output('text') + elif not formatter.supports_format(napi.SearchResults, args.format): + raise UsageError(f"Unsupported format '{args.format}'. " + 'Use --list-formats to see supported formats.') api = napi.NominatimAPI(args.project_dir) - params: Dict[str, Any] = {'max_results': args.limit + min(args.limit, 10), 'address_details': True, # needed for display name 'geometry_output': _get_geometry_output(args), @@ -177,19 +205,10 @@ class APISearch: print(loglib.get_and_disable()) return 0 - output = api_output.format_result( - results, - args.format, - {'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') - + _print_output(formatter, results, args.format, + {'extratags': args.extratags, + 'namedetails': args.namedetails, + 'addressdetails': args.addressdetails}) return 0 @@ -205,9 +224,9 @@ class APIReverse: def add_args(self, parser: argparse.ArgumentParser) -> None: group = parser.add_argument_group('Query arguments') - group.add_argument('--lat', type=float, required=True, + group.add_argument('--lat', type=float, help='Latitude of coordinate to look up (in WGS84)') - group.add_argument('--lon', type=float, required=True, + group.add_argument('--lon', type=float, help='Longitude of coordinate to look up (in WGS84)') group.add_argument('--zoom', type=int, help='Level of detail required for the address') @@ -217,14 +236,25 @@ class APIReverse: help='OSM id to lookup in format (may be repeated)') _add_api_output_arguments(parser) + _add_list_format(parser) def run(self, args: NominatimArgs) -> int: + formatter = napi.load_format_dispatcher('v1', args.project_dir) + + if args.list_formats: + return _list_formats(formatter, napi.ReverseResults) + if args.format == 'debug': loglib.set_log_output('text') + elif not formatter.supports_format(napi.ReverseResults, args.format): + raise UsageError(f"Unsupported format '{args.format}'. " + 'Use --list-formats to see supported formats.') - api = napi.NominatimAPI(args.project_dir) + if args.lat is None or args.lon is None: + raise UsageError("lat' and 'lon' parameters are required.") + api = napi.NominatimAPI(args.project_dir) result = api.reverse(napi.Point(args.lon, args.lat), max_rank=zoom_to_rank(args.zoom or 18), layers=_get_layers(args, napi.DataLayer.ADDRESS | napi.DataLayer.POI), @@ -238,18 +268,10 @@ class APIReverse: return 0 if result: - output = api_output.format_result( - napi.ReverseResults([result]), - args.format, - {'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') + _print_output(formatter, napi.ReverseResults([result]), args.format, + {'extratags': args.extratags, + 'namedetails': args.namedetails, + 'addressdetails': args.addressdetails}) return 0 @@ -271,43 +293,45 @@ class APILookup: def add_args(self, parser: argparse.ArgumentParser) -> None: group = parser.add_argument_group('Query arguments') group.add_argument('--id', metavar='OSMID', - action='append', required=True, dest='ids', + action='append', dest='ids', help='OSM id to lookup in format (may be repeated)') _add_api_output_arguments(parser) + _add_list_format(parser) def run(self, args: NominatimArgs) -> int: - if args.format == 'debug': - loglib.set_log_output('text') + formatter = napi.load_format_dispatcher('v1', args.project_dir) - api = napi.NominatimAPI(args.project_dir) + if args.list_formats: + return _list_formats(formatter, napi.ReverseResults) if args.format == 'debug': - print(loglib.get_and_disable()) - return 0 + loglib.set_log_output('text') + elif not formatter.supports_format(napi.ReverseResults, args.format): + raise UsageError(f"Unsupported format '{args.format}'. " + 'Use --list-formats to see supported formats.') + + if args.ids is None: + raise UsageError("'id' parameter required.") places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids] + api = napi.NominatimAPI(args.project_dir) results = api.lookup(places, address_details=True, # needed for display name geometry_output=_get_geometry_output(args), geometry_simplification=args.polygon_threshold or 0.0, locales=_get_locales(args, api.config.DEFAULT_LANGUAGE)) - output = api_output.format_result( - results, - args.format, - {'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') + if args.format == 'debug': + print(loglib.get_and_disable()) + return 0 + _print_output(formatter, results, args.format, + {'extratags': args.extratags, + 'namedetails': args.namedetails, + 'addressdetails': args.addressdetails}) return 0 @@ -323,20 +347,21 @@ class APIDetails: def add_args(self, parser: argparse.ArgumentParser) -> None: group = parser.add_argument_group('Query arguments') - objs = group.add_mutually_exclusive_group(required=True) - objs.add_argument('--node', '-n', type=int, - help="Look up the OSM node with the given ID.") - objs.add_argument('--way', '-w', type=int, - help="Look up the OSM way with the given ID.") - objs.add_argument('--relation', '-r', type=int, - help="Look up the OSM relation with the given ID.") - objs.add_argument('--place_id', '-p', type=int, - help='Database internal identifier of the OSM object to look up') + group.add_argument('--node', '-n', type=int, + help="Look up the OSM node with the given ID.") + group.add_argument('--way', '-w', type=int, + help="Look up the OSM way with the given ID.") + group.add_argument('--relation', '-r', type=int, + help="Look up the OSM relation with the given ID.") + group.add_argument('--place_id', '-p', type=int, + help='Database internal identifier of the OSM object to look up') group.add_argument('--class', dest='object_class', help=("Class type to disambiguated multiple entries " "of the same object.")) group = parser.add_argument_group('Output arguments') + group.add_argument('--format', type=str, default='json', + help='Format of result (use --list-formats to see supported formats)') group.add_argument('--addressdetails', action='store_true', help='Include a breakdown of the address into elements') group.add_argument('--keywords', action='store_true', @@ -351,9 +376,21 @@ class APIDetails: help='Include geometry of result') group.add_argument('--lang', '--accept-language', metavar='LANGS', help='Preferred language order for presenting search results') + _add_list_format(parser) def run(self, args: NominatimArgs) -> int: + formatter = napi.load_format_dispatcher('v1', args.project_dir) + + if args.list_formats: + return _list_formats(formatter, napi.DetailedResult) + + if args.format == 'debug': + loglib.set_log_output('text') + elif not formatter.supports_format(napi.DetailedResult, args.format): + raise UsageError(f"Unsupported format '{args.format}'. " + 'Use --list-formats to see supported formats.') + place: napi.PlaceRef if args.node: place = napi.OsmID('N', args.node, args.object_class) @@ -361,12 +398,13 @@ class APIDetails: place = napi.OsmID('W', args.way, args.object_class) elif args.relation: place = napi.OsmID('R', args.relation, args.object_class) - else: - assert args.place_id is not None + elif args.place_id is not None: place = napi.PlaceID(args.place_id) + else: + raise UsageError('One of the arguments --node/-n --way/-w ' + '--relation/-r --place_id/-p is required/') api = napi.NominatimAPI(args.project_dir) - locales = _get_locales(args, api.config.DEFAULT_LANGUAGE) result = api.details(place, address_details=args.addressdetails, @@ -378,17 +416,14 @@ class APIDetails: else napi.GeometryFormat.NONE, locales=locales) + if args.format == 'debug': + print(loglib.get_and_disable()) + return 0 if result: - output = api_output.format_result( - result, - 'json', - {'locales': locales, - 'group_hierarchy': args.group_hierarchy}) - # reformat the result, so it is pretty-printed - json.dump(json.loads(output), sys.stdout, indent=4, ensure_ascii=False) - sys.stdout.write('\n') - + _print_output(formatter, result, args.format or 'json', + {'locales': locales, + 'group_hierarchy': args.group_hierarchy}) return 0 LOG.error("Object not found in database.") @@ -406,13 +441,30 @@ class APIStatus: """ def add_args(self, parser: argparse.ArgumentParser) -> None: - formats = api_output.list_formats(napi.StatusResult) group = parser.add_argument_group('API parameters') - group.add_argument('--format', default=formats[0], choices=formats, - help='Format of result') + group.add_argument('--format', type=str, default='text', + help='Format of result (use --list-formats to see supported formats)') + _add_list_format(parser) def run(self, args: NominatimArgs) -> int: + formatter = napi.load_format_dispatcher('v1', args.project_dir) + + if args.list_formats: + return _list_formats(formatter, napi.StatusResult) + + if args.format == 'debug': + loglib.set_log_output('text') + elif not formatter.supports_format(napi.StatusResult, args.format): + raise UsageError(f"Unsupported format '{args.format}'. " + 'Use --list-formats to see supported formats.') + status = napi.NominatimAPI(args.project_dir).status() - print(api_output.format_result(status, args.format, {})) + + if args.format == 'debug': + print(loglib.get_and_disable()) + return 0 + + _print_output(formatter, status, args.format, {}) + return 0 diff --git a/src/nominatim_db/clicmd/args.py b/src/nominatim_db/clicmd/args.py index 6a11b089..c74bca62 100644 --- a/src/nominatim_db/clicmd/args.py +++ b/src/nominatim_db/clicmd/args.py @@ -137,6 +137,7 @@ class NominatimArgs: # Arguments to all query functions format: str + list_formats: bool addressdetails: bool extratags: bool namedetails: bool diff --git a/test/python/api/test_result_formatting_v1.py b/test/python/api/test_result_formatting_v1.py index 6ac1bb6d..aaecab45 100644 --- a/test/python/api/test_result_formatting_v1.py +++ b/test/python/api/test_result_formatting_v1.py @@ -15,7 +15,7 @@ import json import pytest -import nominatim_api.v1 as api_impl +from nominatim_api.v1.format import dispatch as v1_format import nominatim_api as napi STATUS_FORMATS = {'text', 'json'} @@ -23,30 +23,30 @@ STATUS_FORMATS = {'text', 'json'} # StatusResult def test_status_format_list(): - assert set(api_impl.list_formats(napi.StatusResult)) == STATUS_FORMATS + assert set(v1_format.list_formats(napi.StatusResult)) == STATUS_FORMATS @pytest.mark.parametrize('fmt', list(STATUS_FORMATS)) def test_status_supported(fmt): - assert api_impl.supports_format(napi.StatusResult, fmt) + assert v1_format.supports_format(napi.StatusResult, fmt) def test_status_unsupported(): - assert not api_impl.supports_format(napi.StatusResult, 'gagaga') + assert not v1_format.supports_format(napi.StatusResult, 'gagaga') def test_status_format_text(): - assert api_impl.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK' + assert v1_format.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK' def test_status_format_text(): - assert api_impl.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here' + assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here' def test_status_format_json_minimal(): status = napi.StatusResult(700, 'Bad format.') - result = api_impl.format_result(status, 'json', {}) + result = v1_format.format_result(status, 'json', {}) assert result == \ f'{{"status":700,"message":"Bad format.","software_version":"{napi.__version__}"}}' @@ -57,7 +57,7 @@ def test_status_format_json_full(): status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc) status.database_version = '5.6' - result = api_impl.format_result(status, 'json', {}) + result = v1_format.format_result(status, 'json', {}) assert result == \ f'{{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"{napi.__version__}","database_version":"5.6"}}' @@ -70,7 +70,7 @@ def test_search_details_minimal(): ('place', 'thing'), napi.Point(1.0, 2.0)) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) assert json.loads(result) == \ {'category': 'place', @@ -114,7 +114,7 @@ def test_search_details_full(): ) search.localize(napi.Locales()) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) assert json.loads(result) == \ {'place_id': 37563, @@ -153,7 +153,7 @@ def test_search_details_no_geometry(gtype, isarea): napi.Point(1.0, 2.0), geometry={'type': gtype}) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) js = json.loads(result) assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]} @@ -166,7 +166,7 @@ def test_search_details_with_geometry(): napi.Point(1.0, 2.0), geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'}) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) js = json.loads(result) assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]} @@ -178,7 +178,7 @@ def test_search_details_with_icon_available(): ('amenity', 'restaurant'), napi.Point(1.0, 2.0)) - result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'}) + result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'}) js = json.loads(result) assert js['icon'] == 'foo/food_restaurant.p.20.png' @@ -189,7 +189,7 @@ def test_search_details_with_icon_not_available(): ('amenity', 'tree'), napi.Point(1.0, 2.0)) - result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'}) + result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'}) js = json.loads(result) assert 'icon' not in js @@ -212,7 +212,7 @@ def test_search_details_with_address_minimal(): distance=0.0) ]) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) js = json.loads(result) assert js['address'] == [{'localname': '', @@ -245,7 +245,7 @@ def test_search_details_with_further_infos(field, outfield): distance=0.034) ]) - result = api_impl.format_result(search, 'json', {}) + result = v1_format.format_result(search, 'json', {}) js = json.loads(result) assert js[outfield] == [{'localname': 'Trespass', @@ -279,7 +279,7 @@ def test_search_details_grouped_hierarchy(): distance=0.034) ]) - result = api_impl.format_result(search, 'json', {'group_hierarchy': True}) + result = v1_format.format_result(search, 'json', {'group_hierarchy': True}) js = json.loads(result) assert js['hierarchy'] == {'note': [{'localname': 'Trespass', @@ -303,7 +303,7 @@ def test_search_details_keywords_name(): napi.WordInfo(23, 'foo', 'mefoo'), napi.WordInfo(24, 'foo', 'bafoo')]) - result = api_impl.format_result(search, 'json', {'keywords': True}) + result = v1_format.format_result(search, 'json', {'keywords': True}) js = json.loads(result) assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'}, @@ -319,7 +319,7 @@ def test_search_details_keywords_address(): napi.WordInfo(23, 'foo', 'mefoo'), napi.WordInfo(24, 'foo', 'bafoo')]) - result = api_impl.format_result(search, 'json', {'keywords': True}) + result = v1_format.format_result(search, 'json', {'keywords': True}) js = json.loads(result) assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'}, diff --git a/test/python/api/test_result_formatting_v1_reverse.py b/test/python/api/test_result_formatting_v1_reverse.py index 1248fa9e..2c036a65 100644 --- a/test/python/api/test_result_formatting_v1_reverse.py +++ b/test/python/api/test_result_formatting_v1_reverse.py @@ -15,7 +15,7 @@ import xml.etree.ElementTree as ET import pytest -import nominatim_api.v1 as api_impl +from nominatim_api.v1.format import dispatch as v1_format import nominatim_api as napi FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml'] @@ -26,7 +26,7 @@ def test_format_reverse_minimal(fmt): ('amenity', 'post_box'), napi.Point(0.3, -8.9)) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, {}) + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {}) if fmt == 'xml': root = ET.fromstring(raw) @@ -38,7 +38,7 @@ def test_format_reverse_minimal(fmt): @pytest.mark.parametrize('fmt', FORMATS) def test_format_reverse_no_result(fmt): - raw = api_impl.format_result(napi.ReverseResults(), fmt, {}) + raw = v1_format.format_result(napi.ReverseResults(), fmt, {}) if fmt == 'xml': root = ET.fromstring(raw) @@ -55,7 +55,7 @@ def test_format_reverse_with_osm_id(fmt): place_id=5564, osm_object=('N', 23)) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, {}) + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {}) if fmt == 'xml': root = ET.fromstring(raw).find('result') @@ -103,7 +103,7 @@ def test_format_reverse_with_address(fmt): ])) reverse.localize(napi.Locales()) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'addressdetails': True}) @@ -167,7 +167,7 @@ def test_format_reverse_geocodejson_special_parts(): reverse.localize(napi.Locales()) - raw = api_impl.format_result(napi.ReverseResults([reverse]), 'geocodejson', + raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson', {'addressdetails': True}) props = json.loads(raw)['features'][0]['properties']['geocoding'] @@ -183,7 +183,7 @@ def test_format_reverse_with_address_none(fmt): napi.Point(1.0, 2.0), address_rows=napi.AddressLines()) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'addressdetails': True}) @@ -213,7 +213,7 @@ def test_format_reverse_with_extratags(fmt): napi.Point(1.0, 2.0), extratags={'one': 'A', 'two':'B'}) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'extratags': True}) if fmt == 'xml': @@ -235,7 +235,7 @@ def test_format_reverse_with_extratags_none(fmt): ('place', 'thing'), napi.Point(1.0, 2.0)) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'extratags': True}) if fmt == 'xml': @@ -258,7 +258,7 @@ def test_format_reverse_with_namedetails_with_name(fmt): napi.Point(1.0, 2.0), names={'name': 'A', 'ref':'1'}) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'namedetails': True}) if fmt == 'xml': @@ -280,7 +280,7 @@ def test_format_reverse_with_namedetails_without_name(fmt): ('place', 'thing'), napi.Point(1.0, 2.0)) - raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'namedetails': True}) if fmt == 'xml': @@ -302,7 +302,7 @@ def test_search_details_with_icon_available(fmt): ('amenity', 'restaurant'), napi.Point(1.0, 2.0)) - result = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + result = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'icon_base_url': 'foo'}) js = json.loads(result) @@ -316,7 +316,7 @@ def test_search_details_with_icon_not_available(fmt): ('amenity', 'tree'), napi.Point(1.0, 2.0)) - result = api_impl.format_result(napi.ReverseResults([reverse]), fmt, + result = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {'icon_base_url': 'foo'}) assert 'icon' not in json.loads(result) diff --git a/test/python/cli/test_cmd_api.py b/test/python/cli/test_cmd_api.py index 811eadfe..1c0750d1 100644 --- a/test/python/cli/test_cmd_api.py +++ b/test/python/cli/test_cmd_api.py @@ -13,6 +13,15 @@ import pytest import nominatim_db.clicmd.api import nominatim_api as napi +@pytest.mark.parametrize('call', ['search', 'reverse', 'lookup', 'details', 'status']) +def test_list_format(cli_call, call): + assert 0 == cli_call(call, '--list-formats') + + +@pytest.mark.parametrize('call', ['search', 'reverse', 'lookup', 'details', 'status']) +def test_bad_format(cli_call, call): + assert 1 == cli_call(call, '--format', 'rsdfsdfsdfsaefsdfsd') + class TestCliStatusCall: -- 2.39.5