From f9bd8b6a6bbc91c8436602d56f376998da6ec8dc Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Mon, 8 Jun 2026 11:24:13 +0200 Subject: [PATCH] make sure 'replacements' exists and is of right type --- .../query_preprocessing/config.py | 23 +++++++++++++++++-- .../query_preprocessing/regex_replace.py | 2 +- .../query_processing/test_regex_replace.py | 15 ++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/nominatim_api/query_preprocessing/config.py b/src/nominatim_api/query_preprocessing/config.py index a3c6a9ad..ab757c36 100644 --- a/src/nominatim_api/query_preprocessing/config.py +++ b/src/nominatim_api/query_preprocessing/config.py @@ -5,11 +5,15 @@ # Copyright (C) 2026 by the Nominatim developer community. # For a full list of authors see the git log. """ -Configuration for Sanitizers. +Configuration for query preprocessors. """ -from typing import Any +from typing import Any, TypeVar from collections import UserDict +from ..errors import UsageError + +T = TypeVar('T') + class QueryConfig(UserDict[str, Any]): """ The `QueryConfig` class is a read-only dictionary @@ -18,3 +22,18 @@ class QueryConfig(UserDict[str, Any]): accessors to standard preprocessor options that are used by many of the preprocessors. """ + + def require_typed(self, param: str, dtype: type[T]) -> T: + """ Return the value for the given parameter. + Raises a UsageError if the parameter is not present or + of the wrong type. + """ + result = self.get(param) + + if result is None: + raise UsageError(f"Parameter '{param}' missing.") + + if not isinstance(result, dtype): + raise UsageError(f"Parameter '{param}' must be of type {dtype.__name__}") + + return result diff --git a/src/nominatim_api/query_preprocessing/regex_replace.py b/src/nominatim_api/query_preprocessing/regex_replace.py index d62b1e18..2433c917 100644 --- a/src/nominatim_api/query_preprocessing/regex_replace.py +++ b/src/nominatim_api/query_preprocessing/regex_replace.py @@ -29,7 +29,7 @@ class _GenericPreprocessing: """Initialise the _GenericPreprocessing class with patterns from the ICU config file.""" self.config = config - match_patterns = self.config.get('replacements', 'Key not found') + match_patterns = self.config.require_typed('replacements', list) self.compiled_patterns = [ (re.compile(item['pattern']), item['replace']) for item in match_patterns ] diff --git a/test/python/api/query_processing/test_regex_replace.py b/test/python/api/query_processing/test_regex_replace.py index cb3f6037..26d32eb6 100644 --- a/test/python/api/query_processing/test_regex_replace.py +++ b/test/python/api/query_processing/test_regex_replace.py @@ -4,14 +4,15 @@ # # Copyright (C) 2026 by the Nominatim developer community. # For a full list of authors see the git log. -''' +""" Tests for replacing values in an input using custom regex. -''' +""" import pytest import nominatim_api.search.query as qmod from nominatim_api.query_preprocessing.config import QueryConfig from nominatim_api.query_preprocessing import regex_replace +from nominatim_api.errors import UsageError def run_preprocessor_on(query): @@ -46,3 +47,13 @@ def test_split_phrases(inp, outp): out = run_preprocessor_on(query) assert out == [qmod.Phrase(qmod.PHRASE_ANY, text) for text in outp] + + +def test_missing_replacement_section(): + with pytest.raises(UsageError, match="'replacements' missing"): + regex_replace.create(QueryConfig()) + + +def test_bad_type_for_replacement(): + with pytest.raises(UsageError, match="'replacements' must be of type list"): + regex_replace.create(QueryConfig({'replacements': 'something'})) -- 2.47.3