update Locales constructor:

expect output names as argument and avoid redundant configuration initialization
This commit is contained in:
Ayush Dhar Dubey
2025-12-20 19:14:20 +05:30
parent 049164086a
commit 4fd616254a
5 changed files with 73 additions and 93 deletions

View File

@@ -8,7 +8,6 @@
Helper functions for localizing names of results. Helper functions for localizing names of results.
""" """
from typing import Mapping, List, Optional from typing import Mapping, List, Optional
from .config import Configuration
from .results import AddressLines, BaseResultT from .results import AddressLines, BaseResultT
import re import re
@@ -18,15 +17,15 @@ class Locales:
""" Helper class for localization of names. """ Helper class for localization of names.
It takes a list of language prefixes in their order of preferred It takes a list of language prefixes in their order of preferred
usage. usage and comma separated name keys (Configuration.OUTPUT_NAMES).
""" """
def __init__(self, langs: Optional[List[str]] = None): def __init__(self, langs: Optional[List[str]] = None,
self.config = Configuration(None) names: str = 'name:XX,name') -> None:
self.languages = langs or [] self.languages = langs or []
self.name_tags: List[str] = [] self.name_tags: List[str] = []
parts = self.config.OUTPUT_NAMES.split(',') parts = names.split(',') if names else []
for part in parts: for part in parts:
part = part.strip() part = part.strip()
@@ -68,7 +67,7 @@ class Locales:
return next(iter(names.values())) return next(iter(names.values()))
@staticmethod @staticmethod
def from_accept_languages(langstr: str) -> 'Locales': def from_accept_languages(langstr: str, names: str = 'name:XX,name') -> 'Locales':
""" Create a localization object from a language list in the """ Create a localization object from a language list in the
format of HTTP accept-languages header. format of HTTP accept-languages header.
@@ -96,7 +95,7 @@ class Locales:
if len(parts) > 1 and all(c[0] != parts[0] for c in candidates): if len(parts) > 1 and all(c[0] != parts[0] for c in candidates):
languages.append(parts[0]) languages.append(parts[0])
return Locales(languages) return Locales(languages, names)
def localize(self, lines: AddressLines) -> None: def localize(self, lines: AddressLines) -> None:
""" Sets the local name of address parts according to the chosen """ Sets the local name of address parts according to the chosen

View File

@@ -174,7 +174,8 @@ async def details_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
if result is None: if result is None:
params.raise_error('No place with that OSM ID found.', status=404) params.raise_error('No place with that OSM ID found.', status=404)
locales = Locales.from_accept_languages(get_accepted_languages(params)) locales = Locales.from_accept_languages(get_accepted_languages(params),
params.config().OUTPUT_NAMES)
locales.localize_results([result]) locales.localize_results([result])
output = params.formatting().format_result( output = params.formatting().format_result(
@@ -215,8 +216,8 @@ async def reverse_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
query = '' query = ''
if result: if result:
Locales.from_accept_languages(get_accepted_languages(params)).localize_results( Locales.from_accept_languages(get_accepted_languages(params),
[result]) params.config().OUTPUT_NAMES).localize_results([result])
fmt_options = {'query': query, fmt_options = {'query': query,
'extratags': params.get_bool('extratags', False), 'extratags': params.get_bool('extratags', False),
@@ -255,7 +256,8 @@ async def lookup_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
if debug: if debug:
return build_response(params, loglib.get_and_disable(), num_results=len(results)) return build_response(params, loglib.get_and_disable(), num_results=len(results))
Locales.from_accept_languages(get_accepted_languages(params)).localize_results(results) Locales.from_accept_languages(get_accepted_languages(params),
params.config().OUTPUT_NAMES).localize_results(results)
fmt_options = {'extratags': params.get_bool('extratags', False), fmt_options = {'extratags': params.get_bool('extratags', False),
'namedetails': params.get_bool('namedetails', False), 'namedetails': params.get_bool('namedetails', False),
@@ -348,7 +350,8 @@ async def search_endpoint(api: NominatimAPIAsync, params: ASGIAdaptor) -> Any:
except UsageError as err: except UsageError as err:
params.raise_error(str(err)) params.raise_error(str(err))
Locales.from_accept_languages(get_accepted_languages(params)).localize_results(results) Locales.from_accept_languages(get_accepted_languages(params),
params.config().OUTPUT_NAMES).localize_results(results)
if details['dedupe'] and len(results) > 1: if details['dedupe'] and len(results) > 1:
results = helpers.deduplicate_results(results, max_results) results = helpers.deduplicate_results(results, max_results)

View File

@@ -19,6 +19,7 @@ import nominatim_api as napi
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.server.content_types import CONTENT_JSON from nominatim_api.server.content_types import CONTENT_JSON
import nominatim_api.logging as loglib import nominatim_api.logging as loglib
from ..config import Configuration
from ..errors import UsageError from ..errors import UsageError
from .args import NominatimArgs from .args import NominatimArgs
@@ -91,13 +92,14 @@ def _get_geometry_output(args: NominatimArgs) -> napi.GeometryFormat:
raise UsageError(f"Unknown polygon output format '{args.polygon_output}'.") from exp raise UsageError(f"Unknown polygon output format '{args.polygon_output}'.") from exp
def _get_locales(args: NominatimArgs, default: Optional[str]) -> napi.Locales: def _get_locales(args: NominatimArgs, config: Configuration) -> napi.Locales:
""" Get the locales from the language parameter. """ Get the locales from the language parameter.
""" """
if args.lang: language = args.lang or config.DEFAULT_LANGUAGE
return napi.Locales.from_accept_languages(args.lang) output_names = config.OUTPUT_NAMES
if default:
return napi.Locales.from_accept_languages(default) if language:
return napi.Locales.from_accept_languages(language, output_names)
return napi.Locales() return napi.Locales()
@@ -214,7 +216,7 @@ class APISearch:
except napi.UsageError as ex: except napi.UsageError as ex:
raise UsageError(ex) from ex raise UsageError(ex) from ex
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE) locales = _get_locales(args, api.config)
locales.localize_results(results) locales.localize_results(results)
if args.dedupe and len(results) > 1: if args.dedupe and len(results) > 1:
@@ -287,7 +289,7 @@ class APIReverse:
raise UsageError(ex) from ex raise UsageError(ex) from ex
if result is not None: if result is not None:
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE) locales = _get_locales(args, api.config)
locales.localize_results([result]) locales.localize_results([result])
if args.format == 'debug': if args.format == 'debug':
@@ -352,7 +354,7 @@ class APILookup:
except napi.UsageError as ex: except napi.UsageError as ex:
raise UsageError(ex) from ex raise UsageError(ex) from ex
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE) locales = _get_locales(args, api.config)
locales.localize_results(results) locales.localize_results(results)
if args.format == 'debug': if args.format == 'debug':
@@ -452,7 +454,7 @@ class APIDetails:
raise UsageError(ex) from ex raise UsageError(ex) from ex
if result is not None: if result is not None:
locales = _get_locales(args, api.config.DEFAULT_LANGUAGE) locales = _get_locales(args, api.config)
locales.localize_results([result]) locales.localize_results([result])
if args.format == 'debug': if args.format == 'debug':

View File

@@ -154,7 +154,7 @@ async def dump_results(conn: napi.SearchConnection,
await add_result_details(conn, results, await add_result_details(conn, results,
LookupDetails(address_details=True)) LookupDetails(address_details=True))
locale = napi.Locales([lang] if lang else None) locale = napi.Locales([lang] if lang else None, conn.config.OUTPUT_NAMES)
locale.localize_results(results) locale.localize_results(results)
for result in results: for result in results:

View File

@@ -31,20 +31,15 @@ def test_output_names_none_localized():
loc = Locales() loc = Locales()
expected_tags = [ expected_tags = [
'name', '_place_name', 'brand', '_place_brand', 'official_name', '_place_official_name', 'name', '_place_name'
'short_name', '_place_short_name', 'ref', '_place_ref'
] ]
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_none_localized_and_custom_output_names(monkeypatch): def test_output_names_none_localized_and_custom_output_names():
monkeypatch.setenv( loc = Locales(names='name:XX,entrance:XX,name,brand,test_tag,'
'NOMINATIM_OUTPUT_NAMES', 'official_name:XX,short_name:XX,alt_name:XX')
'name:XX,entrance:XX,name,brand,test_tag,'
'official_name:XX,short_name:XX,alt_name:XX'
)
loc = Locales()
expected_tags = [ expected_tags = [
'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag' 'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag'
@@ -53,13 +48,9 @@ def test_output_names_none_localized_and_custom_output_names(monkeypatch):
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_none_localized_and_custom_output_names_more_than_two_changes(monkeypatch): def test_output_names_none_localized_and_custom_output_names_more_than_two_changes():
monkeypatch.setenv( loc = Locales(names='name:XX,brand,test_tag:XX,official_name,short_name:XX,'
'NOMINATIM_OUTPUT_NAMES', 'alt_name,another_tag_with:XX,another_tag_withoutXX')
'name:XX,brand,test_tag:XX,official_name,short_name:XX,'
'alt_name,another_tag_with:XX,another_tag_withoutXX'
)
loc = Locales()
expected_tags = [ expected_tags = [
'brand', '_place_brand', 'official_name', '_place_official_name', 'alt_name', 'brand', '_place_brand', 'official_name', '_place_official_name', 'alt_name',
@@ -69,12 +60,8 @@ def test_output_names_none_localized_and_custom_output_names_more_than_two_chang
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_none_localized_and_custom_output_names_including_space(monkeypatch): def test_output_names_none_localized_and_custom_output_names_including_space():
monkeypatch.setenv( loc = Locales(names='name:XX,name ,short_name:XX, short_name')
'NOMINATIM_OUTPUT_NAMES',
'name:XX,name ,short_name:XX, short_name'
)
loc = Locales()
expected_tags = [ expected_tags = [
'name', '_place_name', 'short_name', '_place_short_name' 'name', '_place_name', 'short_name', '_place_short_name'
@@ -95,22 +82,32 @@ def test_output_names_localized():
loc = Locales(['en', 'es']) loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'name', '_place_name', 'brand', 'name:en', '_place_name:en', 'name:es', '_place_name:es', 'name', '_place_name'
'_place_brand', 'official_name:en', '_place_official_name:en', 'official_name:es',
'_place_official_name:es', 'short_name:en', '_place_short_name:en', 'short_name:es',
'_place_short_name:es', 'official_name', '_place_official_name', 'short_name',
'_place_short_name', 'ref', '_place_ref'
] ]
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_including_space(monkeypatch): def test_output_names_localized_and_empty_names():
monkeypatch.setenv( loc = Locales(['en'], "")
'NOMINATIM_OUTPUT_NAMES', expected_tags = []
'name:XX,name ,short_name:XX, short_name'
) assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
loc = Locales(['en', 'es'])
def test_output_names_localized_and_custom_names_ordering_logic():
loc = Locales(['en', 'fr'], "name:XX,ref")
expected = [
'name:en', '_place_name:en',
'name:fr', '_place_name:fr',
'ref', '_place_ref'
]
assert loc.name_tags == expected
def test_output_names_localized_and_custom_output_names_including_space():
loc = Locales(['en', 'es'], names='name:XX,name ,short_name:XX, short_name')
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'name:en', '_place_name:en', 'name:es', '_place_name:es',
@@ -122,12 +119,9 @@ def test_output_names_localized_and_custom_output_names_including_space(monkeypa
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names(monkeypatch): def test_output_names_localized_and_custom_output_names():
monkeypatch.setenv( loc = Locales(['en', 'es'], names='name:XX,entrance:XX,name,brand,test_tag,'
'NOMINATIM_OUTPUT_NAMES', 'official_name:XX,short_name:XX,alt_name:XX')
'name:XX,entrance:XX,name,brand,test_tag,official_name:XX,short_name:XX,alt_name:XX'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en', 'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en',
@@ -141,12 +135,9 @@ def test_output_names_localized_and_custom_output_names(monkeypatch):
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_start_with_tag_that_has_no_XX(monkeypatch): def test_output_names_localized_and_custom_output_names_start_with_tag_that_has_no_XX():
monkeypatch.setenv( loc = Locales(['en', 'es'],
'NOMINATIM_OUTPUT_NAMES', names='name,brand,test_tag,official_name:XX,short_name:XX,alt_name:XX')
'name,brand,test_tag,official_name:XX,short_name:XX,alt_name:XX'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag', 'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag',
@@ -159,12 +150,8 @@ def test_output_names_localized_and_custom_output_names_start_with_tag_that_has_
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_no_named_tags(monkeypatch): def test_output_names_localized_and_custom_output_names_no_named_tags():
monkeypatch.setenv( loc = Locales(['en', 'es'], names='name,brand,test_tag')
'NOMINATIM_OUTPUT_NAMES',
'name,brand,test_tag'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag' 'name', '_place_name', 'brand', '_place_brand', 'test_tag', '_place_test_tag'
@@ -173,12 +160,9 @@ def test_output_names_localized_and_custom_output_names_no_named_tags(monkeypatc
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_only_named_tags(monkeypatch): def test_output_names_localized_and_custom_output_names_only_named_tags():
monkeypatch.setenv( loc = Locales(['en', 'es'],
'NOMINATIM_OUTPUT_NAMES', names='name:XX,entrance:XX,official_name:XX,short_name:XX,alt_name:XX')
'name:XX,entrance:XX,official_name:XX,short_name:XX,alt_name:XX'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en', 'name:en', '_place_name:en', 'name:es', '_place_name:es', 'entrance:en',
@@ -191,13 +175,9 @@ def test_output_names_localized_and_custom_output_names_only_named_tags(monkeypa
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_more_than_two_changes(monkeypatch): def test_output_names_localized_and_custom_output_names_more_than_two_changes():
monkeypatch.setenv( loc = Locales(['en', 'es'], names='name:XX,brand,test_tag:XX,official_name,'
'NOMINATIM_OUTPUT_NAMES', 'short_name:XX,alt_name,another_tag_with:XX,another_tag_withoutXX')
'name:XX,brand,test_tag:XX,official_name,short_name:XX,'
'alt_name,another_tag_with:XX,another_tag_withoutXX'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'brand', '_place_brand', 'name:en', '_place_name:en', 'name:es', '_place_name:es', 'brand', '_place_brand',
@@ -211,13 +191,9 @@ def test_output_names_localized_and_custom_output_names_more_than_two_changes(mo
assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}' assert loc.name_tags == expected_tags, f'Expected {expected_tags}, but got {loc.name_tags}'
def test_output_names_localized_and_custom_output_names_XX_in_the_middle(monkeypatch): def test_output_names_localized_and_custom_output_names_XX_in_the_middle():
monkeypatch.setenv( loc = Locales(['en', 'es'], names='name:XX,br:XXand,test_tag:XX,official_name,'
'NOMINATIM_OUTPUT_NAMES', 'sh:XXort_name:XX,alt_name,another_tag_with:XX,another_tag_withoutXX')
'name:XX,br:XXand,test_tag:XX,official_name,sh:XXort_name:XX,'
'alt_name,another_tag_with:XX,another_tag_withoutXX'
)
loc = Locales(['en', 'es'])
expected_tags = [ expected_tags = [
'name:en', '_place_name:en', 'name:es', '_place_name:es', 'br:XXand', '_place_br:XXand', 'name:en', '_place_name:en', 'name:es', '_place_name:es', 'br:XXand', '_place_br:XXand',