mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-25 18:48:15 +00:00
use custom result formatters in CLI commands
This commit is contained in:
@@ -39,6 +39,7 @@ from .results import (SourceTable as SourceTable,
|
|||||||
SearchResult as SearchResult,
|
SearchResult as SearchResult,
|
||||||
SearchResults as SearchResults)
|
SearchResults as SearchResults)
|
||||||
from .localization import (Locales as Locales)
|
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__
|
from .version import NOMINATIM_API_VERSION as __version__
|
||||||
|
|||||||
@@ -11,9 +11,3 @@ Implementation of API version v1 (aka the legacy version).
|
|||||||
#pylint: disable=useless-import-alias
|
#pylint: disable=useless-import-alias
|
||||||
|
|
||||||
from .server_glue import ROUTES as ROUTES
|
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
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"""
|
"""
|
||||||
Subcommand definitions for API calls from the command line.
|
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 argparse
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
@@ -15,9 +15,8 @@ import sys
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import nominatim_api as napi
|
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.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
|
import nominatim_api.logging as loglib
|
||||||
from ..errors import UsageError
|
from ..errors import UsageError
|
||||||
from .args import NominatimArgs
|
from .args import NominatimArgs
|
||||||
@@ -44,11 +43,16 @@ EXTRADATA_PARAMS = (
|
|||||||
('namedetails', 'Include a list of alternative names')
|
('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:
|
def _add_api_output_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
group = parser.add_argument_group('Output arguments')
|
group = parser.add_argument_group('Output formatting')
|
||||||
group.add_argument('--format', default='jsonv2',
|
group.add_argument('--format', type=str, default='jsonv2',
|
||||||
choices=formatting.list_formats(napi.SearchResults) + ['debug'],
|
help='Format of result (use --list-format to see supported formats)')
|
||||||
help='Format of result')
|
|
||||||
for name, desc in EXTRADATA_PARAMS:
|
for name, desc in EXTRADATA_PARAMS:
|
||||||
group.add_argument('--' + name, action='store_true', help=desc)
|
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))
|
(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:
|
class APISearch:
|
||||||
"""\
|
"""\
|
||||||
Execute a search query.
|
Execute a search query.
|
||||||
@@ -135,18 +157,24 @@ class APISearch:
|
|||||||
help='Preferred area to find search results')
|
help='Preferred area to find search results')
|
||||||
group.add_argument('--bounded', action='store_true',
|
group.add_argument('--bounded', action='store_true',
|
||||||
help='Strictly restrict results to viewbox area')
|
help='Strictly restrict results to viewbox area')
|
||||||
|
|
||||||
group = parser.add_argument_group('Other arguments')
|
|
||||||
group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
|
group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
|
||||||
help='Do not remove duplicates from the result list')
|
help='Do not remove duplicates from the result list')
|
||||||
|
_add_list_format(parser)
|
||||||
|
|
||||||
|
|
||||||
def run(self, args: NominatimArgs) -> int:
|
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':
|
if args.format == 'debug':
|
||||||
loglib.set_log_output('text')
|
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)
|
api = napi.NominatimAPI(args.project_dir)
|
||||||
|
|
||||||
params: Dict[str, Any] = {'max_results': args.limit + min(args.limit, 10),
|
params: Dict[str, Any] = {'max_results': args.limit + min(args.limit, 10),
|
||||||
'address_details': True, # needed for display name
|
'address_details': True, # needed for display name
|
||||||
'geometry_output': _get_geometry_output(args),
|
'geometry_output': _get_geometry_output(args),
|
||||||
@@ -177,19 +205,10 @@ class APISearch:
|
|||||||
print(loglib.get_and_disable())
|
print(loglib.get_and_disable())
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
output = api_output.format_result(
|
_print_output(formatter, results, args.format,
|
||||||
results,
|
{'extratags': args.extratags,
|
||||||
args.format,
|
'namedetails': args.namedetails,
|
||||||
{'extratags': args.extratags,
|
'addressdetails': args.addressdetails})
|
||||||
'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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -205,9 +224,9 @@ class APIReverse:
|
|||||||
|
|
||||||
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
||||||
group = parser.add_argument_group('Query arguments')
|
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)')
|
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)')
|
help='Longitude of coordinate to look up (in WGS84)')
|
||||||
group.add_argument('--zoom', type=int,
|
group.add_argument('--zoom', type=int,
|
||||||
help='Level of detail required for the address')
|
help='Level of detail required for the address')
|
||||||
@@ -217,14 +236,25 @@ class APIReverse:
|
|||||||
help='OSM id to lookup in format <NRW><id> (may be repeated)')
|
help='OSM id to lookup in format <NRW><id> (may be repeated)')
|
||||||
|
|
||||||
_add_api_output_arguments(parser)
|
_add_api_output_arguments(parser)
|
||||||
|
_add_list_format(parser)
|
||||||
|
|
||||||
|
|
||||||
def run(self, args: NominatimArgs) -> int:
|
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':
|
if args.format == 'debug':
|
||||||
loglib.set_log_output('text')
|
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.lat is None or args.lon is None:
|
||||||
|
raise UsageError("lat' and 'lon' parameters are required.")
|
||||||
|
|
||||||
api = napi.NominatimAPI(args.project_dir)
|
api = napi.NominatimAPI(args.project_dir)
|
||||||
|
|
||||||
result = api.reverse(napi.Point(args.lon, args.lat),
|
result = api.reverse(napi.Point(args.lon, args.lat),
|
||||||
max_rank=zoom_to_rank(args.zoom or 18),
|
max_rank=zoom_to_rank(args.zoom or 18),
|
||||||
layers=_get_layers(args, napi.DataLayer.ADDRESS | napi.DataLayer.POI),
|
layers=_get_layers(args, napi.DataLayer.ADDRESS | napi.DataLayer.POI),
|
||||||
@@ -238,18 +268,10 @@ class APIReverse:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
output = api_output.format_result(
|
_print_output(formatter, napi.ReverseResults([result]), args.format,
|
||||||
napi.ReverseResults([result]),
|
{'extratags': args.extratags,
|
||||||
args.format,
|
'namedetails': args.namedetails,
|
||||||
{'extratags': args.extratags,
|
'addressdetails': args.addressdetails})
|
||||||
'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
|
return 0
|
||||||
|
|
||||||
@@ -271,43 +293,45 @@ class APILookup:
|
|||||||
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
||||||
group = parser.add_argument_group('Query arguments')
|
group = parser.add_argument_group('Query arguments')
|
||||||
group.add_argument('--id', metavar='OSMID',
|
group.add_argument('--id', metavar='OSMID',
|
||||||
action='append', required=True, dest='ids',
|
action='append', dest='ids',
|
||||||
help='OSM id to lookup in format <NRW><id> (may be repeated)')
|
help='OSM id to lookup in format <NRW><id> (may be repeated)')
|
||||||
|
|
||||||
_add_api_output_arguments(parser)
|
_add_api_output_arguments(parser)
|
||||||
|
_add_list_format(parser)
|
||||||
|
|
||||||
|
|
||||||
def run(self, args: NominatimArgs) -> int:
|
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':
|
if args.format == 'debug':
|
||||||
loglib.set_log_output('text')
|
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.ids is None:
|
||||||
|
raise UsageError("'id' parameter required.")
|
||||||
if args.format == 'debug':
|
|
||||||
print(loglib.get_and_disable())
|
|
||||||
return 0
|
|
||||||
|
|
||||||
places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids]
|
places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids]
|
||||||
|
|
||||||
|
api = napi.NominatimAPI(args.project_dir)
|
||||||
results = api.lookup(places,
|
results = api.lookup(places,
|
||||||
address_details=True, # needed for display name
|
address_details=True, # needed for display name
|
||||||
geometry_output=_get_geometry_output(args),
|
geometry_output=_get_geometry_output(args),
|
||||||
geometry_simplification=args.polygon_threshold or 0.0,
|
geometry_simplification=args.polygon_threshold or 0.0,
|
||||||
locales=_get_locales(args, api.config.DEFAULT_LANGUAGE))
|
locales=_get_locales(args, api.config.DEFAULT_LANGUAGE))
|
||||||
|
|
||||||
output = api_output.format_result(
|
if args.format == 'debug':
|
||||||
results,
|
print(loglib.get_and_disable())
|
||||||
args.format,
|
return 0
|
||||||
{'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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -323,20 +347,21 @@ class APIDetails:
|
|||||||
|
|
||||||
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
||||||
group = parser.add_argument_group('Query arguments')
|
group = parser.add_argument_group('Query arguments')
|
||||||
objs = group.add_mutually_exclusive_group(required=True)
|
group.add_argument('--node', '-n', type=int,
|
||||||
objs.add_argument('--node', '-n', type=int,
|
help="Look up the OSM node with the given ID.")
|
||||||
help="Look up the OSM node with the given ID.")
|
group.add_argument('--way', '-w', type=int,
|
||||||
objs.add_argument('--way', '-w', type=int,
|
help="Look up the OSM way with the given ID.")
|
||||||
help="Look up the OSM way with the given ID.")
|
group.add_argument('--relation', '-r', type=int,
|
||||||
objs.add_argument('--relation', '-r', type=int,
|
help="Look up the OSM relation with the given ID.")
|
||||||
help="Look up the OSM relation with the given ID.")
|
group.add_argument('--place_id', '-p', type=int,
|
||||||
objs.add_argument('--place_id', '-p', type=int,
|
help='Database internal identifier of the OSM object to look up')
|
||||||
help='Database internal identifier of the OSM object to look up')
|
|
||||||
group.add_argument('--class', dest='object_class',
|
group.add_argument('--class', dest='object_class',
|
||||||
help=("Class type to disambiguated multiple entries "
|
help=("Class type to disambiguated multiple entries "
|
||||||
"of the same object."))
|
"of the same object."))
|
||||||
|
|
||||||
group = parser.add_argument_group('Output arguments')
|
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',
|
group.add_argument('--addressdetails', action='store_true',
|
||||||
help='Include a breakdown of the address into elements')
|
help='Include a breakdown of the address into elements')
|
||||||
group.add_argument('--keywords', action='store_true',
|
group.add_argument('--keywords', action='store_true',
|
||||||
@@ -351,9 +376,21 @@ class APIDetails:
|
|||||||
help='Include geometry of result')
|
help='Include geometry of result')
|
||||||
group.add_argument('--lang', '--accept-language', metavar='LANGS',
|
group.add_argument('--lang', '--accept-language', metavar='LANGS',
|
||||||
help='Preferred language order for presenting search results')
|
help='Preferred language order for presenting search results')
|
||||||
|
_add_list_format(parser)
|
||||||
|
|
||||||
|
|
||||||
def run(self, args: NominatimArgs) -> int:
|
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
|
place: napi.PlaceRef
|
||||||
if args.node:
|
if args.node:
|
||||||
place = napi.OsmID('N', args.node, args.object_class)
|
place = napi.OsmID('N', args.node, args.object_class)
|
||||||
@@ -361,12 +398,13 @@ class APIDetails:
|
|||||||
place = napi.OsmID('W', args.way, args.object_class)
|
place = napi.OsmID('W', args.way, args.object_class)
|
||||||
elif args.relation:
|
elif args.relation:
|
||||||
place = napi.OsmID('R', args.relation, args.object_class)
|
place = napi.OsmID('R', args.relation, args.object_class)
|
||||||
else:
|
elif args.place_id is not None:
|
||||||
assert args.place_id is not None
|
|
||||||
place = napi.PlaceID(args.place_id)
|
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)
|
api = napi.NominatimAPI(args.project_dir)
|
||||||
|
|
||||||
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
|
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE)
|
||||||
result = api.details(place,
|
result = api.details(place,
|
||||||
address_details=args.addressdetails,
|
address_details=args.addressdetails,
|
||||||
@@ -378,17 +416,14 @@ class APIDetails:
|
|||||||
else napi.GeometryFormat.NONE,
|
else napi.GeometryFormat.NONE,
|
||||||
locales=locales)
|
locales=locales)
|
||||||
|
|
||||||
|
if args.format == 'debug':
|
||||||
|
print(loglib.get_and_disable())
|
||||||
|
return 0
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
output = api_output.format_result(
|
_print_output(formatter, result, args.format or 'json',
|
||||||
result,
|
{'locales': locales,
|
||||||
'json',
|
'group_hierarchy': args.group_hierarchy})
|
||||||
{'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')
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
LOG.error("Object not found in database.")
|
LOG.error("Object not found in database.")
|
||||||
@@ -406,13 +441,30 @@ class APIStatus:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
def add_args(self, parser: argparse.ArgumentParser) -> None:
|
||||||
formats = api_output.list_formats(napi.StatusResult)
|
|
||||||
group = parser.add_argument_group('API parameters')
|
group = parser.add_argument_group('API parameters')
|
||||||
group.add_argument('--format', default=formats[0], choices=formats,
|
group.add_argument('--format', type=str, default='text',
|
||||||
help='Format of result')
|
help='Format of result (use --list-formats to see supported formats)')
|
||||||
|
_add_list_format(parser)
|
||||||
|
|
||||||
|
|
||||||
def run(self, args: NominatimArgs) -> int:
|
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()
|
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
|
return 0
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ class NominatimArgs:
|
|||||||
|
|
||||||
# Arguments to all query functions
|
# Arguments to all query functions
|
||||||
format: str
|
format: str
|
||||||
|
list_formats: bool
|
||||||
addressdetails: bool
|
addressdetails: bool
|
||||||
extratags: bool
|
extratags: bool
|
||||||
namedetails: bool
|
namedetails: bool
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import json
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import nominatim_api.v1 as api_impl
|
from nominatim_api.v1.format import dispatch as v1_format
|
||||||
import nominatim_api as napi
|
import nominatim_api as napi
|
||||||
|
|
||||||
STATUS_FORMATS = {'text', 'json'}
|
STATUS_FORMATS = {'text', 'json'}
|
||||||
@@ -23,30 +23,30 @@ STATUS_FORMATS = {'text', 'json'}
|
|||||||
# StatusResult
|
# StatusResult
|
||||||
|
|
||||||
def test_status_format_list():
|
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))
|
@pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
|
||||||
def test_status_supported(fmt):
|
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():
|
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():
|
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():
|
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():
|
def test_status_format_json_minimal():
|
||||||
status = napi.StatusResult(700, 'Bad format.')
|
status = napi.StatusResult(700, 'Bad format.')
|
||||||
|
|
||||||
result = api_impl.format_result(status, 'json', {})
|
result = v1_format.format_result(status, 'json', {})
|
||||||
|
|
||||||
assert result == \
|
assert result == \
|
||||||
f'{{"status":700,"message":"Bad format.","software_version":"{napi.__version__}"}}'
|
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.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
|
||||||
status.database_version = '5.6'
|
status.database_version = '5.6'
|
||||||
|
|
||||||
result = api_impl.format_result(status, 'json', {})
|
result = v1_format.format_result(status, 'json', {})
|
||||||
|
|
||||||
assert result == \
|
assert result == \
|
||||||
f'{{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"{napi.__version__}","database_version":"5.6"}}'
|
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'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0))
|
napi.Point(1.0, 2.0))
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'json', {})
|
result = v1_format.format_result(search, 'json', {})
|
||||||
|
|
||||||
assert json.loads(result) == \
|
assert json.loads(result) == \
|
||||||
{'category': 'place',
|
{'category': 'place',
|
||||||
@@ -114,7 +114,7 @@ def test_search_details_full():
|
|||||||
)
|
)
|
||||||
search.localize(napi.Locales())
|
search.localize(napi.Locales())
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'json', {})
|
result = v1_format.format_result(search, 'json', {})
|
||||||
|
|
||||||
assert json.loads(result) == \
|
assert json.loads(result) == \
|
||||||
{'place_id': 37563,
|
{'place_id': 37563,
|
||||||
@@ -153,7 +153,7 @@ def test_search_details_no_geometry(gtype, isarea):
|
|||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
geometry={'type': gtype})
|
geometry={'type': gtype})
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'json', {})
|
result = v1_format.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
|
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),
|
napi.Point(1.0, 2.0),
|
||||||
geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
|
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
|
||||||
@@ -178,7 +178,7 @@ def test_search_details_with_icon_available():
|
|||||||
('amenity', 'restaurant'),
|
('amenity', 'restaurant'),
|
||||||
napi.Point(1.0, 2.0))
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['icon'] == 'foo/food_restaurant.p.20.png'
|
assert js['icon'] == 'foo/food_restaurant.p.20.png'
|
||||||
@@ -189,7 +189,7 @@ def test_search_details_with_icon_not_available():
|
|||||||
('amenity', 'tree'),
|
('amenity', 'tree'),
|
||||||
napi.Point(1.0, 2.0))
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert 'icon' not in js
|
assert 'icon' not in js
|
||||||
@@ -212,7 +212,7 @@ def test_search_details_with_address_minimal():
|
|||||||
distance=0.0)
|
distance=0.0)
|
||||||
])
|
])
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'json', {})
|
result = v1_format.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['address'] == [{'localname': '',
|
assert js['address'] == [{'localname': '',
|
||||||
@@ -245,7 +245,7 @@ def test_search_details_with_further_infos(field, outfield):
|
|||||||
distance=0.034)
|
distance=0.034)
|
||||||
])
|
])
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'json', {})
|
result = v1_format.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js[outfield] == [{'localname': 'Trespass',
|
assert js[outfield] == [{'localname': 'Trespass',
|
||||||
@@ -279,7 +279,7 @@ def test_search_details_grouped_hierarchy():
|
|||||||
distance=0.034)
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
|
assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
|
||||||
@@ -303,7 +303,7 @@ def test_search_details_keywords_name():
|
|||||||
napi.WordInfo(23, 'foo', 'mefoo'),
|
napi.WordInfo(23, 'foo', 'mefoo'),
|
||||||
napi.WordInfo(24, 'foo', 'bafoo')])
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
|
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(23, 'foo', 'mefoo'),
|
||||||
napi.WordInfo(24, 'foo', 'bafoo')])
|
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)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
|
assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import xml.etree.ElementTree as ET
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import nominatim_api.v1 as api_impl
|
from nominatim_api.v1.format import dispatch as v1_format
|
||||||
import nominatim_api as napi
|
import nominatim_api as napi
|
||||||
|
|
||||||
FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml']
|
FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml']
|
||||||
@@ -26,7 +26,7 @@ def test_format_reverse_minimal(fmt):
|
|||||||
('amenity', 'post_box'),
|
('amenity', 'post_box'),
|
||||||
napi.Point(0.3, -8.9))
|
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':
|
if fmt == 'xml':
|
||||||
root = ET.fromstring(raw)
|
root = ET.fromstring(raw)
|
||||||
@@ -38,7 +38,7 @@ def test_format_reverse_minimal(fmt):
|
|||||||
|
|
||||||
@pytest.mark.parametrize('fmt', FORMATS)
|
@pytest.mark.parametrize('fmt', FORMATS)
|
||||||
def test_format_reverse_no_result(fmt):
|
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':
|
if fmt == 'xml':
|
||||||
root = ET.fromstring(raw)
|
root = ET.fromstring(raw)
|
||||||
@@ -55,7 +55,7 @@ def test_format_reverse_with_osm_id(fmt):
|
|||||||
place_id=5564,
|
place_id=5564,
|
||||||
osm_object=('N', 23))
|
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':
|
if fmt == 'xml':
|
||||||
root = ET.fromstring(raw).find('result')
|
root = ET.fromstring(raw).find('result')
|
||||||
@@ -103,7 +103,7 @@ def test_format_reverse_with_address(fmt):
|
|||||||
]))
|
]))
|
||||||
reverse.localize(napi.Locales())
|
reverse.localize(napi.Locales())
|
||||||
|
|
||||||
raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
|
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
|
||||||
{'addressdetails': True})
|
{'addressdetails': True})
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ def test_format_reverse_geocodejson_special_parts():
|
|||||||
|
|
||||||
reverse.localize(napi.Locales())
|
reverse.localize(napi.Locales())
|
||||||
|
|
||||||
raw = api_impl.format_result(napi.ReverseResults([reverse]), 'geocodejson',
|
raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson',
|
||||||
{'addressdetails': True})
|
{'addressdetails': True})
|
||||||
|
|
||||||
props = json.loads(raw)['features'][0]['properties']['geocoding']
|
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),
|
napi.Point(1.0, 2.0),
|
||||||
address_rows=napi.AddressLines())
|
address_rows=napi.AddressLines())
|
||||||
|
|
||||||
raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
|
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
|
||||||
{'addressdetails': True})
|
{'addressdetails': True})
|
||||||
|
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ def test_format_reverse_with_extratags(fmt):
|
|||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
extratags={'one': 'A', 'two':'B'})
|
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})
|
{'extratags': True})
|
||||||
|
|
||||||
if fmt == 'xml':
|
if fmt == 'xml':
|
||||||
@@ -235,7 +235,7 @@ def test_format_reverse_with_extratags_none(fmt):
|
|||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0))
|
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})
|
{'extratags': True})
|
||||||
|
|
||||||
if fmt == 'xml':
|
if fmt == 'xml':
|
||||||
@@ -258,7 +258,7 @@ def test_format_reverse_with_namedetails_with_name(fmt):
|
|||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
names={'name': 'A', 'ref':'1'})
|
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})
|
{'namedetails': True})
|
||||||
|
|
||||||
if fmt == 'xml':
|
if fmt == 'xml':
|
||||||
@@ -280,7 +280,7 @@ def test_format_reverse_with_namedetails_without_name(fmt):
|
|||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0))
|
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})
|
{'namedetails': True})
|
||||||
|
|
||||||
if fmt == 'xml':
|
if fmt == 'xml':
|
||||||
@@ -302,7 +302,7 @@ def test_search_details_with_icon_available(fmt):
|
|||||||
('amenity', 'restaurant'),
|
('amenity', 'restaurant'),
|
||||||
napi.Point(1.0, 2.0))
|
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'})
|
{'icon_base_url': 'foo'})
|
||||||
|
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
@@ -316,7 +316,7 @@ def test_search_details_with_icon_not_available(fmt):
|
|||||||
('amenity', 'tree'),
|
('amenity', 'tree'),
|
||||||
napi.Point(1.0, 2.0))
|
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'})
|
{'icon_base_url': 'foo'})
|
||||||
|
|
||||||
assert 'icon' not in json.loads(result)
|
assert 'icon' not in json.loads(result)
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ import pytest
|
|||||||
import nominatim_db.clicmd.api
|
import nominatim_db.clicmd.api
|
||||||
import nominatim_api as napi
|
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:
|
class TestCliStatusCall:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user