diff --git a/.flake8 b/.flake8
index 82d77ed3..f3e0db56 100644
--- a/.flake8
+++ b/.flake8
@@ -6,3 +6,5 @@ extend-ignore =
E711
per-file-ignores =
__init__.py: F401
+ test/python/utils/test_json_writer.py: E131
+ test/python/conftest.py: E402
diff --git a/Makefile b/Makefile
index 9e914850..72072a59 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ pytest:
pytest test/python
lint:
- flake8 src
+ flake8 src test/python
bdd:
cd test/bdd; behave -DREMOVE_TEMPLATE=1
diff --git a/test/python/api/conftest.py b/test/python/api/conftest.py
index 3ca0720b..bde0afc4 100644
--- a/test/python/api/conftest.py
+++ b/test/python/api/conftest.py
@@ -2,14 +2,13 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Helper fixtures for API call tests.
"""
import pytest
import pytest_asyncio
-import time
import datetime as dt
import sqlalchemy as sa
@@ -20,27 +19,25 @@ from nominatim_api.search.query_analyzer_factory import make_query_analyzer
from nominatim_db.tools import convert_sqlite
import nominatim_api.logging as loglib
+
class APITester:
def __init__(self):
self.api = napi.NominatimAPI()
self.async_to_sync(self.api._async_api.setup_database())
-
def async_to_sync(self, func):
""" Run an asynchronous function until completion using the
internal loop of the API.
"""
return self.api._loop.run_until_complete(func)
-
def add_data(self, table, data):
""" Insert data into the given table.
"""
sql = getattr(self.api._async_api._tables, table).insert()
self.async_to_sync(self.exec_async(sql, data))
-
def add_placex(self, **kw):
name = kw.get('name')
if isinstance(name, str):
@@ -50,30 +47,29 @@ class APITester:
geometry = kw.get('geometry', 'POINT(%f %f)' % centroid)
self.add_data('placex',
- {'place_id': kw.get('place_id', 1000),
- 'osm_type': kw.get('osm_type', 'W'),
- 'osm_id': kw.get('osm_id', 4),
- 'class_': kw.get('class_', 'highway'),
- 'type': kw.get('type', 'residential'),
- 'name': name,
- 'address': kw.get('address'),
- 'extratags': kw.get('extratags'),
- 'parent_place_id': kw.get('parent_place_id'),
- 'linked_place_id': kw.get('linked_place_id'),
- 'admin_level': kw.get('admin_level', 15),
- 'country_code': kw.get('country_code'),
- 'housenumber': kw.get('housenumber'),
- 'postcode': kw.get('postcode'),
- 'wikipedia': kw.get('wikipedia'),
- 'rank_search': kw.get('rank_search', 30),
- 'rank_address': kw.get('rank_address', 30),
- 'importance': kw.get('importance'),
- 'centroid': 'POINT(%f %f)' % centroid,
- 'indexed_status': kw.get('indexed_status', 0),
- 'indexed_date': kw.get('indexed_date',
- dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
- 'geometry': geometry})
-
+ {'place_id': kw.get('place_id', 1000),
+ 'osm_type': kw.get('osm_type', 'W'),
+ 'osm_id': kw.get('osm_id', 4),
+ 'class_': kw.get('class_', 'highway'),
+ 'type': kw.get('type', 'residential'),
+ 'name': name,
+ 'address': kw.get('address'),
+ 'extratags': kw.get('extratags'),
+ 'parent_place_id': kw.get('parent_place_id'),
+ 'linked_place_id': kw.get('linked_place_id'),
+ 'admin_level': kw.get('admin_level', 15),
+ 'country_code': kw.get('country_code'),
+ 'housenumber': kw.get('housenumber'),
+ 'postcode': kw.get('postcode'),
+ 'wikipedia': kw.get('wikipedia'),
+ 'rank_search': kw.get('rank_search', 30),
+ 'rank_address': kw.get('rank_address', 30),
+ 'importance': kw.get('importance'),
+ 'centroid': 'POINT(%f %f)' % centroid,
+ 'indexed_status': kw.get('indexed_status', 0),
+ 'indexed_date': kw.get('indexed_date',
+ dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
+ 'geometry': geometry})
def add_address_placex(self, object_id, **kw):
self.add_placex(**kw)
@@ -85,46 +81,42 @@ class APITester:
'fromarea': kw.get('fromarea', False),
'isaddress': kw.get('isaddress', True)})
-
def add_osmline(self, **kw):
self.add_data('osmline',
- {'place_id': kw.get('place_id', 10000),
- 'osm_id': kw.get('osm_id', 4004),
- 'parent_place_id': kw.get('parent_place_id'),
- 'indexed_date': kw.get('indexed_date',
- dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
- 'startnumber': kw.get('startnumber', 2),
- 'endnumber': kw.get('endnumber', 6),
- 'step': kw.get('step', 2),
- 'address': kw.get('address'),
- 'postcode': kw.get('postcode'),
- 'country_code': kw.get('country_code'),
- 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
-
+ {'place_id': kw.get('place_id', 10000),
+ 'osm_id': kw.get('osm_id', 4004),
+ 'parent_place_id': kw.get('parent_place_id'),
+ 'indexed_date': kw.get('indexed_date',
+ dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
+ 'startnumber': kw.get('startnumber', 2),
+ 'endnumber': kw.get('endnumber', 6),
+ 'step': kw.get('step', 2),
+ 'address': kw.get('address'),
+ 'postcode': kw.get('postcode'),
+ 'country_code': kw.get('country_code'),
+ 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
def add_tiger(self, **kw):
self.add_data('tiger',
- {'place_id': kw.get('place_id', 30000),
- 'parent_place_id': kw.get('parent_place_id'),
- 'startnumber': kw.get('startnumber', 2),
- 'endnumber': kw.get('endnumber', 6),
- 'step': kw.get('step', 2),
- 'postcode': kw.get('postcode'),
- 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
-
+ {'place_id': kw.get('place_id', 30000),
+ 'parent_place_id': kw.get('parent_place_id'),
+ 'startnumber': kw.get('startnumber', 2),
+ 'endnumber': kw.get('endnumber', 6),
+ 'step': kw.get('step', 2),
+ 'postcode': kw.get('postcode'),
+ 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
def add_postcode(self, **kw):
self.add_data('postcode',
- {'place_id': kw.get('place_id', 1000),
- 'parent_place_id': kw.get('parent_place_id'),
- 'country_code': kw.get('country_code'),
- 'postcode': kw.get('postcode'),
- 'rank_search': kw.get('rank_search', 20),
- 'rank_address': kw.get('rank_address', 22),
- 'indexed_date': kw.get('indexed_date',
- dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
- 'geometry': kw.get('geometry', 'POINT(23 34)')})
-
+ {'place_id': kw.get('place_id', 1000),
+ 'parent_place_id': kw.get('parent_place_id'),
+ 'country_code': kw.get('country_code'),
+ 'postcode': kw.get('postcode'),
+ 'rank_search': kw.get('rank_search', 20),
+ 'rank_address': kw.get('rank_address', 22),
+ 'indexed_date': kw.get('indexed_date',
+ dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
+ 'geometry': kw.get('geometry', 'POINT(23 34)')})
def add_country(self, country_code, geometry):
self.add_data('country_grid',
@@ -132,14 +124,12 @@ class APITester:
'area': 0.1,
'geometry': geometry})
-
def add_country_name(self, country_code, names, partition=0):
self.add_data('country_name',
{'country_code': country_code,
'name': names,
'partition': partition})
-
def add_search_name(self, place_id, **kw):
centroid = kw.get('centroid', (23.0, 34.0))
self.add_data('search_name',
@@ -152,7 +142,6 @@ class APITester:
'country_code': kw.get('country_code', 'xx'),
'centroid': 'POINT(%f %f)' % centroid})
-
def add_class_type_table(self, cls, typ):
self.async_to_sync(
self.exec_async(sa.text(f"""CREATE TABLE place_classtype_{cls}_{typ}
@@ -160,7 +149,6 @@ class APITester:
WHERE class = '{cls}' AND type = '{typ}')
""")))
-
def add_word_table(self, content):
data = [dict(zip(['word_id', 'word_token', 'type', 'word', 'info'], c))
for c in content]
@@ -176,12 +164,10 @@ class APITester:
self.async_to_sync(_do_sql())
-
async def exec_async(self, sql, *args, **kwargs):
async with self.api._async_api.begin() as conn:
return await conn.execute(sql, *args, **kwargs)
-
async def create_tables(self):
async with self.api._async_api._engine.begin() as conn:
await conn.run_sync(self.api._async_api._tables.meta.create_all)
@@ -212,11 +198,12 @@ def frontend(request, event_loop, tmp_path):
db = str(tmp_path / 'test_nominatim_python_unittest.sqlite')
def mkapi(apiobj, options={'reverse'}):
- apiobj.add_data('properties',
- [{'property': 'tokenizer', 'value': 'icu'},
- {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'},
- {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"},
- ])
+ apiobj.add_data(
+ 'properties',
+ [{'property': 'tokenizer', 'value': 'icu'},
+ {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'},
+ {'property': 'tokenizer_import_transliteration',
+ 'value': "'1' > '/1/'; 'ä' > 'ä '"}])
async def _do_sql():
async with apiobj.api._async_api.begin() as conn:
diff --git a/test/python/api/fake_adaptor.py b/test/python/api/fake_adaptor.py
index 4b64c17d..a3a3bcf9 100644
--- a/test/python/api/fake_adaptor.py
+++ b/test/python/api/fake_adaptor.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Provides dummy implementations of ASGIAdaptor for testing.
@@ -13,6 +13,7 @@ import nominatim_api.v1.server_glue as glue
from nominatim_api.v1.format import dispatch as formatting
from nominatim_api.config import Configuration
+
class FakeError(BaseException):
def __init__(self, msg, status):
@@ -22,8 +23,10 @@ class FakeError(BaseException):
def __str__(self):
return f'{self.status} -- {self.msg}'
+
FakeResponse = namedtuple('FakeResponse', ['status', 'output', 'content_type'])
+
class FakeAdaptor(glue.ASGIAdaptor):
def __init__(self, params=None, headers=None, config=None):
@@ -31,23 +34,18 @@ class FakeAdaptor(glue.ASGIAdaptor):
self.headers = headers or {}
self._config = config or Configuration(None)
-
def get(self, name, default=None):
return self.params.get(name, default)
-
def get_header(self, name, default=None):
return self.headers.get(name, default)
-
def error(self, msg, status=400):
return FakeError(msg, status)
-
def create_response(self, status, output, num_results):
return FakeResponse(status, output, self.content_type)
-
def base_uri(self):
return 'http://test'
@@ -56,5 +54,3 @@ class FakeAdaptor(glue.ASGIAdaptor):
def formatting(self):
return formatting
-
-
diff --git a/test/python/api/query_processing/test_normalize.py b/test/python/api/query_processing/test_normalize.py
index 12a8de2a..35f5fcd7 100644
--- a/test/python/api/query_processing/test_normalize.py
+++ b/test/python/api/query_processing/test_normalize.py
@@ -2,21 +2,18 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for normalizing search queries.
"""
-from pathlib import Path
-
-import pytest
-
from icu import Transliterator
import nominatim_api.search.query as qmod
from nominatim_api.query_preprocessing.config import QueryConfig
from nominatim_api.query_preprocessing import normalize
+
def run_preprocessor_on(query, norm):
normalizer = Transliterator.createFromRules("normalization", norm)
proc = normalize.create(QueryConfig().set_normalizer(normalizer))
diff --git a/test/python/api/query_processing/test_split_japanese_phrases.py b/test/python/api/query_processing/test_split_japanese_phrases.py
index 51d592e3..30f22e7b 100644
--- a/test/python/api/query_processing/test_split_japanese_phrases.py
+++ b/test/python/api/query_processing/test_split_japanese_phrases.py
@@ -7,16 +7,13 @@
"""
Tests for japanese phrase splitting.
"""
-from pathlib import Path
-
import pytest
-from icu import Transliterator
-
import nominatim_api.search.query as qmod
from nominatim_api.query_preprocessing.config import QueryConfig
from nominatim_api.query_preprocessing import split_japanese_phrases
+
def run_preprocessor_on(query):
proc = split_japanese_phrases.create(QueryConfig().set_normalizer(None))
diff --git a/test/python/api/search/test_api_search_query.py b/test/python/api/search/test_api_search_query.py
index 08a1f7aa..c9e8de93 100644
--- a/test/python/api/search/test_api_search_query.py
+++ b/test/python/api/search/test_api_search_query.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for tokenized query data structures.
@@ -11,6 +11,7 @@ import pytest
from nominatim_api.search import query
+
class MyToken(query.Token):
def get_category(self):
@@ -21,9 +22,11 @@ def mktoken(tid: int):
return MyToken(penalty=3.0, token=tid, count=1, addr_count=1,
lookup_word='foo')
+
@pytest.fixture
def qnode():
- return query.QueryNode(query.BREAK_PHRASE, query.PHRASE_ANY, 0.0 ,'', '')
+ return query.QueryNode(query.BREAK_PHRASE, query.PHRASE_ANY, 0.0, '', '')
+
@pytest.mark.parametrize('ptype,ttype', [(query.PHRASE_ANY, 'W'),
(query.PHRASE_AMENITY, 'Q'),
@@ -132,4 +135,3 @@ def test_query_struct_amenity_two_words():
assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_PARTIAL)) == 1
assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_NEAR_ITEM)) == 0
assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_QUALIFIER)) == 1
-
diff --git a/test/python/api/search/test_db_search_builder.py b/test/python/api/search/test_db_search_builder.py
index 49d5f303..be34fbea 100644
--- a/test/python/api/search/test_db_search_builder.py
+++ b/test/python/api/search/test_db_search_builder.py
@@ -16,6 +16,7 @@ from nominatim_api.search.token_assignment import TokenAssignment
from nominatim_api.types import SearchDetails
import nominatim_api.search.db_searches as dbs
+
class MyToken(Token):
def get_category(self):
return 'this', 'that'
@@ -36,7 +37,6 @@ def make_query(*args):
token=tid, count=1, addr_count=1,
lookup_word=word))
-
return q
@@ -241,8 +241,7 @@ def test_name_and_address():
[(2, qmod.TOKEN_PARTIAL, [(2, 'b')]),
(2, qmod.TOKEN_WORD, [(101, 'b')])],
[(3, qmod.TOKEN_PARTIAL, [(3, 'c')]),
- (3, qmod.TOKEN_WORD, [(102, 'c')])]
- )
+ (3, qmod.TOKEN_WORD, [(102, 'c')])])
builder = SearchBuilder(q, SearchDetails())
searches = list(builder.build(TokenAssignment(name=TokenRange(0, 1),
@@ -267,8 +266,7 @@ def test_name_and_complex_address():
(3, qmod.TOKEN_WORD, [(101, 'bc')])],
[(3, qmod.TOKEN_PARTIAL, [(3, 'c')])],
[(4, qmod.TOKEN_PARTIAL, [(4, 'd')]),
- (4, qmod.TOKEN_WORD, [(103, 'd')])]
- )
+ (4, qmod.TOKEN_WORD, [(103, 'd')])])
builder = SearchBuilder(q, SearchDetails())
searches = list(builder.build(TokenAssignment(name=TokenRange(0, 1),
@@ -423,8 +421,8 @@ def test_infrequent_partials_in_name():
assert len(search.lookups) == 2
assert len(search.rankings) == 2
- assert set((l.column, l.lookup_type.__name__) for l in search.lookups) == \
- {('name_vector', 'LookupAll'), ('nameaddress_vector', 'Restrict')}
+ assert set((s.column, s.lookup_type.__name__) for s in search.lookups) == \
+ {('name_vector', 'LookupAll'), ('nameaddress_vector', 'Restrict')}
def test_frequent_partials_in_name_and_address():
@@ -435,10 +433,10 @@ def test_frequent_partials_in_name_and_address():
assert all(isinstance(s, dbs.PlaceSearch) for s in searches)
searches.sort(key=lambda s: s.penalty)
- assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \
- {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
- assert set((l.column, l.lookup_type.__name__) for l in searches[1].lookups) == \
- {('nameaddress_vector', 'LookupAll'), ('name_vector', 'LookupAll')}
+ assert set((s.column, s.lookup_type.__name__) for s in searches[0].lookups) == \
+ {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
+ assert set((s.column, s.lookup_type.__name__) for s in searches[1].lookups) == \
+ {('nameaddress_vector', 'LookupAll'), ('name_vector', 'LookupAll')}
def test_too_frequent_partials_in_name_and_address():
@@ -449,5 +447,5 @@ def test_too_frequent_partials_in_name_and_address():
assert all(isinstance(s, dbs.PlaceSearch) for s in searches)
searches.sort(key=lambda s: s.penalty)
- assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \
- {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
+ assert set((s.column, s.lookup_type.__name__) for s in searches[0].lookups) == \
+ {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
diff --git a/test/python/api/search/test_icu_query_analyzer.py b/test/python/api/search/test_icu_query_analyzer.py
index fc200bca..72535026 100644
--- a/test/python/api/search/test_icu_query_analyzer.py
+++ b/test/python/api/search/test_icu_query_analyzer.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for query analyzer for ICU tokenizer.
@@ -16,7 +16,8 @@ import nominatim_api.search.query as qmod
import nominatim_api.search.icu_tokenizer as tok
from nominatim_api.logging import set_log_output, get_and_disable
-async def add_word(conn, word_id, word_token, wtype, word, info = None):
+
+async def add_word(conn, word_id, word_token, wtype, word, info=None):
t = conn.t.meta.tables['word']
await conn.execute(t.insert(), {'word_id': word_id,
'word_token': word_token,
@@ -28,6 +29,7 @@ async def add_word(conn, word_id, word_token, wtype, word, info = None):
def make_phrase(query):
return [Phrase(qmod.PHRASE_ANY, s) for s in query.split(',')]
+
@pytest_asyncio.fixture
async def conn(table_factory):
""" Create an asynchronous SQLAlchemy engine for the test DB.
@@ -102,8 +104,7 @@ async def test_splitting_in_transliteration(conn):
@pytest.mark.asyncio
@pytest.mark.parametrize('term,order', [('23456', ['P', 'H', 'W', 'w']),
- ('3', ['H', 'W', 'w'])
- ])
+ ('3', ['H', 'W', 'w'])])
async def test_penalty_postcodes_and_housenumbers(conn, term, order):
ana = await tok.create_query_analyzer(conn)
@@ -120,6 +121,7 @@ async def test_penalty_postcodes_and_housenumbers(conn, term, order):
assert [t[1] for t in torder] == order
+
@pytest.mark.asyncio
async def test_category_words_only_at_beginning(conn):
ana = await tok.create_query_analyzer(conn)
diff --git a/test/python/api/search/test_postcode_parser.py b/test/python/api/search/test_postcode_parser.py
index 284aba5b..38638e07 100644
--- a/test/python/api/search/test_postcode_parser.py
+++ b/test/python/api/search/test_postcode_parser.py
@@ -16,6 +16,7 @@ import pytest
from nominatim_api.search.postcode_parser import PostcodeParser
from nominatim_api.search.query import QueryStruct, PHRASE_ANY, PHRASE_POSTCODE, PHRASE_STREET
+
@pytest.fixture
def pc_config(project_env):
country_file = project_env.project_dir / 'country_settings.yaml'
@@ -55,6 +56,7 @@ ky:
return project_env
+
def mk_query(inp):
query = QueryStruct([])
phrase_split = re.split(r"([ ,:'-])", inp)
@@ -80,6 +82,7 @@ def test_simple_postcode(pc_config, query, pos):
assert result == {(pos, pos + 1, '45325'), (pos, pos + 1, '453 25')}
+
def test_contained_postcode(pc_config):
parser = PostcodeParser(pc_config)
@@ -87,7 +90,6 @@ def test_contained_postcode(pc_config):
(0, 2, '12345 DX')}
-
@pytest.mark.parametrize('query,frm,to', [('345987', 0, 1), ('345 987', 0, 2),
('Aina 345 987', 1, 3),
('Aina 23 345 987 ff', 2, 4)])
@@ -98,6 +100,7 @@ def test_postcode_with_space(pc_config, query, frm, to):
assert result == {(frm, to, '345987')}
+
def test_overlapping_postcode(pc_config):
parser = PostcodeParser(pc_config)
@@ -131,6 +134,7 @@ def test_postcode_with_non_matching_country_prefix(pc_config):
assert not parser.parse(mk_query('ky12233'))
+
def test_postcode_inside_postcode_phrase(pc_config):
parser = PostcodeParser(pc_config)
diff --git a/test/python/api/search/test_query.py b/test/python/api/search/test_query.py
index bfed38df..09f25f8e 100644
--- a/test/python/api/search/test_query.py
+++ b/test/python/api/search/test_query.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test data types for search queries.
@@ -11,14 +11,15 @@ import pytest
import nominatim_api.search.query as nq
+
def test_token_range_equal():
assert nq.TokenRange(2, 3) == nq.TokenRange(2, 3)
assert not (nq.TokenRange(2, 3) != nq.TokenRange(2, 3))
@pytest.mark.parametrize('lop,rop', [((1, 2), (3, 4)),
- ((3, 4), (3, 5)),
- ((10, 12), (11, 12))])
+ ((3, 4), (3, 5)),
+ ((10, 12), (11, 12))])
def test_token_range_unequal(lop, rop):
assert not (nq.TokenRange(*lop) == nq.TokenRange(*rop))
assert nq.TokenRange(*lop) != nq.TokenRange(*rop)
@@ -28,17 +29,17 @@ def test_token_range_lt():
assert nq.TokenRange(1, 3) < nq.TokenRange(10, 12)
assert nq.TokenRange(5, 6) < nq.TokenRange(7, 8)
assert nq.TokenRange(1, 4) < nq.TokenRange(4, 5)
- assert not(nq.TokenRange(5, 6) < nq.TokenRange(5, 6))
- assert not(nq.TokenRange(10, 11) < nq.TokenRange(4, 5))
+ assert not (nq.TokenRange(5, 6) < nq.TokenRange(5, 6))
+ assert not (nq.TokenRange(10, 11) < nq.TokenRange(4, 5))
def test_token_rankge_gt():
assert nq.TokenRange(3, 4) > nq.TokenRange(1, 2)
assert nq.TokenRange(100, 200) > nq.TokenRange(10, 11)
assert nq.TokenRange(10, 11) > nq.TokenRange(4, 10)
- assert not(nq.TokenRange(5, 6) > nq.TokenRange(5, 6))
- assert not(nq.TokenRange(1, 2) > nq.TokenRange(3, 4))
- assert not(nq.TokenRange(4, 10) > nq.TokenRange(3, 5))
+ assert not (nq.TokenRange(5, 6) > nq.TokenRange(5, 6))
+ assert not (nq.TokenRange(1, 2) > nq.TokenRange(3, 4))
+ assert not (nq.TokenRange(4, 10) > nq.TokenRange(3, 5))
def test_token_range_unimplemented_ops():
@@ -58,8 +59,7 @@ def test_query_extract_words():
words = q.extract_words(base_penalty=1.0)
assert set(words.keys()) \
- == {'12', 'ab', 'hallo', '12 ab', 'ab 12', '12 ab 12'}
+ == {'12', 'ab', 'hallo', '12 ab', 'ab 12', '12 ab 12'}
assert sorted(words['12']) == [nq.TokenRange(0, 1, 1.0), nq.TokenRange(2, 3, 1.0)]
assert words['12 ab'] == [nq.TokenRange(0, 2, 1.1)]
assert words['hallo'] == [nq.TokenRange(3, 4, 1.0)]
-
diff --git a/test/python/api/search/test_query_analyzer_factory.py b/test/python/api/search/test_query_analyzer_factory.py
index 42220b55..933bdd1f 100644
--- a/test/python/api/search/test_query_analyzer_factory.py
+++ b/test/python/api/search/test_query_analyzer_factory.py
@@ -2,18 +2,17 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for query analyzer creation.
"""
-from pathlib import Path
-
import pytest
from nominatim_api.search.query_analyzer_factory import make_query_analyzer
from nominatim_api.search.icu_tokenizer import ICUQueryAnalyzer
+
@pytest.mark.asyncio
async def test_import_icu_tokenizer(table_factory, api):
table_factory('nominatim_properties',
diff --git a/test/python/api/search/test_search_country.py b/test/python/api/search/test_search_country.py
index 2109ecb0..46875a2c 100644
--- a/test/python/api/search/test_search_country.py
+++ b/test/python/api/search/test_search_country.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for running the country searcher.
@@ -48,6 +48,7 @@ def test_find_from_placex(apiobj, frontend):
assert results[0].place_id == 55
assert results[0].accuracy == 0.8
+
def test_find_from_fallback_countries(apiobj, frontend):
apiobj.add_country('ro', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
apiobj.add_country_name('ro', {'name': 'România'})
@@ -87,7 +88,6 @@ class TestCountryParameters:
apiobj.add_country('ro', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
apiobj.add_country_name('ro', {'name': 'România'})
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -100,7 +100,6 @@ class TestCountryParameters:
assert len(results) == 1
assert geom.name.lower() in results[0].geometry
-
@pytest.mark.parametrize('pid,rids', [(76, [55]), (55, [])])
def test_exclude_place_id(self, apiobj, frontend, pid, rids):
results = run_search(apiobj, frontend, 0.5, ['yw', 'ro'],
@@ -108,7 +107,6 @@ class TestCountryParameters:
assert [r.place_id for r in results] == rids
-
@pytest.mark.parametrize('viewbox,rids', [((9, 9, 11, 11), [55]),
((-10, -10, -3, -3), [])])
def test_bounded_viewbox_in_placex(self, apiobj, frontend, viewbox, rids):
@@ -118,9 +116,8 @@ class TestCountryParameters:
assert [r.place_id for r in results] == rids
-
@pytest.mark.parametrize('viewbox,numres', [((0, 0, 1, 1), 1),
- ((-10, -10, -3, -3), 0)])
+ ((-10, -10, -3, -3), 0)])
def test_bounded_viewbox_in_fallback(self, apiobj, frontend, viewbox, numres):
results = run_search(apiobj, frontend, 0.5, ['ro'],
details=SearchDetails.from_kwargs({'viewbox': viewbox,
diff --git a/test/python/api/search/test_search_near.py b/test/python/api/search/test_search_near.py
index 43098ddd..e9650168 100644
--- a/test/python/api/search/test_search_near.py
+++ b/test/python/api/search/test_search_near.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for running the near searcher.
@@ -12,8 +12,8 @@ import pytest
import nominatim_api as napi
from nominatim_api.types import SearchDetails
from nominatim_api.search.db_searches import NearSearch, PlaceSearch
-from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories,\
- FieldLookup, FieldRanking, RankedTokens
+from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories, \
+ FieldLookup
from nominatim_api.search.db_search_lookups import LookupAll
@@ -80,7 +80,6 @@ class TestNearSearch:
apiobj.add_search_name(101, names=[56], country_code='mx',
centroid=(-10.3, 56.9))
-
def test_near_in_placex(self, apiobj, frontend):
apiobj.add_placex(place_id=22, class_='amenity', type='bank',
centroid=(5.6001, 4.2994))
@@ -91,7 +90,6 @@ class TestNearSearch:
assert [r.place_id for r in results] == [22]
-
def test_multiple_types_near_in_placex(self, apiobj, frontend):
apiobj.add_placex(place_id=22, class_='amenity', type='bank',
importance=0.002,
@@ -105,7 +103,6 @@ class TestNearSearch:
assert [r.place_id for r in results] == [22, 23]
-
def test_near_in_classtype(self, apiobj, frontend):
apiobj.add_placex(place_id=22, class_='amenity', type='bank',
centroid=(5.6, 4.34))
@@ -118,7 +115,6 @@ class TestNearSearch:
assert [r.place_id for r in results] == [22]
-
@pytest.mark.parametrize('cc,rid', [('us', 22), ('mx', 23)])
def test_restrict_by_country(self, apiobj, frontend, cc, rid):
apiobj.add_placex(place_id=22, class_='amenity', type='bank',
@@ -138,7 +134,6 @@ class TestNearSearch:
assert [r.place_id for r in results] == [rid]
-
@pytest.mark.parametrize('excluded,rid', [(22, 122), (122, 22)])
def test_exclude_place_by_id(self, apiobj, frontend, excluded, rid):
apiobj.add_placex(place_id=22, class_='amenity', type='bank',
@@ -148,13 +143,11 @@ class TestNearSearch:
centroid=(5.6001, 4.2994),
country_code='us')
-
results = run_search(apiobj, frontend, 0.1, [('amenity', 'bank')],
details=SearchDetails(excluded=[excluded]))
assert [r.place_id for r in results] == [rid]
-
@pytest.mark.parametrize('layer,rids', [(napi.DataLayer.POI, [22]),
(napi.DataLayer.MANMADE, [])])
def test_with_layer(self, apiobj, frontend, layer, rids):
diff --git a/test/python/api/search/test_search_places.py b/test/python/api/search/test_search_places.py
index c6ff16b8..ed0722c3 100644
--- a/test/python/api/search/test_search_places.py
+++ b/test/python/api/search/test_search_places.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for running the generic place searcher.
@@ -14,12 +14,13 @@ import pytest
import nominatim_api as napi
from nominatim_api.types import SearchDetails
from nominatim_api.search.db_searches import PlaceSearch
-from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories,\
+from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories, \
FieldLookup, FieldRanking, RankedTokens
from nominatim_api.search.db_search_lookups import LookupAll, LookupAny, Restrict
APIOPTIONS = ['search']
+
def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2,
hnrs=[], pcs=[], ccodes=[], quals=[],
details=SearchDetails()):
@@ -55,29 +56,27 @@ class TestNameOnlySearches:
def fill_database(self, apiobj):
apiobj.add_placex(place_id=100, country_code='us',
centroid=(5.6, 4.3))
- apiobj.add_search_name(100, names=[1,2,10,11], country_code='us',
+ apiobj.add_search_name(100, names=[1, 2, 10, 11], country_code='us',
centroid=(5.6, 4.3))
apiobj.add_placex(place_id=101, country_code='mx',
centroid=(-10.3, 56.9))
- apiobj.add_search_name(101, names=[1,2,20,21], country_code='mx',
+ apiobj.add_search_name(101, names=[1, 2, 20, 21], country_code='mx',
centroid=(-10.3, 56.9))
-
@pytest.mark.parametrize('lookup_type', [LookupAll, Restrict])
@pytest.mark.parametrize('rank,res', [([10], [100, 101]),
([20], [101, 100])])
def test_lookup_all_match(self, apiobj, frontend, lookup_type, rank, res):
- lookup = FieldLookup('name_vector', [1,2], lookup_type)
+ lookup = FieldLookup('name_vector', [1, 2], lookup_type)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, rank)])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking])
assert [r.place_id for r in results] == res
-
@pytest.mark.parametrize('lookup_type', [LookupAll, Restrict])
def test_lookup_all_partial_match(self, apiobj, frontend, lookup_type):
- lookup = FieldLookup('name_vector', [1,20], lookup_type)
+ lookup = FieldLookup('name_vector', [1, 20], lookup_type)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking])
@@ -88,14 +87,13 @@ class TestNameOnlySearches:
@pytest.mark.parametrize('rank,res', [([10], [100, 101]),
([20], [101, 100])])
def test_lookup_any_match(self, apiobj, frontend, rank, res):
- lookup = FieldLookup('name_vector', [11,21], LookupAny)
+ lookup = FieldLookup('name_vector', [11, 21], LookupAny)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, rank)])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking])
assert [r.place_id for r in results] == res
-
def test_lookup_any_partial_match(self, apiobj, frontend):
lookup = FieldLookup('name_vector', [20], LookupAll)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])])
@@ -105,19 +103,17 @@ class TestNameOnlySearches:
assert len(results) == 1
assert results[0].place_id == 101
-
@pytest.mark.parametrize('cc,res', [('us', 100), ('mx', 101)])
def test_lookup_restrict_country(self, apiobj, frontend, cc, res):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], ccodes=[cc])
assert [r.place_id for r in results] == [res]
-
def test_lookup_restrict_placeid(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking],
@@ -125,7 +121,6 @@ class TestNameOnlySearches:
assert [r.place_id for r in results] == [100]
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -139,7 +134,6 @@ class TestNameOnlySearches:
assert geom.name.lower() in results[0].geometry
-
@pytest.mark.parametrize('factor,npoints', [(0.0, 3), (1.0, 2)])
def test_return_simplified_geometry(self, apiobj, frontend, factor, npoints):
apiobj.add_placex(place_id=333, country_code='us',
@@ -162,7 +156,6 @@ class TestNameOnlySearches:
assert result.place_id == 333
assert len(geom['coordinates']) == npoints
-
@pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.7,4.0,6.0,5.0'])
@pytest.mark.parametrize('wcount,rids', [(2, [100, 101]), (20000, [100])])
def test_prefer_viewbox(self, apiobj, frontend, viewbox, wcount, rids):
@@ -177,18 +170,16 @@ class TestNameOnlySearches:
details=SearchDetails.from_kwargs({'viewbox': viewbox}))
assert [r.place_id for r in results] == rids
-
@pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.55,4.27,5.62,4.31'])
def test_force_viewbox(self, apiobj, frontend, viewbox):
lookup = FieldLookup('name_vector', [1, 2], LookupAll)
- details=SearchDetails.from_kwargs({'viewbox': viewbox,
- 'bounded_viewbox': True})
+ details = SearchDetails.from_kwargs({'viewbox': viewbox,
+ 'bounded_viewbox': True})
results = run_search(apiobj, frontend, 0.1, [lookup], [], details=details)
assert [r.place_id for r in results] == [100]
-
def test_prefer_near(self, apiobj, frontend):
lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])])
@@ -202,13 +193,12 @@ class TestNameOnlySearches:
results.sort(key=lambda r: -r.importance)
assert [r.place_id for r in results] == [100, 101]
-
@pytest.mark.parametrize('radius', [0.09, 0.11])
def test_force_near(self, apiobj, frontend, radius):
lookup = FieldLookup('name_vector', [1, 2], LookupAll)
- details=SearchDetails.from_kwargs({'near': '5.6,4.3',
- 'near_radius': radius})
+ details = SearchDetails.from_kwargs({'near': '5.6,4.3',
+ 'near_radius': radius})
results = run_search(apiobj, frontend, 0.1, [lookup], [], details=details)
@@ -228,7 +218,7 @@ class TestStreetWithHousenumber:
apiobj.add_placex(place_id=1000, class_='highway', type='residential',
rank_search=26, rank_address=26,
country_code='es')
- apiobj.add_search_name(1000, names=[1,2,10,11],
+ apiobj.add_search_name(1000, names=[1, 2, 10, 11],
search_rank=26, address_rank=26,
country_code='es')
apiobj.add_placex(place_id=91, class_='place', type='house',
@@ -243,26 +233,24 @@ class TestStreetWithHousenumber:
apiobj.add_placex(place_id=2000, class_='highway', type='residential',
rank_search=26, rank_address=26,
country_code='pt')
- apiobj.add_search_name(2000, names=[1,2,20,21],
+ apiobj.add_search_name(2000, names=[1, 2, 20, 21],
search_rank=26, address_rank=26,
country_code='pt')
-
@pytest.mark.parametrize('hnr,res', [('20', [91, 1]), ('20 a', [1]),
('21', [2]), ('22', [2, 92]),
('24', [93]), ('25', [])])
def test_lookup_by_single_housenumber(self, apiobj, frontend, hnr, res):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=[hnr])
assert [r.place_id for r in results] == res + [1000, 2000]
-
@pytest.mark.parametrize('cc,res', [('es', [2, 1000]), ('pt', [92, 2000])])
def test_lookup_with_country_restriction(self, apiobj, frontend, cc, res):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -270,9 +258,8 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == res
-
def test_lookup_exclude_housenumber_placeid(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -280,9 +267,8 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == [2, 1000, 2000]
-
def test_lookup_exclude_street_placeid(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -290,9 +276,8 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == [2, 92, 2000]
-
def test_lookup_only_house_qualifier(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -300,9 +285,8 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == [2, 92]
-
def test_lookup_only_street_qualifier(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -310,10 +294,9 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == [1000, 2000]
-
@pytest.mark.parametrize('rank,found', [(26, True), (27, False), (30, False)])
def test_lookup_min_rank(self, apiobj, frontend, rank, found):
- lookup = FieldLookup('name_vector', [1,2], LookupAll)
+ lookup = FieldLookup('name_vector', [1, 2], LookupAll)
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -321,7 +304,6 @@ class TestStreetWithHousenumber:
assert [r.place_id for r in results] == ([2, 92, 1000, 2000] if found else [2, 92])
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -343,7 +325,7 @@ def test_very_large_housenumber(apiobj, frontend):
apiobj.add_placex(place_id=2000, class_='highway', type='residential',
rank_search=26, rank_address=26,
country_code='pt')
- apiobj.add_search_name(2000, names=[1,2],
+ apiobj.add_search_name(2000, names=[1, 2],
search_rank=26, address_rank=26,
country_code='pt')
@@ -405,7 +387,6 @@ class TestInterpolations:
centroid=(10.0, 10.00001),
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
-
@pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])])
def test_lookup_housenumber(self, apiobj, frontend, hnr, res):
lookup = FieldLookup('name_vector', [111], LookupAll)
@@ -414,7 +395,6 @@ class TestInterpolations:
assert [r.place_id for r in results] == res + [990]
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -429,7 +409,6 @@ class TestInterpolations:
assert geom.name.lower() in results[0].geometry
-
class TestTiger:
@pytest.fixture(autouse=True)
@@ -453,7 +432,6 @@ class TestTiger:
centroid=(10.0, 10.00001),
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
-
@pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])])
def test_lookup_housenumber(self, apiobj, frontend, hnr, res):
lookup = FieldLookup('name_vector', [111], LookupAll)
@@ -462,7 +440,6 @@ class TestTiger:
assert [r.place_id for r in results] == res + [990]
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -513,15 +490,15 @@ class TestLayersRank30:
importance=0.0005,
address_rank=0, search_rank=30)
-
- @pytest.mark.parametrize('layer,res', [(napi.DataLayer.ADDRESS, [223]),
- (napi.DataLayer.POI, [224]),
- (napi.DataLayer.ADDRESS | napi.DataLayer.POI, [223, 224]),
- (napi.DataLayer.MANMADE, [225]),
- (napi.DataLayer.RAILWAY, [226]),
- (napi.DataLayer.NATURAL, [227]),
- (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, [225, 227]),
- (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, [225, 226])])
+ @pytest.mark.parametrize('layer,res',
+ [(napi.DataLayer.ADDRESS, [223]),
+ (napi.DataLayer.POI, [224]),
+ (napi.DataLayer.ADDRESS | napi.DataLayer.POI, [223, 224]),
+ (napi.DataLayer.MANMADE, [225]),
+ (napi.DataLayer.RAILWAY, [226]),
+ (napi.DataLayer.NATURAL, [227]),
+ (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, [225, 227]),
+ (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, [225, 226])])
def test_layers_rank30(self, apiobj, frontend, layer, res):
lookup = FieldLookup('name_vector', [34], LookupAny)
diff --git a/test/python/api/search/test_search_poi.py b/test/python/api/search/test_search_poi.py
index d4319a57..9387385e 100644
--- a/test/python/api/search/test_search_poi.py
+++ b/test/python/api/search/test_search_poi.py
@@ -2,14 +2,13 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for running the POI searcher.
"""
import pytest
-import nominatim_api as napi
from nominatim_api.types import SearchDetails
from nominatim_api.search.db_searches import PoiSearch
from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories
@@ -84,14 +83,12 @@ class TestPoiSearchWithRestrictions:
else:
self.args = {'near': '34.3, 56.100021', 'near_radius': 0.001}
-
def test_unrestricted(self, apiobj, frontend):
results = run_search(apiobj, frontend, 0.1, [('highway', 'bus_stop')], [0.5],
details=SearchDetails.from_kwargs(self.args))
assert [r.place_id for r in results] == [1, 2]
-
def test_restict_country(self, apiobj, frontend):
results = run_search(apiobj, frontend, 0.1, [('highway', 'bus_stop')], [0.5],
ccodes=['de', 'nz'],
@@ -99,7 +96,6 @@ class TestPoiSearchWithRestrictions:
assert [r.place_id for r in results] == [2]
-
def test_restrict_by_viewbox(self, apiobj, frontend):
args = {'bounded_viewbox': True, 'viewbox': '34.299,56.0,34.3001,56.10001'}
args.update(self.args)
diff --git a/test/python/api/search/test_search_postcode.py b/test/python/api/search/test_search_postcode.py
index 369e1504..529fb409 100644
--- a/test/python/api/search/test_search_postcode.py
+++ b/test/python/api/search/test_search_postcode.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for running the postcode searcher.
@@ -15,6 +15,7 @@ from nominatim_api.search.db_searches import PostcodeSearch
from nominatim_api.search.db_search_fields import WeightedStrings, FieldLookup, \
FieldRanking, RankedTokens
+
def run_search(apiobj, frontend, global_penalty, pcs, pc_penalties=None,
ccodes=[], lookup=[], ranking=[], details=SearchDetails()):
if pc_penalties is None:
@@ -85,26 +86,24 @@ class TestPostcodeSearchWithAddress:
apiobj.add_placex(place_id=1000, class_='place', type='village',
rank_search=22, rank_address=22,
country_code='ch')
- apiobj.add_search_name(1000, names=[1,2,10,11],
+ apiobj.add_search_name(1000, names=[1, 2, 10, 11],
search_rank=22, address_rank=22,
country_code='ch')
apiobj.add_placex(place_id=2000, class_='place', type='village',
rank_search=22, rank_address=22,
country_code='pl')
- apiobj.add_search_name(2000, names=[1,2,20,21],
+ apiobj.add_search_name(2000, names=[1, 2, 20, 21],
search_rank=22, address_rank=22,
country_code='pl')
-
def test_lookup_both(self, apiobj, frontend):
- lookup = FieldLookup('name_vector', [1,2], 'restrict')
+ lookup = FieldLookup('name_vector', [1, 2], 'restrict')
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, ['12345'], lookup=[lookup], ranking=[ranking])
assert [r.place_id for r in results] == [100, 101]
-
def test_restrict_by_name(self, apiobj, frontend):
lookup = FieldLookup('name_vector', [10], 'restrict')
@@ -112,11 +111,10 @@ class TestPostcodeSearchWithAddress:
assert [r.place_id for r in results] == [100]
-
@pytest.mark.parametrize('coord,place_id', [((16.5, 5), 100),
((-45.1, 7.004), 101)])
def test_lookup_near(self, apiobj, frontend, coord, place_id):
- lookup = FieldLookup('name_vector', [1,2], 'restrict')
+ lookup = FieldLookup('name_vector', [1, 2], 'restrict')
ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
results = run_search(apiobj, frontend, 0.1, ['12345'],
@@ -126,7 +124,6 @@ class TestPostcodeSearchWithAddress:
assert [r.place_id for r in results] == [place_id]
-
@pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
napi.GeometryFormat.KML,
napi.GeometryFormat.SVG,
@@ -138,18 +135,16 @@ class TestPostcodeSearchWithAddress:
assert results
assert all(geom.name.lower() in r.geometry for r in results)
-
- @pytest.mark.parametrize('viewbox, rids', [('-46,6,-44,8', [101,100]),
- ('16,4,18,6', [100,101])])
+ @pytest.mark.parametrize('viewbox, rids', [('-46,6,-44,8', [101, 100]),
+ ('16,4,18,6', [100, 101])])
def test_prefer_viewbox(self, apiobj, frontend, viewbox, rids):
results = run_search(apiobj, frontend, 0.1, ['12345'],
details=SearchDetails.from_kwargs({'viewbox': viewbox}))
assert [r.place_id for r in results] == rids
-
@pytest.mark.parametrize('viewbox, rid', [('-46,6,-44,8', 101),
- ('16,4,18,6', 100)])
+ ('16,4,18,6', 100)])
def test_restrict_to_viewbox(self, apiobj, frontend, viewbox, rid):
results = run_search(apiobj, frontend, 0.1, ['12345'],
details=SearchDetails.from_kwargs({'viewbox': viewbox,
@@ -157,7 +152,6 @@ class TestPostcodeSearchWithAddress:
assert [r.place_id for r in results] == [rid]
-
@pytest.mark.parametrize('coord,rids', [((17.05, 5), [100, 101]),
((-45, 7.1), [101, 100])])
def test_prefer_near(self, apiobj, frontend, coord, rids):
@@ -166,7 +160,6 @@ class TestPostcodeSearchWithAddress:
assert [r.place_id for r in results] == rids
-
@pytest.mark.parametrize('pid,rid', [(100, 101), (101, 100)])
def test_exclude(self, apiobj, frontend, pid, rid):
results = run_search(apiobj, frontend, 0.1, ['12345'],
diff --git a/test/python/api/search/test_token_assignment.py b/test/python/api/search/test_token_assignment.py
index fff8d471..2ffba335 100644
--- a/test/python/api/search/test_token_assignment.py
+++ b/test/python/api/search/test_token_assignment.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for creation of token assignments from tokenized queries.
@@ -11,7 +11,10 @@ import pytest
from nominatim_api.search.query import QueryStruct, Phrase, TokenRange, Token
import nominatim_api.search.query as qmod
-from nominatim_api.search.token_assignment import yield_token_assignments, TokenAssignment, PENALTY_TOKENCHANGE
+from nominatim_api.search.token_assignment import (yield_token_assignments,
+ TokenAssignment,
+ PENALTY_TOKENCHANGE)
+
class MyToken(Token):
def get_category(self):
@@ -102,8 +105,7 @@ def test_multiple_simple_words(btype):
TokenAssignment(penalty=penalty, name=TokenRange(1, 3),
address=[TokenRange(0, 1)]),
TokenAssignment(penalty=penalty, name=TokenRange(2, 3),
- address=[TokenRange(0, 2)])
- )
+ address=[TokenRange(0, 2)]))
def test_multiple_words_respect_phrase_break():
@@ -156,6 +158,7 @@ def test_housenumber_and_postcode():
address=[TokenRange(0, 1), TokenRange(2, 3)],
postcode=TokenRange(3, 4)))
+
def test_postcode_and_housenumber():
q = make_query((qmod.BREAK_START, qmod.PHRASE_ANY, [(1, qmod.TOKEN_PARTIAL)]),
(qmod.BREAK_WORD, qmod.PHRASE_ANY, [(2, qmod.TOKEN_POSTCODE)]),
@@ -211,11 +214,11 @@ def test_housenumber_many_phrases():
check_assignments(yield_token_assignments(q),
TokenAssignment(penalty=0.1,
name=TokenRange(4, 5),
- housenumber=TokenRange(3, 4),\
+ housenumber=TokenRange(3, 4),
address=[TokenRange(0, 1), TokenRange(1, 2),
TokenRange(2, 3)]),
TokenAssignment(penalty=0.1,
- housenumber=TokenRange(3, 4),\
+ housenumber=TokenRange(3, 4),
address=[TokenRange(0, 1), TokenRange(1, 2),
TokenRange(2, 3), TokenRange(4, 5)]))
@@ -299,7 +302,6 @@ def test_qualifier_at_beginning():
(qmod.BREAK_WORD, qmod.PHRASE_ANY, [(2, qmod.TOKEN_PARTIAL)]),
(qmod.BREAK_WORD, qmod.PHRASE_ANY, [(3, qmod.TOKEN_PARTIAL)]))
-
check_assignments(yield_token_assignments(q),
TokenAssignment(penalty=0.1, name=TokenRange(1, 3),
qualifier=TokenRange(0, 1)),
@@ -315,7 +317,6 @@ def test_qualifier_after_name():
(qmod.BREAK_WORD, qmod.PHRASE_ANY, [(4, qmod.TOKEN_PARTIAL)]),
(qmod.BREAK_WORD, qmod.PHRASE_ANY, [(5, qmod.TOKEN_PARTIAL)]))
-
check_assignments(yield_token_assignments(q),
TokenAssignment(penalty=0.2, name=TokenRange(0, 2),
qualifier=TokenRange(2, 3),
@@ -349,4 +350,3 @@ def test_qualifier_in_middle_of_phrase():
(qmod.BREAK_PHRASE, qmod.PHRASE_ANY, [(5, qmod.TOKEN_PARTIAL)]))
check_assignments(yield_token_assignments(q))
-
diff --git a/test/python/api/test_api_connection.py b/test/python/api/test_api_connection.py
index f62b0d9e..9b29411a 100644
--- a/test/python/api/test_api_connection.py
+++ b/test/python/api/test_api_connection.py
@@ -2,12 +2,11 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for enhanced connection class for API functions.
"""
-from pathlib import Path
import pytest
import sqlalchemy as sa
@@ -76,7 +75,7 @@ async def test_get_db_property_existing(api):
@pytest.mark.asyncio
-async def test_get_db_property_existing(api):
+async def test_get_db_property_bad_name(api):
async with api.begin() as conn:
with pytest.raises(ValueError):
await conn.get_db_property('dfkgjd.rijg')
diff --git a/test/python/api/test_api_deletable_v1.py b/test/python/api/test_api_deletable_v1.py
index 9e113886..8ea4c9cd 100644
--- a/test/python/api/test_api_deletable_v1.py
+++ b/test/python/api/test_api_deletable_v1.py
@@ -2,20 +2,20 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the deletable v1 API call.
"""
import json
-from pathlib import Path
import pytest
-from fake_adaptor import FakeAdaptor, FakeError, FakeResponse
+from fake_adaptor import FakeAdaptor
import nominatim_api.v1.server_glue as glue
+
class TestDeletableEndPoint:
@pytest.fixture(autouse=True)
@@ -25,14 +25,13 @@ class TestDeletableEndPoint:
content=[(345, 'N', 'boundary', 'administrative'),
(781, 'R', 'landuse', 'wood'),
(781, 'R', 'landcover', 'grass')])
- table_factory('placex',
- definition="""place_id bigint, osm_id bigint, osm_type char(1),
- class text, type text, name HSTORE, country_code char(2)""",
- content=[(1, 345, 'N', 'boundary', 'administrative', {'old_name': 'Former'}, 'ab'),
- (2, 781, 'R', 'landuse', 'wood', {'name': 'Wood'}, 'cd'),
- (3, 781, 'R', 'landcover', 'grass', None, 'cd')])
-
-
+ table_factory(
+ 'placex',
+ definition="""place_id bigint, osm_id bigint, osm_type char(1),
+ class text, type text, name HSTORE, country_code char(2)""",
+ content=[(1, 345, 'N', 'boundary', 'administrative', {'old_name': 'Former'}, 'ab'),
+ (2, 781, 'R', 'landuse', 'wood', {'name': 'Wood'}, 'cd'),
+ (3, 781, 'R', 'landcover', 'grass', None, 'cd')])
@pytest.mark.asyncio
async def test_deletable(self, api):
diff --git a/test/python/api/test_api_details.py b/test/python/api/test_api_details.py
index 7f405728..4f6dd92b 100644
--- a/test/python/api/test_api_details.py
+++ b/test/python/api/test_api_details.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for details API call.
@@ -13,23 +13,24 @@ import pytest
import nominatim_api as napi
+
@pytest.mark.parametrize('idobj', (napi.PlaceID(332), napi.OsmID('W', 4),
napi.OsmID('W', 4, 'highway')))
def test_lookup_in_placex(apiobj, frontend, idobj):
import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- name={'name': 'Road'}, address={'city': 'Barrow'},
- extratags={'surface': 'paved'},
- parent_place_id=34, linked_place_id=55,
- admin_level=15, country_code='gb',
- housenumber='4',
- postcode='34425', wikipedia='en:Faa',
- rank_search=27, rank_address=26,
- importance=0.01,
- centroid=(23, 34),
- indexed_date=import_date,
- geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
+ class_='highway', type='residential',
+ name={'name': 'Road'}, address={'city': 'Barrow'},
+ extratags={'surface': 'paved'},
+ parent_place_id=34, linked_place_id=55,
+ admin_level=15, country_code='gb',
+ housenumber='4',
+ postcode='34425', wikipedia='en:Faa',
+ rank_search=27, rank_address=26,
+ importance=0.01,
+ centroid=(23, 34),
+ indexed_date=import_date,
+ geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
api = frontend(apiobj, options={'details'})
result = api.details(idobj)
@@ -73,12 +74,12 @@ def test_lookup_in_placex(apiobj, frontend, idobj):
def test_lookup_in_placex_minimal_info(apiobj, frontend):
import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0)
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- admin_level=15,
- rank_search=27, rank_address=26,
- centroid=(23, 34),
- indexed_date=import_date,
- geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
+ class_='highway', type='residential',
+ admin_level=15,
+ rank_search=27, rank_address=26,
+ centroid=(23, 34),
+ indexed_date=import_date,
+ geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
api = frontend(apiobj, options={'details'})
result = api.details(napi.PlaceID(332))
@@ -131,9 +132,9 @@ def test_lookup_in_placex_with_geometry(apiobj, frontend):
def test_lookup_placex_with_address_details(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl',
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl',
+ rank_search=27, rank_address=26)
apiobj.add_address_placex(332, fromarea=False, isaddress=False,
distance=0.0034,
place_id=1000, osm_type='N', osm_id=3333,
@@ -178,9 +179,9 @@ def test_lookup_placex_with_address_details(apiobj, frontend):
def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl', linked_place_id=45,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', linked_place_id=45,
+ rank_search=27, rank_address=26)
api = frontend(apiobj, options={'details'})
result = api.details(napi.PlaceID(332), linked_places=True)
@@ -190,17 +191,17 @@ def test_lookup_place_with_linked_places_none_existing(apiobj, frontend):
def test_lookup_place_with_linked_places_existing(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl', linked_place_id=45,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', linked_place_id=45,
+ rank_search=27, rank_address=26)
apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5,
- class_='highway', type='residential', name='Street',
- country_code='pl', linked_place_id=332,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', linked_place_id=332,
+ rank_search=27, rank_address=26)
apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
- class_='highway', type='residential', name='Street',
- country_code='pl', linked_place_id=332,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', linked_place_id=332,
+ rank_search=27, rank_address=26)
api = frontend(apiobj, options={'details'})
result = api.details(napi.PlaceID(332), linked_places=True)
@@ -221,9 +222,9 @@ def test_lookup_place_with_linked_places_existing(apiobj, frontend):
def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl', parent_place_id=45,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', parent_place_id=45,
+ rank_search=27, rank_address=26)
api = frontend(apiobj, options={'details'})
result = api.details(napi.PlaceID(332), parented_places=True)
@@ -233,17 +234,17 @@ def test_lookup_place_with_parented_places_not_existing(apiobj, frontend):
def test_lookup_place_with_parented_places_existing(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl', parent_place_id=45,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', parent_place_id=45,
+ rank_search=27, rank_address=26)
apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5,
- class_='place', type='house', housenumber='23',
- country_code='pl', parent_place_id=332,
- rank_search=30, rank_address=30)
+ class_='place', type='house', housenumber='23',
+ country_code='pl', parent_place_id=332,
+ rank_search=30, rank_address=30)
apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6,
- class_='highway', type='residential', name='Street',
- country_code='pl', parent_place_id=332,
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', parent_place_id=332,
+ rank_search=27, rank_address=26)
api = frontend(apiobj, options={'details'})
result = api.details(napi.PlaceID(332), parented_places=True)
@@ -332,9 +333,9 @@ def test_lookup_osmline_with_address_details(apiobj, frontend):
startnumber=2, endnumber=4, step=1,
parent_place_id=332)
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl',
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl',
+ rank_search=27, rank_address=26)
apiobj.add_address_placex(332, fromarea=False, isaddress=False,
distance=0.0034,
place_id=1000, osm_type='N', osm_id=3333,
@@ -432,9 +433,9 @@ def test_lookup_tiger_with_address_details(apiobj, frontend):
startnumber=2, endnumber=4, step=1,
parent_place_id=332)
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='us',
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='us',
+ rank_search=27, rank_address=26)
apiobj.add_address_placex(332, fromarea=False, isaddress=False,
distance=0.0034,
place_id=1000, osm_type='N', osm_id=3333,
@@ -571,6 +572,7 @@ def test_lookup_postcode_with_address_details(apiobj, frontend):
rank_address=4, distance=0.0)
]
+
@pytest.mark.parametrize('objid', [napi.PlaceID(1736),
napi.OsmID('W', 55),
napi.OsmID('N', 55, 'amenity')])
@@ -583,8 +585,8 @@ def test_lookup_missing_object(apiobj, frontend, objid):
@pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
- napi.GeometryFormat.SVG,
- napi.GeometryFormat.TEXT))
+ napi.GeometryFormat.SVG,
+ napi.GeometryFormat.TEXT))
def test_lookup_unsupported_geometry(apiobj, frontend, gtype):
apiobj.add_placex(place_id=332)
diff --git a/test/python/api/test_api_lookup.py b/test/python/api/test_api_lookup.py
index 4281cd6c..a2660f51 100644
--- a/test/python/api/test_api_lookup.py
+++ b/test/python/api/test_api_lookup.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for lookup API call.
@@ -13,6 +13,7 @@ import pytest
import nominatim_api as napi
+
def test_lookup_empty_list(apiobj, frontend):
api = frontend(apiobj, options={'details'})
assert api.lookup([]) == []
@@ -28,17 +29,17 @@ def test_lookup_non_existing(apiobj, frontend):
napi.OsmID('W', 4, 'highway')))
def test_lookup_single_placex(apiobj, frontend, idobj):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- name={'name': 'Road'}, address={'city': 'Barrow'},
- extratags={'surface': 'paved'},
- parent_place_id=34, linked_place_id=55,
- admin_level=15, country_code='gb',
- housenumber='4',
- postcode='34425', wikipedia='en:Faa',
- rank_search=27, rank_address=26,
- importance=0.01,
- centroid=(23, 34),
- geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
+ class_='highway', type='residential',
+ name={'name': 'Road'}, address={'city': 'Barrow'},
+ extratags={'surface': 'paved'},
+ parent_place_id=34, linked_place_id=55,
+ admin_level=15, country_code='gb',
+ housenumber='4',
+ postcode='34425', wikipedia='en:Faa',
+ rank_search=27, rank_address=26,
+ importance=0.01,
+ centroid=(23, 34),
+ geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
api = frontend(apiobj, options={'details'})
result = api.lookup([idobj])
@@ -79,17 +80,17 @@ def test_lookup_single_placex(apiobj, frontend, idobj):
def test_lookup_multiple_places(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- name={'name': 'Road'}, address={'city': 'Barrow'},
- extratags={'surface': 'paved'},
- parent_place_id=34, linked_place_id=55,
- admin_level=15, country_code='gb',
- housenumber='4',
- postcode='34425', wikipedia='en:Faa',
- rank_search=27, rank_address=26,
- importance=0.01,
- centroid=(23, 34),
- geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
+ class_='highway', type='residential',
+ name={'name': 'Road'}, address={'city': 'Barrow'},
+ extratags={'surface': 'paved'},
+ parent_place_id=34, linked_place_id=55,
+ admin_level=15, country_code='gb',
+ housenumber='4',
+ postcode='34425', wikipedia='en:Faa',
+ rank_search=27, rank_address=26,
+ importance=0.01,
+ centroid=(23, 34),
+ geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
apiobj.add_osmline(place_id=4924, osm_id=9928,
parent_place_id=12,
startnumber=1, endnumber=4, step=1,
@@ -97,7 +98,6 @@ def test_lookup_multiple_places(apiobj, frontend):
address={'city': 'Big'},
geometry='LINESTRING(23 34, 23 35)')
-
api = frontend(apiobj, options={'details'})
result = api.lookup((napi.OsmID('W', 1),
napi.OsmID('W', 4),
@@ -111,17 +111,17 @@ def test_lookup_multiple_places(apiobj, frontend):
@pytest.mark.parametrize('gtype', list(napi.GeometryFormat))
def test_simple_place_with_geometry(apiobj, frontend, gtype):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- name={'name': 'Road'}, address={'city': 'Barrow'},
- extratags={'surface': 'paved'},
- parent_place_id=34, linked_place_id=55,
- admin_level=15, country_code='gb',
- housenumber='4',
- postcode='34425', wikipedia='en:Faa',
- rank_search=27, rank_address=26,
- importance=0.01,
- centroid=(23, 34),
- geometry='POLYGON((23 34, 23.1 34, 23.1 34.1, 23 34))')
+ class_='highway', type='residential',
+ name={'name': 'Road'}, address={'city': 'Barrow'},
+ extratags={'surface': 'paved'},
+ parent_place_id=34, linked_place_id=55,
+ admin_level=15, country_code='gb',
+ housenumber='4',
+ postcode='34425', wikipedia='en:Faa',
+ rank_search=27, rank_address=26,
+ importance=0.01,
+ centroid=(23, 34),
+ geometry='POLYGON((23 34, 23.1 34, 23.1 34.1, 23 34))')
api = frontend(apiobj, options={'details'})
result = api.lookup([napi.OsmID('W', 4)], geometry_output=gtype)
@@ -137,17 +137,17 @@ def test_simple_place_with_geometry(apiobj, frontend, gtype):
def test_simple_place_with_geometry_simplified(apiobj, frontend):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential',
- name={'name': 'Road'}, address={'city': 'Barrow'},
- extratags={'surface': 'paved'},
- parent_place_id=34, linked_place_id=55,
- admin_level=15, country_code='gb',
- housenumber='4',
- postcode='34425', wikipedia='en:Faa',
- rank_search=27, rank_address=26,
- importance=0.01,
- centroid=(23, 34),
- geometry='POLYGON((23 34, 22.999 34, 23.1 34, 23.1 34.1, 23 34))')
+ class_='highway', type='residential',
+ name={'name': 'Road'}, address={'city': 'Barrow'},
+ extratags={'surface': 'paved'},
+ parent_place_id=34, linked_place_id=55,
+ admin_level=15, country_code='gb',
+ housenumber='4',
+ postcode='34425', wikipedia='en:Faa',
+ rank_search=27, rank_address=26,
+ importance=0.01,
+ centroid=(23, 34),
+ geometry='POLYGON((23 34, 22.999 34, 23.1 34, 23.1 34.1, 23 34))')
api = frontend(apiobj, options={'details'})
result = api.lookup([napi.OsmID('W', 4)],
@@ -159,5 +159,5 @@ def test_simple_place_with_geometry_simplified(apiobj, frontend):
geom = json.loads(result[0].geometry['geojson'])
- assert geom['type'] == 'Polygon'
+ assert geom['type'] == 'Polygon'
assert geom['coordinates'] == [[[23, 34], [23.1, 34], [23.1, 34.1], [23, 34]]]
diff --git a/test/python/api/test_api_polygons_v1.py b/test/python/api/test_api_polygons_v1.py
index ac2b4cb9..e4700a95 100644
--- a/test/python/api/test_api_polygons_v1.py
+++ b/test/python/api/test_api_polygons_v1.py
@@ -2,21 +2,21 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the deletable v1 API call.
"""
import json
import datetime as dt
-from pathlib import Path
import pytest
-from fake_adaptor import FakeAdaptor, FakeError, FakeResponse
+from fake_adaptor import FakeAdaptor
import nominatim_api.v1.server_glue as glue
+
class TestPolygonsEndPoint:
@pytest.fixture(autouse=True)
@@ -35,13 +35,12 @@ class TestPolygonsEndPoint:
errormessage text,
prevgeometry geometry(Geometry,4326),
newgeometry geometry(Geometry,4326)""",
- content=[(345, 'N', 'boundary', 'administrative',
- {'name': 'Foo'}, 'xx', self.recent,
- 'some text', None, None),
- (781, 'R', 'landuse', 'wood',
- None, 'ds', self.now,
- 'Area reduced by lots', None, None)])
-
+ content=[(345, 'N', 'boundary', 'administrative',
+ {'name': 'Foo'}, 'xx', self.recent,
+ 'some text', None, None),
+ (781, 'R', 'landuse', 'wood',
+ None, 'ds', self.now,
+ 'Area reduced by lots', None, None)])
@pytest.mark.asyncio
async def test_polygons_simple(self, api):
@@ -63,7 +62,6 @@ class TestPolygonsEndPoint:
'errormessage': 'Area reduced by lots',
'updated': self.now.isoformat(sep=' ', timespec='seconds')}]
-
@pytest.mark.asyncio
async def test_polygons_days(self, api):
a = FakeAdaptor()
@@ -74,7 +72,6 @@ class TestPolygonsEndPoint:
assert [r['osm_id'] for r in results] == [781]
-
@pytest.mark.asyncio
async def test_polygons_class(self, api):
a = FakeAdaptor()
@@ -85,8 +82,6 @@ class TestPolygonsEndPoint:
assert [r['osm_id'] for r in results] == [781]
-
-
@pytest.mark.asyncio
async def test_polygons_reduced(self, api):
a = FakeAdaptor()
diff --git a/test/python/api/test_api_reverse.py b/test/python/api/test_api_reverse.py
index ff7f402b..91074ecb 100644
--- a/test/python/api/test_api_reverse.py
+++ b/test/python/api/test_api_reverse.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for reverse API call.
@@ -18,6 +18,7 @@ import nominatim_api as napi
API_OPTIONS = {'reverse'}
+
def test_reverse_rank_30(apiobj, frontend):
apiobj.add_placex(place_id=223, class_='place', type='house',
housenumber='1',
@@ -35,7 +36,7 @@ def test_reverse_rank_30(apiobj, frontend):
def test_reverse_street(apiobj, frontend, country):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
country_code=country,
geometry='LINESTRING(9.995 10, 10.005 10)')
@@ -57,16 +58,17 @@ def test_reverse_ignore_unindexed(apiobj, frontend):
assert result is None
-@pytest.mark.parametrize('y,layer,place_id', [(0.7, napi.DataLayer.ADDRESS, 223),
- (0.70001, napi.DataLayer.POI, 224),
- (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224),
- (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223),
- (0.7, napi.DataLayer.MANMADE, 225),
- (0.7, napi.DataLayer.RAILWAY, 226),
- (0.7, napi.DataLayer.NATURAL, 227),
- (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
- (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225),
- (5, napi.DataLayer.ADDRESS, 229)])
+@pytest.mark.parametrize('y,layer,place_id',
+ [(0.7, napi.DataLayer.ADDRESS, 223),
+ (0.70001, napi.DataLayer.POI, 224),
+ (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224),
+ (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223),
+ (0.7, napi.DataLayer.MANMADE, 225),
+ (0.7, napi.DataLayer.RAILWAY, 226),
+ (0.7, napi.DataLayer.NATURAL, 227),
+ (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225),
+ (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225),
+ (5, napi.DataLayer.ADDRESS, 229)])
def test_reverse_rank_30_layers(apiobj, frontend, y, layer, place_id):
apiobj.add_placex(place_id=223, osm_type='N', class_='place', type='house',
housenumber='1',
@@ -108,14 +110,14 @@ def test_reverse_poi_layer_with_no_pois(apiobj, frontend):
api = frontend(apiobj, options=API_OPTIONS)
assert api.reverse((1.3, 0.70001), max_rank=29,
- layers=napi.DataLayer.POI) is None
+ layers=napi.DataLayer.POI) is None
@pytest.mark.parametrize('with_geom', [True, False])
def test_reverse_housenumber_on_street(apiobj, frontend, with_geom):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
geometry='LINESTRING(9.995 10, 10.005 10)')
apiobj.add_placex(place_id=991, class_='place', type='house',
@@ -125,7 +127,7 @@ def test_reverse_housenumber_on_street(apiobj, frontend, with_geom):
centroid=(10.0, 10.00001))
apiobj.add_placex(place_id=1990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'Other Street'},
+ name={'name': 'Other Street'},
centroid=(10.0, 1.0),
geometry='LINESTRING(9.995 1, 10.005 1)')
apiobj.add_placex(place_id=1991, class_='place', type='house',
@@ -147,7 +149,7 @@ def test_reverse_housenumber_on_street(apiobj, frontend, with_geom):
def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
geometry='LINESTRING(9.995 10, 10.005 10)')
apiobj.add_placex(place_id=991, class_='place', type='house',
@@ -162,7 +164,7 @@ def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom):
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
apiobj.add_placex(place_id=1990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'Other Street'},
+ name={'name': 'Other Street'},
centroid=(10.0, 20.0),
geometry='LINESTRING(9.995 20, 10.005 20)')
apiobj.add_osmline(place_id=1992,
@@ -181,7 +183,7 @@ def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom):
def test_reverse_housenumber_point_interpolation(apiobj, frontend):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
geometry='LINESTRING(9.995 10, 10.005 10)')
apiobj.add_osmline(place_id=992,
@@ -199,7 +201,7 @@ def test_reverse_housenumber_point_interpolation(apiobj, frontend):
def test_reverse_tiger_number(apiobj, frontend):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
country_code='us',
geometry='LINESTRING(9.995 10, 10.005 10)')
@@ -217,7 +219,7 @@ def test_reverse_tiger_number(apiobj, frontend):
def test_reverse_point_tiger(apiobj, frontend):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
country_code='us',
geometry='LINESTRING(9.995 10, 10.005 10)')
@@ -393,14 +395,15 @@ def test_reverse_interpolation_geometry(apiobj, frontend):
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
api = frontend(apiobj, options=API_OPTIONS)
- assert api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)\
- .geometry['text'] == 'POINT(10 10.00001)'
+ result = api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)
+
+ assert result.geometry['text'] == 'POINT(10 10.00001)'
def test_reverse_tiger_geometry(apiobj, frontend):
apiobj.add_placex(place_id=990, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(10.0, 10.0),
country_code='us',
geometry='LINESTRING(9.995 10, 10.005 10)')
@@ -411,7 +414,7 @@ def test_reverse_tiger_geometry(apiobj, frontend):
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
apiobj.add_placex(place_id=1000, class_='highway', type='service',
rank_search=27, rank_address=27,
- name = {'name': 'My Street'},
+ name={'name': 'My Street'},
centroid=(11.0, 11.0),
country_code='us',
geometry='LINESTRING(10.995 11, 11.005 11)')
@@ -426,8 +429,9 @@ def test_reverse_tiger_geometry(apiobj, frontend):
params = {'geometry_output': napi.GeometryFormat.GEOJSON}
output = api.reverse((10.0, 10.0), **params)
- assert json.loads(output.geometry['geojson']) == {'coordinates': [10, 10.00001], 'type': 'Point'}
+ assert json.loads(output.geometry['geojson']) \
+ == {'coordinates': [10, 10.00001], 'type': 'Point'}
output = api.reverse((11.0, 11.0), **params)
- assert json.loads(output.geometry['geojson']) == {'coordinates': [11, 11.00001], 'type': 'Point'}
-
+ assert json.loads(output.geometry['geojson']) \
+ == {'coordinates': [11, 11.00001], 'type': 'Point'}
diff --git a/test/python/api/test_api_search.py b/test/python/api/test_api_search.py
index 54138e24..59a83aa9 100644
--- a/test/python/api/test_api_search.py
+++ b/test/python/api/test_api_search.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for search API calls.
@@ -10,17 +10,13 @@ Tests for search API calls.
These tests make sure that all Python code is correct and executable.
Functional tests can be found in the BDD test suite.
"""
-import json
-
import pytest
-import sqlalchemy as sa
-
-import nominatim_api as napi
import nominatim_api.logging as loglib
API_OPTIONS = {'search'}
+
@pytest.fixture(autouse=True)
def setup_icu_tokenizer(apiobj):
""" Setup the properties needed for using the ICU tokenizer.
@@ -28,8 +24,9 @@ def setup_icu_tokenizer(apiobj):
apiobj.add_data('properties',
[{'property': 'tokenizer', 'value': 'icu'},
{'property': 'tokenizer_import_normalisation', 'value': ':: lower();'},
- {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"},
- ])
+ {'property': 'tokenizer_import_transliteration',
+ 'value': "'1' > '/1/'; 'ä' > 'ä '"},
+ ])
def test_search_no_content(apiobj, frontend):
@@ -64,7 +61,7 @@ def test_search_with_debug(apiobj, frontend, logtype):
api = frontend(apiobj, options=API_OPTIONS)
loglib.set_log_output(logtype)
- results = api.search('TEST')
+ api.search('TEST')
assert loglib.get_and_disable()
diff --git a/test/python/api/test_api_status.py b/test/python/api/test_api_status.py
index 9341b527..a80c8710 100644
--- a/test/python/api/test_api_status.py
+++ b/test/python/api/test_api_status.py
@@ -2,18 +2,17 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the status API call.
"""
import datetime as dt
-import pytest
-from nominatim_db.version import NominatimVersion
from nominatim_api.version import NOMINATIM_API_VERSION
import nominatim_api as napi
+
def test_status_no_extra_info(apiobj, frontend):
api = frontend(apiobj)
result = api.status()
diff --git a/test/python/api/test_api_types.py b/test/python/api/test_api_types.py
index fbb9b682..898b884d 100644
--- a/test/python/api/test_api_types.py
+++ b/test/python/api/test_api_types.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for loading of parameter dataclasses.
@@ -12,6 +12,7 @@ import pytest
from nominatim_api.errors import UsageError
import nominatim_api.types as typ
+
def test_no_params_defaults():
params = typ.LookupDetails.from_kwargs({})
@@ -24,7 +25,7 @@ def test_no_params_defaults():
('geometry_simplification', 'NaN')])
def test_bad_format_reverse(k, v):
with pytest.raises(UsageError):
- params = typ.ReverseDetails.from_kwargs({k: v})
+ typ.ReverseDetails.from_kwargs({k: v})
@pytest.mark.parametrize('rin,rout', [(-23, 0), (0, 0), (1, 1),
diff --git a/test/python/api/test_export.py b/test/python/api/test_export.py
index b0da52ce..c94cb7fb 100644
--- a/test/python/api/test_export.py
+++ b/test/python/api/test_export.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for export CLI function.
@@ -11,12 +11,13 @@ import pytest
import nominatim_db.cli
+
@pytest.fixture
def run_export(tmp_path, capsys):
def _exec(args):
+ cli_args = ['export', '--project-dir', str(tmp_path)] + args
assert 0 == nominatim_db.cli.nominatim(osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
- cli_args=['export', '--project-dir', str(tmp_path)]
- + args)
+ cli_args=cli_args)
return capsys.readouterr().out.split('\r\n')
return _exec
@@ -25,9 +26,9 @@ def run_export(tmp_path, capsys):
@pytest.fixture(autouse=True)
def setup_database_with_context(apiobj):
apiobj.add_placex(place_id=332, osm_type='W', osm_id=4,
- class_='highway', type='residential', name='Street',
- country_code='pl', postcode='55674',
- rank_search=27, rank_address=26)
+ class_='highway', type='residential', name='Street',
+ country_code='pl', postcode='55674',
+ rank_search=27, rank_address=26)
apiobj.add_address_placex(332, fromarea=False, isaddress=False,
distance=0.0034,
place_id=1000, osm_type='N', osm_id=3333,
diff --git a/test/python/api/test_helpers_v1.py b/test/python/api/test_helpers_v1.py
index 3a6a9a0b..10f0921b 100644
--- a/test/python/api/test_helpers_v1.py
+++ b/test/python/api/test_helpers_v1.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the helper functions for v1 API.
@@ -11,6 +11,7 @@ import pytest
import nominatim_api.v1.helpers as helper
+
@pytest.mark.parametrize('inp', ['',
'abc',
'12 23',
@@ -35,40 +36,42 @@ def test_extract_coords_with_text_before():
def test_extract_coords_with_text_after():
assert ('abc', 12.456, -78.90) == helper.extract_coords_from_query('-78.90, 12.456 abc')
+
@pytest.mark.parametrize('inp', [' [12.456,-78.90] ', ' 12.456,-78.90 '])
def test_extract_coords_with_spaces(inp):
assert ('', -78.90, 12.456) == helper.extract_coords_from_query(inp)
+
@pytest.mark.parametrize('inp', ['40 26.767 N 79 58.933 W',
- '40° 26.767′ N 79° 58.933′ W',
- "40° 26.767' N 79° 58.933' W",
- "40° 26.767'\n"
- " N 79° 58.933' W",
- 'N 40 26.767, W 79 58.933',
- 'N 40°26.767′, W 79°58.933′',
- ' N 40°26.767′, W 79°58.933′',
- "N 40°26.767', W 79°58.933'",
-
- '40 26 46 N 79 58 56 W',
- '40° 26′ 46″ N 79° 58′ 56″ W',
- '40° 26′ 46.00″ N 79° 58′ 56.00″ W',
- '40°26′46″N 79°58′56″W',
- 'N 40 26 46 W 79 58 56',
- 'N 40° 26′ 46″, W 79° 58′ 56″',
- 'N 40° 26\' 46", W 79° 58\' 56"',
- 'N 40° 26\' 46", W 79° 58\' 56"',
-
- '40.446 -79.982',
- '40.446,-79.982',
- '40.446° N 79.982° W',
- 'N 40.446° W 79.982°',
-
- '[40.446 -79.982]',
- '[40.446,-79.982]',
- ' 40.446 , -79.982 ',
- ' 40.446 , -79.982 ',
- ' 40.446 , -79.982 ',
- ' 40.446, -79.982 '])
+ '40° 26.767′ N 79° 58.933′ W',
+ "40° 26.767' N 79° 58.933' W",
+ "40° 26.767'\n"
+ " N 79° 58.933' W",
+ 'N 40 26.767, W 79 58.933',
+ 'N 40°26.767′, W 79°58.933′',
+ ' N 40°26.767′, W 79°58.933′',
+ "N 40°26.767', W 79°58.933'",
+
+ '40 26 46 N 79 58 56 W',
+ '40° 26′ 46″ N 79° 58′ 56″ W',
+ '40° 26′ 46.00″ N 79° 58′ 56.00″ W',
+ '40°26′46″N 79°58′56″W',
+ 'N 40 26 46 W 79 58 56',
+ 'N 40° 26′ 46″, W 79° 58′ 56″',
+ 'N 40° 26\' 46", W 79° 58\' 56"',
+ 'N 40° 26\' 46", W 79° 58\' 56"',
+
+ '40.446 -79.982',
+ '40.446,-79.982',
+ '40.446° N 79.982° W',
+ 'N 40.446° W 79.982°',
+
+ '[40.446 -79.982]',
+ '[40.446,-79.982]',
+ ' 40.446 , -79.982 ',
+ ' 40.446 , -79.982 ',
+ ' 40.446 , -79.982 ',
+ ' 40.446, -79.982 '])
def test_extract_coords_formats(inp):
query, x, y = helper.extract_coords_from_query(inp)
@@ -108,9 +111,11 @@ def test_extract_category_good(inp):
assert cls == 'shop'
assert typ == 'fish'
+
def test_extract_category_only():
assert helper.extract_category_from_query('[shop=market]') == ('', 'shop', 'market')
+
@pytest.mark.parametrize('inp', ['house []', 'nothing', '[352]'])
-def test_extract_category_no_match(inp):
+def test_extract_category_no_match(inp):
assert helper.extract_category_from_query(inp) == (inp, None, None)
diff --git a/test/python/api/test_localization.py b/test/python/api/test_localization.py
index 21fa72c8..0a30cdc1 100644
--- a/test/python/api/test_localization.py
+++ b/test/python/api/test_localization.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test functions for adapting results to the user's locale.
@@ -11,34 +11,36 @@ import pytest
from nominatim_api import Locales
-def test_display_name_empty_names():
- l = Locales(['en', 'de'])
- assert l.display_name(None) == ''
- assert l.display_name({}) == ''
+def test_display_name_empty_names():
+ loc = Locales(['en', 'de'])
+
+ assert loc.display_name(None) == ''
+ assert loc.display_name({}) == ''
+
def test_display_name_none_localized():
- l = Locales()
+ loc = Locales()
- assert l.display_name({}) == ''
- assert l.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'ALL'
- assert l.display_name({'ref': '34', 'name:de': 'DE'}) == '34'
+ assert loc.display_name({}) == ''
+ assert loc.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'ALL'
+ assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == '34'
def test_display_name_localized():
- l = Locales(['en', 'de'])
+ loc = Locales(['en', 'de'])
- assert l.display_name({}) == ''
- assert l.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'DE'
- assert l.display_name({'ref': '34', 'name:de': 'DE'}) == 'DE'
+ assert loc.display_name({}) == ''
+ assert loc.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'DE'
+ assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == 'DE'
def test_display_name_preference():
- l = Locales(['en', 'de'])
+ loc = Locales(['en', 'de'])
- assert l.display_name({}) == ''
- assert l.display_name({'name:de': 'DE', 'name:en': 'EN'}) == 'EN'
- assert l.display_name({'official_name:en': 'EN', 'name:de': 'DE'}) == 'DE'
+ assert loc.display_name({}) == ''
+ assert loc.display_name({'name:de': 'DE', 'name:en': 'EN'}) == 'EN'
+ assert loc.display_name({'official_name:en': 'EN', 'name:de': 'DE'}) == 'DE'
@pytest.mark.parametrize('langstr,langlist',
diff --git a/test/python/api/test_result_formatting_v1.py b/test/python/api/test_result_formatting_v1.py
index aaecab45..406c7654 100644
--- a/test/python/api/test_result_formatting_v1.py
+++ b/test/python/api/test_result_formatting_v1.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for formatting results for the V1 API.
@@ -22,6 +22,7 @@ STATUS_FORMATS = {'text', 'json'}
# StatusResult
+
def test_status_format_list():
assert set(v1_format.list_formats(napi.StatusResult)) == STATUS_FORMATS
@@ -36,11 +37,13 @@ def test_status_unsupported():
def test_status_format_text():
- assert v1_format.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 v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here'
+def test_status_format_error_text():
+ assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) \
+ == 'ERROR: message here'
def test_status_format_json_minimal():
@@ -48,8 +51,9 @@ def test_status_format_json_minimal():
result = v1_format.format_result(status, 'json', {})
- assert result == \
- f'{{"status":700,"message":"Bad format.","software_version":"{napi.__version__}"}}'
+ assert json.loads(result) == {'status': 700,
+ 'message': 'Bad format.',
+ 'software_version': napi.__version__}
def test_status_format_json_full():
@@ -59,8 +63,11 @@ def test_status_format_json_full():
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"}}'
+ assert json.loads(result) == {'status': 0,
+ 'message': 'OK',
+ 'data_updated': '2010-02-07T20:20:03+00:00',
+ 'software_version': napi.__version__,
+ 'database_version': '5.6'}
# DetailedResult
@@ -86,7 +93,7 @@ def test_search_details_minimal():
'extratags': {},
'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
- }
+ }
def test_search_details_full():
@@ -110,7 +117,7 @@ def test_search_details_full():
rank_search=28,
importance=0.0443,
country_code='ll',
- indexed_date = import_date
+ indexed_date=import_date
)
search.localize(napi.Locales())
@@ -140,7 +147,7 @@ def test_search_details_full():
'isarea': False,
'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
- }
+ }
@pytest.mark.parametrize('gtype,isarea', [('ST_Point', False),
@@ -149,9 +156,9 @@ def test_search_details_full():
('ST_MultiPolygon', True)])
def test_search_details_no_geometry(gtype, isarea):
search = napi.DetailedResult(napi.SourceTable.PLACEX,
- ('place', 'thing'),
- napi.Point(1.0, 2.0),
- geometry={'type': gtype})
+ ('place', 'thing'),
+ napi.Point(1.0, 2.0),
+ geometry={'type': gtype})
result = v1_format.format_result(search, 'json', {})
js = json.loads(result)
@@ -161,16 +168,17 @@ def test_search_details_no_geometry(gtype, isarea):
def test_search_details_with_geometry():
- search = napi.DetailedResult(napi.SourceTable.PLACEX,
- ('place', 'thing'),
- napi.Point(1.0, 2.0),
- geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
+ search = napi.DetailedResult(
+ napi.SourceTable.PLACEX,
+ ('place', 'thing'),
+ napi.Point(1.0, 2.0),
+ geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
result = v1_format.format_result(search, 'json', {})
js = json.loads(result)
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
- assert js['isarea'] == False
+ assert js['isarea'] is False
def test_search_details_with_icon_available():
@@ -226,7 +234,7 @@ def test_search_details_with_address_minimal():
@pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
('linked_rows', 'linked_places'),
('parented_rows', 'hierarchy')
- ])
+ ])
def test_search_details_with_further_infos(field, outfield):
search = napi.DetailedResult(napi.SourceTable.PLACEX,
('place', 'thing'),
@@ -249,50 +257,49 @@ def test_search_details_with_further_infos(field, outfield):
js = json.loads(result)
assert js[outfield] == [{'localname': 'Trespass',
- 'place_id': 3498,
- 'osm_id': 442,
- 'osm_type': 'R',
- 'place_type': 'spec',
- 'class': 'bnd',
- 'type': 'note',
- 'admin_level': 4,
- 'rank_address': 10,
- 'distance': 0.034,
- 'isaddress': True}]
+ 'place_id': 3498,
+ 'osm_id': 442,
+ 'osm_type': 'R',
+ 'place_type': 'spec',
+ 'class': 'bnd',
+ 'type': 'note',
+ 'admin_level': 4,
+ 'rank_address': 10,
+ 'distance': 0.034,
+ 'isaddress': True}]
def test_search_details_grouped_hierarchy():
search = napi.DetailedResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
- parented_rows =
- [napi.AddressLine(place_id=3498,
- osm_object=('R', 442),
- category=('bnd', 'note'),
- names={'name': 'Trespass'},
- extratags={'access': 'no',
- 'place_type': 'spec'},
- admin_level=4,
- fromarea=True,
- isaddress=True,
- rank_address=10,
- distance=0.034)
- ])
+ parented_rows=[napi.AddressLine(
+ place_id=3498,
+ osm_object=('R', 442),
+ category=('bnd', 'note'),
+ names={'name': 'Trespass'},
+ extratags={'access': 'no',
+ 'place_type': 'spec'},
+ admin_level=4,
+ fromarea=True,
+ isaddress=True,
+ rank_address=10,
+ distance=0.034)])
result = v1_format.format_result(search, 'json', {'group_hierarchy': True})
js = json.loads(result)
assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
- 'place_id': 3498,
- 'osm_id': 442,
- 'osm_type': 'R',
- 'place_type': 'spec',
- 'class': 'bnd',
- 'type': 'note',
- 'admin_level': 4,
- 'rank_address': 10,
- 'distance': 0.034,
- 'isaddress': True}]}
+ 'place_id': 3498,
+ 'osm_id': 442,
+ 'osm_type': 'R',
+ 'place_type': 'spec',
+ 'class': 'bnd',
+ 'type': 'note',
+ 'admin_level': 4,
+ 'rank_address': 10,
+ 'distance': 0.034,
+ 'isaddress': True}]}
def test_search_details_keywords_name():
@@ -307,7 +314,7 @@ def test_search_details_keywords_name():
js = json.loads(result)
assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
- {'id': 24, 'token': 'foo'}],
+ {'id': 24, 'token': 'foo'}],
'address': []}
@@ -323,6 +330,5 @@ def test_search_details_keywords_address():
js = json.loads(result)
assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
- {'id': 24, 'token': 'foo'}],
+ {'id': 24, 'token': 'foo'}],
'name': []}
-
diff --git a/test/python/api/test_result_formatting_v1_reverse.py b/test/python/api/test_result_formatting_v1_reverse.py
index 2c036a65..902f0e79 100644
--- a/test/python/api/test_result_formatting_v1_reverse.py
+++ b/test/python/api/test_result_formatting_v1_reverse.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for formatting reverse results for the V1 API.
@@ -20,6 +20,7 @@ import nominatim_api as napi
FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml']
+
@pytest.mark.parametrize('fmt', FORMATS)
def test_format_reverse_minimal(fmt):
reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
@@ -104,8 +105,7 @@ def test_format_reverse_with_address(fmt):
reverse.localize(napi.Locales())
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'addressdetails': True})
-
+ {'addressdetails': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -168,7 +168,7 @@ def test_format_reverse_geocodejson_special_parts():
reverse.localize(napi.Locales())
raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson',
- {'addressdetails': True})
+ {'addressdetails': True})
props = json.loads(raw)['features'][0]['properties']['geocoding']
assert props['housenumber'] == '1'
@@ -184,8 +184,7 @@ def test_format_reverse_with_address_none(fmt):
address_rows=napi.AddressLines())
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'addressdetails': True})
-
+ {'addressdetails': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -211,10 +210,10 @@ def test_format_reverse_with_extratags(fmt):
reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
- extratags={'one': 'A', 'two':'B'})
+ extratags={'one': 'A', 'two': 'B'})
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'extratags': True})
+ {'extratags': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -226,7 +225,7 @@ def test_format_reverse_with_extratags(fmt):
else:
extra = result['extratags']
- assert extra == {'one': 'A', 'two':'B'}
+ assert extra == {'one': 'A', 'two': 'B'}
@pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
@@ -236,7 +235,7 @@ def test_format_reverse_with_extratags_none(fmt):
napi.Point(1.0, 2.0))
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'extratags': True})
+ {'extratags': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -256,10 +255,10 @@ def test_format_reverse_with_namedetails_with_name(fmt):
reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
- names={'name': 'A', 'ref':'1'})
+ names={'name': 'A', 'ref': '1'})
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'namedetails': True})
+ {'namedetails': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -271,7 +270,7 @@ def test_format_reverse_with_namedetails_with_name(fmt):
else:
extra = result['namedetails']
- assert extra == {'name': 'A', 'ref':'1'}
+ assert extra == {'name': 'A', 'ref': '1'}
@pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
@@ -281,7 +280,7 @@ def test_format_reverse_with_namedetails_without_name(fmt):
napi.Point(1.0, 2.0))
raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'namedetails': True})
+ {'namedetails': True})
if fmt == 'xml':
root = ET.fromstring(raw)
@@ -303,7 +302,7 @@ def test_search_details_with_icon_available(fmt):
napi.Point(1.0, 2.0))
result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'icon_base_url': 'foo'})
+ {'icon_base_url': 'foo'})
js = json.loads(result)
@@ -317,7 +316,6 @@ def test_search_details_with_icon_not_available(fmt):
napi.Point(1.0, 2.0))
result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
- {'icon_base_url': 'foo'})
+ {'icon_base_url': 'foo'})
assert 'icon' not in json.loads(result)
-
diff --git a/test/python/api/test_results.py b/test/python/api/test_results.py
index f0bfa163..8e9fbf68 100644
--- a/test/python/api/test_results.py
+++ b/test/python/api/test_results.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for result datatype helper functions.
@@ -11,16 +11,15 @@ import struct
from binascii import hexlify
import pytest
-import pytest_asyncio
-import sqlalchemy as sa
-
from nominatim_api import SourceTable, DetailedResult, Point
import nominatim_api.results as nresults
+
def mkpoint(x, y):
return hexlify(struct.pack("=biidd", 1, 0x20000001, 4326, x, y)).decode('utf-8')
+
class FakeRow:
def __init__(self, **kwargs):
if 'parent_place_id' not in kwargs:
@@ -39,6 +38,7 @@ def test_minimal_detailed_result():
assert res.lat == 0.5
assert res.calculated_importance() == pytest.approx(0.00001)
+
def test_detailed_result_custom_importance():
res = DetailedResult(SourceTable.PLACEX,
('amenity', 'post_box'),
diff --git a/test/python/api/test_server_glue_v1.py b/test/python/api/test_server_glue_v1.py
index 6ea790c0..8d9f0940 100644
--- a/test/python/api/test_server_glue_v1.py
+++ b/test/python/api/test_server_glue_v1.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the Python web frameworks adaptor, v1 API.
@@ -121,7 +121,6 @@ class TestAdaptorRaiseError:
return excinfo.value
-
def test_without_content_set(self):
err = self.run_raise_error('TEST', 404)
@@ -129,7 +128,6 @@ class TestAdaptorRaiseError:
assert err.msg == 'ERROR 404: TEST'
assert err.status == 404
-
def test_json(self):
self.adaptor.content_type = 'application/json; charset=utf-8'
@@ -139,7 +137,6 @@ class TestAdaptorRaiseError:
assert content['code'] == 501
assert content['message'] == 'TEST'
-
def test_xml(self):
self.adaptor.content_type = 'text/xml; charset=utf-8'
@@ -235,7 +232,6 @@ class TestStatusEndpoint:
monkeypatch.setattr(napi.NominatimAPIAsync, 'status', _status)
-
@pytest.mark.asyncio
async def test_status_without_params(self):
a = FakeAdaptor()
@@ -247,7 +243,6 @@ class TestStatusEndpoint:
assert resp.status == 200
assert resp.content_type == 'text/plain; charset=utf-8'
-
@pytest.mark.asyncio
async def test_status_with_error(self):
a = FakeAdaptor()
@@ -259,7 +254,6 @@ class TestStatusEndpoint:
assert resp.status == 500
assert resp.content_type == 'text/plain; charset=utf-8'
-
@pytest.mark.asyncio
async def test_status_json_with_error(self):
a = FakeAdaptor(params={'format': 'json'})
@@ -271,7 +265,6 @@ class TestStatusEndpoint:
assert resp.status == 200
assert resp.content_type == 'application/json; charset=utf-8'
-
@pytest.mark.asyncio
async def test_status_bad_format(self):
a = FakeAdaptor(params={'format': 'foo'})
@@ -298,7 +291,6 @@ class TestDetailsEndpoint:
monkeypatch.setattr(napi.NominatimAPIAsync, 'details', _lookup)
-
@pytest.mark.asyncio
async def test_details_no_params(self):
a = FakeAdaptor()
@@ -306,7 +298,6 @@ class TestDetailsEndpoint:
with pytest.raises(FakeError, match='^400 -- .*Missing'):
await glue.details_endpoint(napi.NominatimAPIAsync(), a)
-
@pytest.mark.asyncio
async def test_details_by_place_id(self):
a = FakeAdaptor(params={'place_id': '4573'})
@@ -315,7 +306,6 @@ class TestDetailsEndpoint:
assert self.lookup_args[0].place_id == 4573
-
@pytest.mark.asyncio
async def test_details_by_osm_id(self):
a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45'})
@@ -326,7 +316,6 @@ class TestDetailsEndpoint:
assert self.lookup_args[0].osm_id == 45
assert self.lookup_args[0].osm_class is None
-
@pytest.mark.asyncio
async def test_details_with_debugging(self):
a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45', 'debug': '1'})
@@ -337,7 +326,6 @@ class TestDetailsEndpoint:
assert resp.content_type == 'text/html; charset=utf-8'
assert content.tag == 'html'
-
@pytest.mark.asyncio
async def test_details_no_result(self):
a = FakeAdaptor(params={'place_id': '4573'})
@@ -353,14 +341,14 @@ class TestReverseEndPoint:
@pytest.fixture(autouse=True)
def patch_reverse_func(self, monkeypatch):
self.result = napi.ReverseResult(napi.SourceTable.PLACEX,
- ('place', 'thing'),
- napi.Point(1.0, 2.0))
+ ('place', 'thing'),
+ napi.Point(1.0, 2.0))
+
async def _reverse(*args, **kwargs):
return self.result
monkeypatch.setattr(napi.NominatimAPIAsync, 'reverse', _reverse)
-
@pytest.mark.asyncio
@pytest.mark.parametrize('params', [{}, {'lat': '3.4'}, {'lon': '6.7'}])
async def test_reverse_no_params(self, params):
@@ -371,19 +359,6 @@ class TestReverseEndPoint:
with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'):
await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize('params', [{'lat': '45.6', 'lon': '4563'}])
- async def test_reverse_success(self, params):
- a = FakeAdaptor()
- a.params = params
- a.params['format'] = 'json'
-
- res = await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
-
- assert res == ''
-
-
@pytest.mark.asyncio
async def test_reverse_success(self):
a = FakeAdaptor()
@@ -392,7 +367,6 @@ class TestReverseEndPoint:
assert await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
-
@pytest.mark.asyncio
async def test_reverse_from_search(self):
a = FakeAdaptor()
@@ -413,12 +387,12 @@ class TestLookupEndpoint:
self.results = [napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0))]
+
async def _lookup(*args, **kwargs):
return napi.SearchResults(self.results)
monkeypatch.setattr(napi.NominatimAPIAsync, 'lookup', _lookup)
-
@pytest.mark.asyncio
async def test_lookup_no_params(self):
a = FakeAdaptor()
@@ -428,7 +402,6 @@ class TestLookupEndpoint:
assert res.output == '[]'
-
@pytest.mark.asyncio
@pytest.mark.parametrize('param', ['w', 'bad', ''])
async def test_lookup_bad_params(self, param):
@@ -440,7 +413,6 @@ class TestLookupEndpoint:
assert len(json.loads(res.output)) == 1
-
@pytest.mark.asyncio
@pytest.mark.parametrize('param', ['p234234', '4563'])
async def test_lookup_bad_osm_type(self, param):
@@ -452,7 +424,6 @@ class TestLookupEndpoint:
assert len(json.loads(res.output)) == 1
-
@pytest.mark.asyncio
async def test_lookup_working(self):
a = FakeAdaptor()
@@ -473,12 +444,12 @@ class TestSearchEndPointSearch:
self.results = [napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0))]
+
async def _search(*args, **kwargs):
return napi.SearchResults(self.results)
monkeypatch.setattr(napi.NominatimAPIAsync, 'search', _search)
-
@pytest.mark.asyncio
async def test_search_free_text(self):
a = FakeAdaptor()
@@ -488,7 +459,6 @@ class TestSearchEndPointSearch:
assert len(json.loads(res.output)) == 1
-
@pytest.mark.asyncio
async def test_search_free_text_xml(self):
a = FakeAdaptor()
@@ -500,7 +470,6 @@ class TestSearchEndPointSearch:
assert res.status == 200
assert res.output.index('something') > 0
-
@pytest.mark.asyncio
async def test_search_free_and_structured(self):
a = FakeAdaptor()
@@ -508,8 +477,7 @@ class TestSearchEndPointSearch:
a.params['city'] = 'ignored'
with pytest.raises(FakeError, match='^400 -- .*cannot be used together'):
- res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
-
+ await glue.search_endpoint(napi.NominatimAPIAsync(), a)
@pytest.mark.asyncio
@pytest.mark.parametrize('dedupe,numres', [(True, 1), (False, 2)])
@@ -532,12 +500,12 @@ class TestSearchEndPointSearchAddress:
self.results = [napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0))]
+
async def _search(*args, **kwargs):
return napi.SearchResults(self.results)
monkeypatch.setattr(napi.NominatimAPIAsync, 'search_address', _search)
-
@pytest.mark.asyncio
async def test_search_structured(self):
a = FakeAdaptor()
@@ -555,12 +523,12 @@ class TestSearchEndPointSearchCategory:
self.results = [napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0))]
+
async def _search(*args, **kwargs):
return napi.SearchResults(self.results)
monkeypatch.setattr(napi.NominatimAPIAsync, 'search_category', _search)
-
@pytest.mark.asyncio
async def test_search_category(self):
a = FakeAdaptor()
diff --git a/test/python/api/test_warm.py b/test/python/api/test_warm.py
index f0c9986d..94081d00 100644
--- a/test/python/api/test_warm.py
+++ b/test/python/api/test_warm.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for warm-up CLI function.
@@ -11,6 +11,7 @@ import pytest
import nominatim_db.cli
+
@pytest.fixture(autouse=True)
def setup_database_with_context(apiobj, table_factory):
table_factory('word',
@@ -21,8 +22,9 @@ def setup_database_with_context(apiobj, table_factory):
apiobj.add_data('properties',
[{'property': 'tokenizer', 'value': 'icu'},
{'property': 'tokenizer_import_normalisation', 'value': ':: lower();'},
- {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"},
- ])
+ {'property': 'tokenizer_import_transliteration',
+ 'value': "'1' > '/1/'; 'ä' > 'ä '"}
+ ])
@pytest.mark.parametrize('args', [['--search-only'], ['--reverse-only']])
diff --git a/test/python/cli/conftest.py b/test/python/cli/conftest.py
index 84f2d659..921a3a32 100644
--- a/test/python/cli/conftest.py
+++ b/test/python/cli/conftest.py
@@ -2,12 +2,13 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
import pytest
import nominatim_db.cli
+
class MockParamCapture:
""" Mock that records the parameters with which a function was called
as well as the number of calls.
diff --git a/test/python/cli/test_cli.py b/test/python/cli/test_cli.py
index d42df50a..a538049e 100644
--- a/test/python/cli/test_cli.py
+++ b/test/python/cli/test_cli.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for command line interface wrapper.
@@ -11,7 +11,6 @@ These tests just check that the various command line parameters route to the
correct functionality. They use a lot of monkeypatching to avoid executing
the actual functions.
"""
-import importlib
import pytest
import nominatim_db.indexer.indexer
@@ -28,6 +27,7 @@ def test_cli_help(cli_call, capsys):
captured = capsys.readouterr()
assert captured.out.startswith('usage:')
+
def test_cli_version(cli_call, capsys):
""" Running nominatim tool --version prints a version string.
"""
@@ -46,7 +46,6 @@ class TestCliWithDb:
# Make sure tools.freeze.is_frozen doesn't report database as frozen. Monkeypatching failed
table_factory('place')
-
@pytest.mark.parametrize("name,oid", [('file', 'foo.osm'), ('diff', 'foo.osc')])
def test_cli_add_data_file_command(self, cli_call, mock_func_factory, name, oid):
mock_run_legacy = mock_func_factory(nominatim_db.tools.add_osm_data, 'add_data_from_file')
@@ -54,7 +53,6 @@ class TestCliWithDb:
assert mock_run_legacy.called == 1
-
@pytest.mark.parametrize("name,oid", [('node', 12), ('way', 8), ('relation', 32)])
def test_cli_add_data_object_command(self, cli_call, mock_func_factory, name, oid):
mock_run_legacy = mock_func_factory(nominatim_db.tools.add_osm_data, 'add_osm_object')
@@ -62,8 +60,6 @@ class TestCliWithDb:
assert mock_run_legacy.called == 1
-
-
def test_cli_add_data_tiger_data(self, cli_call, cli_tokenizer_mock, async_mock_func_factory):
mock = async_mock_func_factory(nominatim_db.tools.tiger_data, 'add_tiger_data')
@@ -80,7 +76,6 @@ class TestCliWithDb:
assert mock_drop.called == 1
assert mock_flatnode.called == 1
-
@pytest.mark.parametrize("params,do_bnds,do_ranks", [
([], 2, 2),
(['--boundaries-only'], 2, 0),
@@ -89,11 +84,14 @@ class TestCliWithDb:
def test_index_command(self, monkeypatch, async_mock_func_factory, table_factory,
params, do_bnds, do_ranks):
table_factory('import_status', 'indexed bool')
- bnd_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_boundaries')
- rank_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_by_rank')
- postcode_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_postcodes')
+ bnd_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer,
+ 'index_boundaries')
+ rank_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer,
+ 'index_by_rank')
+ postcode_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer,
+ 'index_postcodes')
- monkeypatch.setattr(nominatim_db.indexer.indexer.Indexer, 'has_pending',
+ monkeypatch.setattr(nominatim_db.indexer.indexer.Indexer, 'has_pending',
[False, True].pop)
assert self.call_nominatim('index', *params) == 0
@@ -102,7 +100,6 @@ class TestCliWithDb:
assert rank_mock.called == do_ranks
assert postcode_mock.called == do_ranks
-
def test_special_phrases_wiki_command(self, mock_func_factory):
func = mock_func_factory(nominatim_db.clicmd.special_phrases.SPImporter, 'import_phrases')
@@ -110,7 +107,6 @@ class TestCliWithDb:
assert func.called == 1
-
def test_special_phrases_csv_command(self, src_dir, mock_func_factory):
func = mock_func_factory(nominatim_db.clicmd.special_phrases.SPImporter, 'import_phrases')
testdata = src_dir / 'test' / 'testdb'
@@ -120,7 +116,6 @@ class TestCliWithDb:
assert func.called == 1
-
def test_special_phrases_csv_bad_file(self, src_dir):
testdata = src_dir / 'something349053905.csv'
diff --git a/test/python/cli/test_cmd_admin.py b/test/python/cli/test_cmd_admin.py
index 7b0b9cd4..9732d734 100644
--- a/test/python/cli/test_cmd_admin.py
+++ b/test/python/cli/test_cmd_admin.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for the command line interface wrapper admin subcommand.
@@ -39,11 +39,13 @@ def test_admin_clean_deleted_relations(cli_call, mock_func_factory):
assert cli_call('admin', '--clean-deleted', '1 month') == 0
assert mock.called == 1
+
def test_admin_clean_deleted_relations_no_age(cli_call, mock_func_factory):
- mock = mock_func_factory(nominatim_db.tools.admin, 'clean_deleted_relations')
+ mock_func_factory(nominatim_db.tools.admin, 'clean_deleted_relations')
assert cli_call('admin', '--clean-deleted') == 1
+
class TestCliAdminWithDb:
@pytest.fixture(autouse=True)
@@ -51,7 +53,6 @@ class TestCliAdminWithDb:
self.call_nominatim = cli_call
self.tokenizer_mock = cli_tokenizer_mock
-
@pytest.mark.parametrize("func, params", [('analyse_indexing', ('--analyse-indexing', ))])
def test_analyse_indexing(self, mock_func_factory, func, params):
mock = mock_func_factory(nominatim_db.tools.admin, func)
diff --git a/test/python/cli/test_cmd_api.py b/test/python/cli/test_cmd_api.py
index 1c0750d1..541b680c 100644
--- a/test/python/cli/test_cmd_api.py
+++ b/test/python/cli/test_cmd_api.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for API access commands of command-line interface wrapper.
@@ -10,9 +10,9 @@ Tests for API access commands of command-line interface wrapper.
import json
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')
@@ -30,13 +30,11 @@ class TestCliStatusCall:
monkeypatch.setattr(napi.NominatimAPI, 'status',
lambda self: napi.StatusResult(200, 'OK'))
-
def test_status_simple(self, cli_call, tmp_path):
result = cli_call('status', '--project-dir', str(tmp_path))
assert result == 0
-
def test_status_json_format(self, cli_call, tmp_path, capsys):
result = cli_call('status', '--project-dir', str(tmp_path),
'--format', 'json')
@@ -60,7 +58,6 @@ class TestCliDetailsCall:
('--way', '1'),
('--relation', '1'),
('--place_id', '10001')])
-
def test_details_json_format(self, cli_call, tmp_path, capsys, params):
result = cli_call('details', '--project-dir', str(tmp_path), *params)
@@ -75,15 +72,14 @@ class TestCliReverseCall:
def setup_reverse_mock(self, monkeypatch):
result = napi.ReverseResult(napi.SourceTable.PLACEX, ('place', 'thing'),
napi.Point(1.0, -3.0),
- names={'name':'Name', 'name:fr': 'Nom'},
- extratags={'extra':'Extra'},
+ names={'name': 'Name', 'name:fr': 'Nom'},
+ extratags={'extra': 'Extra'},
locale_name='Name',
display_name='Name')
monkeypatch.setattr(napi.NominatimAPI, 'reverse',
lambda *args, **kwargs: result)
-
def test_reverse_simple(self, cli_call, tmp_path, capsys):
result = cli_call('reverse', '--project-dir', str(tmp_path),
'--lat', '34', '--lon', '34')
@@ -96,7 +92,6 @@ class TestCliReverseCall:
assert 'extratags' not in out
assert 'namedetails' not in out
-
@pytest.mark.parametrize('param,field', [('--addressdetails', 'address'),
('--extratags', 'extratags'),
('--namedetails', 'namedetails')])
@@ -109,7 +104,6 @@ class TestCliReverseCall:
out = json.loads(capsys.readouterr().out)
assert field in out
-
def test_reverse_format(self, cli_call, tmp_path, capsys):
result = cli_call('reverse', '--project-dir', str(tmp_path),
'--lat', '34', '--lon', '34', '--format', 'geojson')
@@ -125,11 +119,11 @@ 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'},
- locale_name='Name',
- display_name='Name')
+ napi.Point(1.0, -3.0),
+ names={'name': 'Name', 'name:fr': 'Nom'},
+ extratags={'extra': 'Extra'},
+ locale_name='Name',
+ display_name='Name')
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
lambda *args, **kwargs: napi.SearchResults([result]))
@@ -150,19 +144,18 @@ class TestCliLookupCall:
@pytest.mark.parametrize('endpoint, params', [('search', ('--query', 'Berlin')),
('search_address', ('--city', 'Berlin'))
- ])
+ ])
def test_search(cli_call, tmp_path, capsys, monkeypatch, endpoint, params):
result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
napi.Point(1.0, -3.0),
- names={'name':'Name', 'name:fr': 'Nom'},
- extratags={'extra':'Extra'},
+ names={'name': 'Name', 'name:fr': 'Nom'},
+ extratags={'extra': 'Extra'},
locale_name='Name',
display_name='Name')
monkeypatch.setattr(napi.NominatimAPI, endpoint,
lambda *args, **kwargs: napi.SearchResults([result]))
-
result = cli_call('search', '--project-dir', str(tmp_path), *params)
assert result == 0
diff --git a/test/python/cli/test_cmd_import.py b/test/python/cli/test_cmd_import.py
index f833dde3..e4a86fe0 100644
--- a/test/python/cli/test_cmd_import.py
+++ b/test/python/cli/test_cmd_import.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for import command of the command-line interface wrapper.
@@ -24,15 +24,12 @@ class TestCliImportWithDb:
self.call_nominatim = cli_call
self.tokenizer_mock = cli_tokenizer_mock
-
def test_import_missing_file(self):
assert self.call_nominatim('import', '--osm-file', 'sfsafegwedgw.reh.erh') == 1
-
def test_import_bad_file(self):
assert self.call_nominatim('import', '--osm-file', '.') == 1
-
@pytest.mark.parametrize('with_updates', [True, False])
def test_import_full(self, mock_func_factory, async_mock_func_factory,
with_updates, place_table, property_table):
@@ -62,7 +59,6 @@ class TestCliImportWithDb:
cf_mock = mock_func_factory(nominatim_db.tools.refresh, 'create_functions')
-
assert self.call_nominatim(*params) == 0
assert self.tokenizer_mock.finalize_import_called
@@ -71,7 +67,6 @@ class TestCliImportWithDb:
for mock in mocks:
assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
-
def test_import_continue_load_data(self, mock_func_factory, async_mock_func_factory):
mocks = [
mock_func_factory(nominatim_db.tools.database_import, 'truncate_data_tables'),
@@ -89,7 +84,6 @@ class TestCliImportWithDb:
for mock in mocks:
assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
-
def test_import_continue_indexing(self, mock_func_factory, async_mock_func_factory,
placex_table, temp_db_conn):
mocks = [
@@ -107,7 +101,6 @@ class TestCliImportWithDb:
# Calling it again still works for the index
assert self.call_nominatim('import', '--continue', 'indexing') == 0
-
def test_import_continue_postprocess(self, mock_func_factory, async_mock_func_factory):
mocks = [
async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'),
diff --git a/test/python/cli/test_cmd_refresh.py b/test/python/cli/test_cmd_refresh.py
index 9f3d7bb2..8121946f 100644
--- a/test/python/cli/test_cmd_refresh.py
+++ b/test/python/cli/test_cmd_refresh.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for command line interface wrapper for refresk command.
@@ -13,6 +13,7 @@ import nominatim_db.tools.refresh
import nominatim_db.tools.postcodes
import nominatim_db.indexer.indexer
+
class TestRefresh:
@pytest.fixture(autouse=True)
@@ -20,7 +21,6 @@ class TestRefresh:
self.call_nominatim = cli_call
self.tokenizer_mock = cli_tokenizer_mock
-
@pytest.mark.parametrize("command,func", [
('address-levels', 'load_address_levels_from_config'),
('wiki-data', 'import_wikipedia_articles'),
@@ -33,17 +33,14 @@ class TestRefresh:
assert self.call_nominatim('refresh', '--' + command) == 0
assert func_mock.called == 1
-
def test_refresh_word_count(self):
assert self.call_nominatim('refresh', '--word-count') == 0
assert self.tokenizer_mock.update_statistics_called
-
def test_refresh_word_tokens(self):
assert self.call_nominatim('refresh', '--word-tokens') == 0
assert self.tokenizer_mock.update_word_tokens_called
-
def test_refresh_postcodes(self, async_mock_func_factory, mock_func_factory, place_table):
func_mock = mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes')
idx_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_postcodes')
@@ -52,12 +49,10 @@ class TestRefresh:
assert func_mock.called == 1
assert idx_mock.called == 1
-
def test_refresh_postcodes_no_place_table(self):
# Do nothing without the place table
assert self.call_nominatim('refresh', '--postcodes') == 0
-
def test_refresh_create_functions(self, mock_func_factory):
func_mock = mock_func_factory(nominatim_db.tools.refresh, 'create_functions')
@@ -65,17 +60,14 @@ class TestRefresh:
assert func_mock.called == 1
assert self.tokenizer_mock.update_sql_functions_called
-
def test_refresh_wikidata_file_not_found(self, monkeypatch):
monkeypatch.setenv('NOMINATIM_WIKIPEDIA_DATA_PATH', 'gjoiergjeroi345Q')
assert self.call_nominatim('refresh', '--wiki-data') == 1
-
def test_refresh_secondary_importance_file_not_found(self):
assert self.call_nominatim('refresh', '--secondary-importance') == 1
-
def test_refresh_secondary_importance_new_table(self, mock_func_factory):
mocks = [mock_func_factory(nominatim_db.tools.refresh, 'import_secondary_importance'),
mock_func_factory(nominatim_db.tools.refresh, 'create_functions')]
@@ -84,7 +76,6 @@ class TestRefresh:
assert mocks[0].called == 1
assert mocks[1].called == 1
-
def test_refresh_importance_computed_after_wiki_import(self, monkeypatch, mock_func_factory):
calls = []
monkeypatch.setattr(nominatim_db.tools.refresh, 'import_wikipedia_articles',
@@ -102,7 +93,8 @@ class TestRefresh:
('--data-object', 'N23', '--data-object', 'N24'),
('--data-area', 'R7723'),
('--data-area', 'r7723', '--data-area', 'r2'),
- ('--data-area', 'R9284425', '--data-object', 'n1234567894567')])
+ ('--data-area', 'R9284425',
+ '--data-object', 'n1234567894567')])
def test_refresh_objects(self, params, mock_func_factory):
func_mock = mock_func_factory(nominatim_db.tools.refresh, 'invalidate_osm_object')
@@ -110,7 +102,6 @@ class TestRefresh:
assert func_mock.called == len(params)/2
-
@pytest.mark.parametrize('func', ('--data-object', '--data-area'))
@pytest.mark.parametrize('param', ('234', 'a55', 'R 453', 'Rel'))
def test_refresh_objects_bad_param(self, func, param, mock_func_factory):
diff --git a/test/python/cli/test_cmd_replication.py b/test/python/cli/test_cmd_replication.py
index 21c6350d..34aafa3a 100644
--- a/test/python/cli/test_cmd_replication.py
+++ b/test/python/cli/test_cmd_replication.py
@@ -18,6 +18,7 @@ import nominatim_db.tools.replication
import nominatim_db.tools.refresh
from nominatim_db.db import status
+
@pytest.fixture
def tokenizer_mock(monkeypatch):
class DummyTokenizer:
@@ -40,7 +41,6 @@ def tokenizer_mock(monkeypatch):
return tok
-
@pytest.fixture
def init_status(temp_db_conn, status_table):
status.set_status(temp_db_conn, date=dt.datetime.now(dt.timezone.utc), seq=1)
@@ -62,16 +62,14 @@ class TestCliReplication:
def setup_cli_call(self, cli_call, temp_db):
self.call_nominatim = lambda *args: cli_call('replication', *args)
-
@pytest.fixture(autouse=True)
def setup_update_function(self, monkeypatch):
def _mock_updates(states):
monkeypatch.setattr(nominatim_db.tools.replication, 'update',
- lambda *args, **kwargs: states.pop())
+ lambda *args, **kwargs: states.pop())
self.update_states = _mock_updates
-
@pytest.mark.parametrize("params,func", [
(('--init',), 'init_replication'),
(('--init', '--no-update-functions'), 'init_replication'),
@@ -88,20 +86,17 @@ class TestCliReplication:
if params == ('--init',):
assert umock.called == 1
-
def test_replication_update_bad_interval(self, monkeypatch):
monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', 'xx')
assert self.call_nominatim() == 1
-
def test_replication_update_bad_interval_for_geofabrik(self, monkeypatch):
monkeypatch.setenv('NOMINATIM_REPLICATION_URL',
'https://download.geofabrik.de/europe/italy-updates')
assert self.call_nominatim() == 1
-
def test_replication_update_continuous_no_index(self):
assert self.call_nominatim('--no-index') == 1
@@ -110,14 +105,12 @@ class TestCliReplication:
assert str(update_mock.last_args[1]['osm2pgsql']).endswith('OSM2PGSQL NOT AVAILABLE')
-
def test_replication_update_custom_osm2pgsql(self, monkeypatch, update_mock):
monkeypatch.setenv('NOMINATIM_OSM2PGSQL_BINARY', '/secret/osm2pgsql')
assert self.call_nominatim('--once', '--no-index') == 0
assert str(update_mock.last_args[1]['osm2pgsql']) == '/secret/osm2pgsql'
-
@pytest.mark.parametrize("update_interval", [60, 3600])
def test_replication_catchup(self, placex_table, monkeypatch, index_mock, update_interval):
monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', str(update_interval))
@@ -125,13 +118,11 @@ class TestCliReplication:
assert self.call_nominatim('--catch-up') == 0
-
def test_replication_update_custom_threads(self, update_mock):
assert self.call_nominatim('--once', '--no-index', '--threads', '4') == 0
assert update_mock.last_args[1]['threads'] == 4
-
def test_replication_update_continuous(self, index_mock):
self.update_states([nominatim_db.tools.replication.UpdateState.UP_TO_DATE,
nominatim_db.tools.replication.UpdateState.UP_TO_DATE])
@@ -141,7 +132,6 @@ class TestCliReplication:
assert index_mock.called == 2
-
def test_replication_update_continuous_no_change(self, mock_func_factory,
index_mock):
self.update_states([nominatim_db.tools.replication.UpdateState.NO_CHANGES,
diff --git a/test/python/config/test_config.py b/test/python/config/test_config.py
index 9f68fcb9..a0dbf476 100644
--- a/test/python/config/test_config.py
+++ b/test/python/config/test_config.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for loading dotenv configuration.
@@ -13,6 +13,7 @@ import pytest
from nominatim_db.config import Configuration, flatten_config_list
from nominatim_db.errors import UsageError
+
@pytest.fixture
def make_config():
""" Create a configuration object from the given project directory.
@@ -22,6 +23,7 @@ def make_config():
return _mk_config
+
@pytest.fixture
def make_config_path(tmp_path):
""" Create a configuration object with project and config directories
@@ -108,7 +110,7 @@ def test_get_libpq_dsn_convert_php(make_config, monkeypatch):
@pytest.mark.parametrize("val,expect", [('foo bar', "'foo bar'"),
("xy'z", "xy\\'z"),
- ])
+ ])
def test_get_libpq_dsn_convert_php_special_chars(make_config, monkeypatch, val, expect):
config = make_config()
@@ -137,6 +139,7 @@ def test_get_bool(make_config, monkeypatch, value, result):
assert config.get_bool('FOOBAR') == result
+
def test_get_bool_empty(make_config):
config = make_config()
@@ -303,7 +306,7 @@ def test_load_subconf_env_absolute_not_found(make_config_path, monkeypatch, tmp_
(config.config_dir / 'test.yaml').write_text('cow: muh\ncat: miau\n')
with pytest.raises(UsageError, match='Config file not found.'):
- rules = config.load_sub_configuration('test.yaml', config='MY_CONFIG')
+ config.load_sub_configuration('test.yaml', config='MY_CONFIG')
@pytest.mark.parametrize("location", ['project_dir', 'config_dir'])
@@ -326,7 +329,7 @@ def test_load_subconf_env_relative_not_found(make_config_path, monkeypatch):
(config.config_dir / 'test.yaml').write_text('cow: muh\ncat: miau\n')
with pytest.raises(UsageError, match='Config file not found.'):
- rules = config.load_sub_configuration('test.yaml', config='MY_CONFIG')
+ config.load_sub_configuration('test.yaml', config='MY_CONFIG')
def test_load_subconf_json(make_config_path):
@@ -338,6 +341,7 @@ def test_load_subconf_json(make_config_path):
assert rules == dict(cow='muh', cat='miau')
+
def test_load_subconf_not_found(make_config_path):
config = make_config_path()
@@ -371,7 +375,7 @@ def test_load_subconf_include_relative(make_config_path, tmp_path, location):
config = make_config_path()
testfile = config.config_dir / 'test.yaml'
- testfile.write_text(f'base: !include inc.yaml\n')
+ testfile.write_text('base: !include inc.yaml\n')
(getattr(config, location) / 'inc.yaml').write_text('first: 1\nsecond: 2\n')
rules = config.load_sub_configuration('test.yaml')
@@ -383,28 +387,28 @@ def test_load_subconf_include_bad_format(make_config_path):
config = make_config_path()
testfile = config.config_dir / 'test.yaml'
- testfile.write_text(f'base: !include inc.txt\n')
+ testfile.write_text('base: !include inc.txt\n')
(config.config_dir / 'inc.txt').write_text('first: 1\nsecond: 2\n')
with pytest.raises(UsageError, match='Cannot handle config file format.'):
- rules = config.load_sub_configuration('test.yaml')
+ config.load_sub_configuration('test.yaml')
def test_load_subconf_include_not_found(make_config_path):
config = make_config_path()
testfile = config.config_dir / 'test.yaml'
- testfile.write_text(f'base: !include inc.txt\n')
+ testfile.write_text('base: !include inc.txt\n')
with pytest.raises(UsageError, match='Config file not found.'):
- rules = config.load_sub_configuration('test.yaml')
+ config.load_sub_configuration('test.yaml')
def test_load_subconf_include_recursive(make_config_path):
config = make_config_path()
testfile = config.config_dir / 'test.yaml'
- testfile.write_text(f'base: !include inc.yaml\n')
+ testfile.write_text('base: !include inc.yaml\n')
(config.config_dir / 'inc.yaml').write_text('- !include more.yaml\n- upper\n')
(config.config_dir / 'more.yaml').write_text('- the end\n')
@@ -435,6 +439,6 @@ def test_flatten_config_list_nested():
[[2, 3], [45, [56, 78], 66]],
'end'
]
+
assert flatten_config_list(content) == \
- [34, {'first': '1st', 'second': '2nd'}, {},
- 2, 3, 45, 56, 78, 66, 'end']
+ [34, {'first': '1st', 'second': '2nd'}, {}, 2, 3, 45, 56, 78, 66, 'end']
diff --git a/test/python/config/test_config_load_module.py b/test/python/config/test_config_load_module.py
index c2912180..309bd1fc 100644
--- a/test/python/config/test_config_load_module.py
+++ b/test/python/config/test_config_load_module.py
@@ -2,18 +2,18 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for loading extra Python modules.
"""
-from pathlib import Path
import sys
import pytest
from nominatim_db.config import Configuration
+
@pytest.fixture
def test_config(src_dir, tmp_path):
""" Create a configuration object with project and config directories
@@ -31,6 +31,7 @@ def test_load_default_module(test_config):
assert isinstance(module.NOMINATIM_VERSION, tuple)
+
def test_load_default_module_with_hyphen(test_config):
module = test_config.load_plugin_module('place-info', 'nominatim_db.data')
diff --git a/test/python/conftest.py b/test/python/conftest.py
index a25ff8ec..f95da39e 100644
--- a/test/python/conftest.py
+++ b/test/python/conftest.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
import itertools
import sys
@@ -69,6 +69,7 @@ def temp_db_with_extensions(temp_db):
return temp_db
+
@pytest.fixture
def temp_db_conn(temp_db):
""" Connection to the test database.
@@ -100,8 +101,9 @@ def table_factory(temp_db_conn):
if content:
sql = pysql.SQL("INSERT INTO {} VALUES ({})")\
.format(pysql.Identifier(name),
- pysql.SQL(',').join([pysql.Placeholder() for _ in range(len(content[0]))]))
- cur.executemany(sql , content)
+ pysql.SQL(',').join([pysql.Placeholder()
+ for _ in range(len(content[0]))]))
+ cur.executemany(sql, content)
return mk_table
@@ -178,6 +180,7 @@ def place_row(place_table, temp_db_cursor):
return _insert
+
@pytest.fixture
def placex_table(temp_db_with_extensions, temp_db_conn):
""" Create an empty version of the place table.
diff --git a/test/python/cursor.py b/test/python/cursor.py
index b3fc260a..5dc93cd5 100644
--- a/test/python/cursor.py
+++ b/test/python/cursor.py
@@ -2,13 +2,14 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Specialised psycopg cursor with shortcut functions useful for testing.
"""
import psycopg
+
class CursorForTesting(psycopg.Cursor):
""" Extension to the DictCursor class that provides execution
short-cuts that simplify writing assertions.
@@ -22,7 +23,6 @@ class CursorForTesting(psycopg.Cursor):
assert self.rowcount == 1
return self.fetchone()[0]
-
def row_set(self, sql, params=None):
""" Execute a query and return the result as a set of tuples.
Fails when the SQL command returns duplicate rows.
@@ -34,7 +34,6 @@ class CursorForTesting(psycopg.Cursor):
return result
-
def table_exists(self, table):
""" Check that a table with the given name exists in the database.
"""
@@ -42,7 +41,6 @@ class CursorForTesting(psycopg.Cursor):
WHERE tablename = %s""", (table, ))
return num == 1
-
def index_exists(self, table, index):
""" Check that an indexwith the given name exists on the given table.
"""
@@ -51,7 +49,6 @@ class CursorForTesting(psycopg.Cursor):
(table, index))
return num == 1
-
def table_rows(self, table, where=None):
""" Return the number of rows in the given table.
"""
diff --git a/test/python/data/test_country_info.py b/test/python/data/test_country_info.py
index 14b306bb..a85b7bf9 100644
--- a/test/python/data/test_country_info.py
+++ b/test/python/data/test_country_info.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for function that handle country properties.
@@ -12,6 +12,7 @@ import pytest
from nominatim_db.data import country_info
+
@pytest.fixture
def loaded_country(def_config):
country_info.setup_country_config(def_config)
@@ -115,8 +116,8 @@ def test_setup_country_config_languages_not_loaded(env_with_country_config):
info = country_info._CountryInfo()
info.load(config)
assert dict(info.items()) == {'de': {'partition': 3,
- 'languages': [],
- 'names': {'name': 'Deutschland'}}}
+ 'languages': [],
+ 'names': {'name': 'Deutschland'}}}
def test_setup_country_config_name_not_loaded(env_with_country_config):
@@ -132,8 +133,7 @@ def test_setup_country_config_name_not_loaded(env_with_country_config):
assert dict(info.items()) == {'de': {'partition': 3,
'languages': ['de'],
- 'names': {}
- }}
+ 'names': {}}}
def test_setup_country_config_names_not_loaded(env_with_country_config):
@@ -148,8 +148,7 @@ def test_setup_country_config_names_not_loaded(env_with_country_config):
assert dict(info.items()) == {'de': {'partition': 3,
'languages': ['de'],
- 'names': {}
- }}
+ 'names': {}}}
def test_setup_country_config_special_character(env_with_country_config):
@@ -157,8 +156,8 @@ def test_setup_country_config_special_character(env_with_country_config):
bq:
partition: 250
languages: nl
- names:
- name:
+ names:
+ name:
default: "\\N"
""")
@@ -167,5 +166,4 @@ def test_setup_country_config_special_character(env_with_country_config):
assert dict(info.items()) == {'bq': {'partition': 250,
'languages': ['nl'],
- 'names': {'name': '\x85'}
- }}
+ 'names': {'name': '\x85'}}}
diff --git a/test/python/db/test_connection.py b/test/python/db/test_connection.py
index a8b5d677..19b945fd 100644
--- a/test/python/db/test_connection.py
+++ b/test/python/db/test_connection.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for specialised connection and cursor classes.
@@ -12,6 +12,7 @@ import psycopg
import nominatim_db.db.connection as nc
+
@pytest.fixture
def db(dsn):
with nc.connect(dsn) as conn:
@@ -36,6 +37,7 @@ def test_has_column(db, table_factory, name, result):
assert nc.table_has_column(db, 'stuff', name) == result
+
def test_connection_index_exists(db, table_factory, temp_db_cursor):
assert not nc.index_exists(db, 'some_index')
@@ -76,6 +78,7 @@ def test_drop_table_non_existing_force(db):
with pytest.raises(psycopg.ProgrammingError, match='.*does not exist.*'):
nc.drop_tables(db, 'dfkjgjriogjigjgjrdghehtre', if_exists=False)
+
def test_connection_server_version_tuple(db):
ver = nc.server_version_tuple(db)
diff --git a/test/python/db/test_properties.py b/test/python/db/test_properties.py
index e55bb973..84d7dae0 100644
--- a/test/python/db/test_properties.py
+++ b/test/python/db/test_properties.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for property table manpulation.
@@ -11,6 +11,7 @@ import pytest
from nominatim_db.db import properties
+
@pytest.fixture
def property_factory(property_table, temp_db_cursor):
""" A function fixture that adds a property into the property table.
diff --git a/test/python/db/test_sql_preprocessor.py b/test/python/db/test_sql_preprocessor.py
index 45109c70..f2fbbb2a 100644
--- a/test/python/db/test_sql_preprocessor.py
+++ b/test/python/db/test_sql_preprocessor.py
@@ -2,16 +2,17 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for SQL preprocessing.
"""
import pytest
-import pytest_asyncio
+import pytest_asyncio # noqa
from nominatim_db.db.sql_preprocessor import SQLPreprocessor
+
@pytest.fixture
def sql_factory(tmp_path):
def _mk_sql(sql_body):
@@ -26,6 +27,7 @@ def sql_factory(tmp_path):
return _mk_sql
+
@pytest.mark.parametrize("expr,ret", [
("'a'", 'a'),
("'{{db.partitions|join}}'", '012'),
@@ -61,8 +63,7 @@ def test_load_file_with_params(sql_preprocessor, sql_factory, temp_db_conn, temp
async def test_load_parallel_file(dsn, sql_preprocessor, tmp_path, temp_db_cursor):
(tmp_path / 'test.sql').write_text("""
CREATE TABLE foo (a TEXT);
- CREATE TABLE foo2(a TEXT);""" +
- "\n---\nCREATE TABLE bar (b INT);")
+ CREATE TABLE foo2(a TEXT);""" + "\n---\nCREATE TABLE bar (b INT);")
await sql_preprocessor.run_parallel_sql_file(dsn, 'test.sql', num_threads=4)
diff --git a/test/python/db/test_status.py b/test/python/db/test_status.py
index 77135a8c..462b8e3d 100644
--- a/test/python/db/test_status.py
+++ b/test/python/db/test_status.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for status table manipulation.
@@ -19,7 +19,8 @@ OSM_NODE_DATA = """\
-"""
+""" # noqa
+
def iso_date(date):
return dt.datetime.strptime(date, nominatim_db.db.status.ISODATE_FORMAT)\
@@ -43,7 +44,8 @@ def test_compute_database_date_from_osm2pgsql(table_factory, temp_db_conn, offli
def test_compute_database_date_from_osm2pgsql_nodata(table_factory, temp_db_conn):
table_factory('osm2pgsql_properties', 'property TEXT, value TEXT')
- with pytest.raises(UsageError, match='Cannot determine database date from data in offline mode'):
+ with pytest.raises(UsageError,
+ match='Cannot determine database date from data in offline mode'):
nominatim_db.db.status.compute_database_date(temp_db_conn, offline=True)
@@ -56,6 +58,7 @@ def test_compute_database_date_valid(monkeypatch, place_row, temp_db_conn):
place_row(osm_type='N', osm_id=45673)
requested_url = []
+
def mock_url(url):
requested_url.append(url)
return OSM_NODE_DATA
@@ -72,6 +75,7 @@ def test_compute_database_broken_api(monkeypatch, place_row, temp_db_conn):
place_row(osm_type='N', osm_id=45673)
requested_url = []
+
def mock_url(url):
requested_url.append(url)
return ' ' '"), sanitizers=[hnr], with_housenumber=True) as anl:
+ with analyzer(trans=(":: upper()", "'🜵' > ' '"), sanitizers=[hnr],
+ with_housenumber=True) as anl:
self.analyzer = anl
yield anl
-
@pytest.fixture
def getorcreate_hnr_id(self, temp_db_cursor):
- temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION create_analyzed_hnr_id(norm_term TEXT, lookup_terms TEXT[])
- RETURNS INTEGER AS $$
- SELECT -nextval('seq_word')::INTEGER; $$ LANGUAGE SQL""")
-
+ temp_db_cursor.execute("""
+ CREATE OR REPLACE FUNCTION create_analyzed_hnr_id(norm_term TEXT, lookup_terms TEXT[])
+ RETURNS INTEGER AS $$
+ SELECT -nextval('seq_word')::INTEGER; $$ LANGUAGE SQL""")
def process_address(self, **kwargs):
return self.analyzer.process_place(PlaceInfo({'address': kwargs}))
-
def name_token_set(self, *expected_terms):
tokens = self.analyzer.get_word_token_info(expected_terms)
for token in tokens:
@@ -598,7 +570,6 @@ class TestPlaceHousenumberWithAnalyser:
return set((t[2] for t in tokens))
-
@pytest.mark.parametrize('hnr', ['123 a', '1', '101'])
def test_process_place_housenumbers_simple(self, hnr, getorcreate_hnr_id):
info = self.process_address(housenumber=hnr)
@@ -606,7 +577,6 @@ class TestPlaceHousenumberWithAnalyser:
assert info['hnr'] == hnr.upper()
assert info['hnr_tokens'] == "{-1}"
-
def test_process_place_housenumbers_duplicates(self, getorcreate_hnr_id):
info = self.process_address(housenumber='134',
conscriptionnumber='134',
@@ -615,7 +585,6 @@ class TestPlaceHousenumberWithAnalyser:
assert set(info['hnr'].split(';')) == set(('134', '99 A'))
assert info['hnr_tokens'] == "{-1,-2}"
-
def test_process_place_housenumbers_cached(self, getorcreate_hnr_id):
info = self.process_address(housenumber="45")
assert info['hnr_tokens'] == "{-1}"
@@ -637,7 +606,6 @@ class TestUpdateWordTokens:
table_factory('search_name', 'place_id BIGINT, name_vector INT[]')
self.tok = tokenizer_factory()
-
@pytest.fixture
def search_entry(self, temp_db_cursor):
place_id = itertools.count(1000)
@@ -648,7 +616,6 @@ class TestUpdateWordTokens:
return _insert
-
@pytest.fixture(params=['simple', 'analyzed'])
def add_housenumber(self, request, word_table):
if request.param == 'simple':
@@ -660,7 +627,6 @@ class TestUpdateWordTokens:
return _make
-
@pytest.mark.parametrize('hnr', ('1a', '1234567', '34 5'))
def test_remove_unused_housenumbers(self, add_housenumber, word_table, hnr):
word_table.add_housenumber(1000, hnr)
@@ -669,7 +635,6 @@ class TestUpdateWordTokens:
self.tok.update_word_tokens()
assert word_table.count_housenumbers() == 0
-
def test_keep_unused_numeral_housenumbers(self, add_housenumber, word_table):
add_housenumber(1000, '5432')
@@ -677,8 +642,8 @@ class TestUpdateWordTokens:
self.tok.update_word_tokens()
assert word_table.count_housenumbers() == 1
-
- def test_keep_housenumbers_from_search_name_table(self, add_housenumber, word_table, search_entry):
+ def test_keep_housenumbers_from_search_name_table(self, add_housenumber,
+ word_table, search_entry):
add_housenumber(9999, '5432a')
add_housenumber(9991, '9 a')
search_entry(123, 9999, 34)
@@ -687,8 +652,8 @@ class TestUpdateWordTokens:
self.tok.update_word_tokens()
assert word_table.count_housenumbers() == 1
-
- def test_keep_housenumbers_from_placex_table(self, add_housenumber, word_table, placex_table):
+ def test_keep_housenumbers_from_placex_table(self, add_housenumber, word_table,
+ placex_table):
add_housenumber(9999, '5432a')
add_housenumber(9990, '34z')
placex_table.add(housenumber='34z')
@@ -698,8 +663,8 @@ class TestUpdateWordTokens:
self.tok.update_word_tokens()
assert word_table.count_housenumbers() == 1
-
- def test_keep_housenumbers_from_placex_table_hnr_list(self, add_housenumber, word_table, placex_table):
+ def test_keep_housenumbers_from_placex_table_hnr_list(self, add_housenumber,
+ word_table, placex_table):
add_housenumber(9991, '9 b')
add_housenumber(9990, '34z')
placex_table.add(housenumber='9 a;9 b;9 c')
diff --git a/test/python/tokenizer/test_icu_rule_loader.py b/test/python/tokenizer/test_icu_rule_loader.py
index a3fae758..f26b84c2 100644
--- a/test/python/tokenizer/test_icu_rule_loader.py
+++ b/test/python/tokenizer/test_icu_rule_loader.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for converting a config file to ICU rules.
@@ -19,17 +19,16 @@ from icu import Transliterator
CONFIG_SECTIONS = ('normalization', 'transliteration', 'token-analysis')
+
class TestIcuRuleLoader:
@pytest.fixture(autouse=True)
def init_env(self, project_env):
self.project_env = project_env
-
def write_config(self, content):
(self.project_env.project_dir / 'icu_tokenizer.yaml').write_text(dedent(content))
-
def config_rules(self, *variants):
content = dedent("""\
normalization:
@@ -49,14 +48,12 @@ class TestIcuRuleLoader:
content += '\n'.join((" - " + s for s in variants)) + '\n'
self.write_config(content)
-
def get_replacements(self, *variants):
self.config_rules(*variants)
loader = ICURuleLoader(self.project_env)
rules = loader.analysis[None].config['replacements']
- return sorted((k, sorted(v)) for k,v in rules)
-
+ return sorted((k, sorted(v)) for k, v in rules)
def test_empty_rule_set(self):
self.write_config("""\
@@ -72,16 +69,14 @@ class TestIcuRuleLoader:
assert rules.get_normalization_rules() == ''
assert rules.get_transliteration_rules() == ''
-
@pytest.mark.parametrize("section", CONFIG_SECTIONS)
def test_missing_section(self, section):
- rule_cfg = { s: [] for s in CONFIG_SECTIONS if s != section}
+ rule_cfg = {s: [] for s in CONFIG_SECTIONS if s != section}
self.write_config(yaml.dump(rule_cfg))
with pytest.raises(UsageError):
ICURuleLoader(self.project_env)
-
def test_get_search_rules(self):
self.config_rules()
loader = ICURuleLoader(self.project_env)
@@ -97,7 +92,6 @@ class TestIcuRuleLoader:
assert trans.transliterate(" Αθήνα ") == " athēna "
assert trans.transliterate(" проспект ") == " prospekt "
-
def test_get_normalization_rules(self):
self.config_rules()
loader = ICURuleLoader(self.project_env)
@@ -106,7 +100,6 @@ class TestIcuRuleLoader:
assert trans.transliterate(" проспект-Prospekt ") == " проспект prospekt "
-
def test_get_transliteration_rules(self):
self.config_rules()
loader = ICURuleLoader(self.project_env)
@@ -115,7 +108,6 @@ class TestIcuRuleLoader:
assert trans.transliterate(" проспект-Prospekt ") == " prospekt Prospekt "
-
def test_transliteration_rules_from_file(self):
self.write_config("""\
normalization:
@@ -135,7 +127,6 @@ class TestIcuRuleLoader:
assert trans.transliterate(" axxt ") == " byt "
-
def test_search_rules(self):
self.config_rules('~street => s,st', 'master => mstr')
proc = ICURuleLoader(self.project_env).make_token_analysis()
@@ -144,7 +135,6 @@ class TestIcuRuleLoader:
assert proc.search.transliterate('Earnes St').strip() == 'earnes st'
assert proc.search.transliterate('Nostreet').strip() == 'nostreet'
-
@pytest.mark.parametrize("variant", ['foo > bar', 'foo -> bar -> bar',
'~foo~ -> bar', 'fo~ o -> bar'])
def test_invalid_variant_description(self, variant):
@@ -157,25 +147,21 @@ class TestIcuRuleLoader:
assert repl == [(' foo ', [' bar', ' foo'])]
-
def test_replace_full(self):
repl = self.get_replacements("foo => bar")
assert repl == [(' foo ', [' bar'])]
-
def test_add_suffix_no_decompose(self):
repl = self.get_replacements("~berg |-> bg")
assert repl == [(' berg ', [' berg', ' bg']),
('berg ', ['berg', 'bg'])]
-
def test_replace_suffix_no_decompose(self):
repl = self.get_replacements("~berg |=> bg")
- assert repl == [(' berg ', [' bg']),('berg ', ['bg'])]
-
+ assert repl == [(' berg ', [' bg']), ('berg ', ['bg'])]
def test_add_suffix_decompose(self):
repl = self.get_replacements("~berg -> bg")
@@ -183,26 +169,22 @@ class TestIcuRuleLoader:
assert repl == [(' berg ', [' berg', ' bg', 'berg', 'bg']),
('berg ', [' berg', ' bg', 'berg', 'bg'])]
-
def test_replace_suffix_decompose(self):
repl = self.get_replacements("~berg => bg")
assert repl == [(' berg ', [' bg', 'bg']),
('berg ', [' bg', 'bg'])]
-
def test_add_prefix_no_compose(self):
repl = self.get_replacements("hinter~ |-> hnt")
assert repl == [(' hinter', [' hinter', ' hnt']),
(' hinter ', [' hinter', ' hnt'])]
-
def test_replace_prefix_no_compose(self):
repl = self.get_replacements("hinter~ |=> hnt")
- assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])]
-
+ assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])]
def test_add_prefix_compose(self):
repl = self.get_replacements("hinter~-> h")
@@ -210,45 +192,38 @@ class TestIcuRuleLoader:
assert repl == [(' hinter', [' h', ' h ', ' hinter', ' hinter ']),
(' hinter ', [' h', ' h', ' hinter', ' hinter'])]
-
def test_replace_prefix_compose(self):
repl = self.get_replacements("hinter~=> h")
assert repl == [(' hinter', [' h', ' h ']),
(' hinter ', [' h', ' h'])]
-
def test_add_beginning_only(self):
repl = self.get_replacements("^Premier -> Pr")
assert repl == [('^ premier ', ['^ pr', '^ premier'])]
-
def test_replace_beginning_only(self):
repl = self.get_replacements("^Premier => Pr")
assert repl == [('^ premier ', ['^ pr'])]
-
def test_add_final_only(self):
repl = self.get_replacements("road$ -> rd")
assert repl == [(' road ^', [' rd ^', ' road ^'])]
-
def test_replace_final_only(self):
repl = self.get_replacements("road$ => rd")
assert repl == [(' road ^', [' rd ^'])]
-
def test_decompose_only(self):
repl = self.get_replacements("~foo -> foo")
assert repl == [(' foo ', [' foo', 'foo']),
('foo ', [' foo', 'foo'])]
-
def test_add_suffix_decompose_end_only(self):
repl = self.get_replacements("~berg |-> bg", "~berg$ -> bg")
@@ -257,7 +232,6 @@ class TestIcuRuleLoader:
('berg ', ['berg', 'bg']),
('berg ^', [' berg ^', ' bg ^', 'berg ^', 'bg ^'])]
-
def test_replace_suffix_decompose_end_only(self):
repl = self.get_replacements("~berg |=> bg", "~berg$ => bg")
@@ -266,7 +240,6 @@ class TestIcuRuleLoader:
('berg ', ['bg']),
('berg ^', [' bg ^', 'bg ^'])]
-
def test_add_multiple_suffix(self):
repl = self.get_replacements("~berg,~burg -> bg")
diff --git a/test/python/tokenizer/test_place_sanitizer.py b/test/python/tokenizer/test_place_sanitizer.py
index 25844459..fcf02bd3 100644
--- a/test/python/tokenizer/test_place_sanitizer.py
+++ b/test/python/tokenizer/test_place_sanitizer.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for execution of the sanitztion step.
@@ -50,13 +50,13 @@ def test_placeinfo_has_attr():
def test_sanitizer_default(def_config):
san = sanitizer.PlaceSanitizer([{'step': 'split-name-list'}], def_config)
- name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'},
- 'address': {'street': 'Bald'}}))
+ name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'},
+ 'address': {'street': 'Bald'}}))
assert len(name) == 3
assert all(isinstance(n, sanitizer.PlaceName) for n in name)
- assert all(n.kind == 'name' for n in name)
- assert all(n.suffix == 'de:de' for n in name)
+ assert all(n.kind == 'name' for n in name)
+ assert all(n.suffix == 'de:de' for n in name)
assert len(address) == 1
assert all(isinstance(n, sanitizer.PlaceName) for n in address)
@@ -66,7 +66,7 @@ def test_sanitizer_default(def_config):
def test_sanitizer_empty_list(def_config, rules):
san = sanitizer.PlaceSanitizer(rules, def_config)
- name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}}))
+ name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}}))
assert len(name) == 1
assert all(isinstance(n, sanitizer.PlaceName) for n in name)
@@ -74,4 +74,4 @@ def test_sanitizer_empty_list(def_config, rules):
def test_sanitizer_missing_step_definition(def_config):
with pytest.raises(UsageError):
- san = sanitizer.PlaceSanitizer([{'id': 'split-name-list'}], def_config)
+ sanitizer.PlaceSanitizer([{'id': 'split-name-list'}], def_config)
diff --git a/test/python/tokenizer/token_analysis/test_analysis_postcodes.py b/test/python/tokenizer/token_analysis/test_analysis_postcodes.py
index 870c8a5d..1eb15a50 100644
--- a/test/python/tokenizer/token_analysis/test_analysis_postcodes.py
+++ b/test/python/tokenizer/token_analysis/test_analysis_postcodes.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for special postcode analysis and variant generation.
@@ -13,7 +13,6 @@ from icu import Transliterator
import nominatim_db.tokenizer.token_analysis.postcodes as module
from nominatim_db.data.place_name import PlaceName
-from nominatim_db.errors import UsageError
DEFAULT_NORMALIZATION = """ :: NFD ();
'🜳' > ' ';
@@ -27,9 +26,10 @@ DEFAULT_TRANSLITERATION = """ :: Latin ();
'🜵' > ' ';
"""
+
@pytest.fixture
def analyser():
- rules = { 'analyzer': 'postcodes'}
+ rules = {'analyzer': 'postcodes'}
config = module.configure(rules, DEFAULT_NORMALIZATION)
trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION)
diff --git a/test/python/tokenizer/token_analysis/test_generic.py b/test/python/tokenizer/token_analysis/test_generic.py
index 191f551f..02870f24 100644
--- a/test/python/tokenizer/token_analysis/test_generic.py
+++ b/test/python/tokenizer/token_analysis/test_generic.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for import name normalisation and variant generation.
@@ -26,8 +26,9 @@ DEFAULT_TRANSLITERATION = """ :: Latin ();
'🜵' > ' ';
"""
+
def make_analyser(*variants, variant_only=False):
- rules = { 'analyzer': 'generic', 'variants': [{'words': variants}]}
+ rules = {'analyzer': 'generic', 'variants': [{'words': variants}]}
if variant_only:
rules['mode'] = 'variant-only'
trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION)
@@ -43,7 +44,7 @@ def get_normalized_variants(proc, name):
def test_no_variants():
- rules = { 'analyzer': 'generic' }
+ rules = {'analyzer': 'generic'}
trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION)
norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION)
config = module.configure(rules, norm, trans)
@@ -62,35 +63,36 @@ def test_variants_empty():
VARIANT_TESTS = [
-(('~strasse,~straße -> str', '~weg => weg'), "hallo", {'hallo'}),
-(('weg => wg',), "holzweg", {'holzweg'}),
-(('weg -> wg',), "holzweg", {'holzweg'}),
-(('~weg => weg',), "holzweg", {'holz weg', 'holzweg'}),
-(('~weg -> weg',), "holzweg", {'holz weg', 'holzweg'}),
-(('~weg => w',), "holzweg", {'holz w', 'holzw'}),
-(('~weg -> w',), "holzweg", {'holz weg', 'holzweg', 'holz w', 'holzw'}),
-(('~weg => weg',), "Meier Weg", {'meier weg', 'meierweg'}),
-(('~weg -> weg',), "Meier Weg", {'meier weg', 'meierweg'}),
-(('~weg => w',), "Meier Weg", {'meier w', 'meierw'}),
-(('~weg -> w',), "Meier Weg", {'meier weg', 'meierweg', 'meier w', 'meierw'}),
-(('weg => wg',), "Meier Weg", {'meier wg'}),
-(('weg -> wg',), "Meier Weg", {'meier weg', 'meier wg'}),
-(('~strasse,~straße -> str', '~weg => weg'), "Bauwegstraße",
+ (('~strasse,~straße -> str', '~weg => weg'), "hallo", {'hallo'}),
+ (('weg => wg',), "holzweg", {'holzweg'}),
+ (('weg -> wg',), "holzweg", {'holzweg'}),
+ (('~weg => weg',), "holzweg", {'holz weg', 'holzweg'}),
+ (('~weg -> weg',), "holzweg", {'holz weg', 'holzweg'}),
+ (('~weg => w',), "holzweg", {'holz w', 'holzw'}),
+ (('~weg -> w',), "holzweg", {'holz weg', 'holzweg', 'holz w', 'holzw'}),
+ (('~weg => weg',), "Meier Weg", {'meier weg', 'meierweg'}),
+ (('~weg -> weg',), "Meier Weg", {'meier weg', 'meierweg'}),
+ (('~weg => w',), "Meier Weg", {'meier w', 'meierw'}),
+ (('~weg -> w',), "Meier Weg", {'meier weg', 'meierweg', 'meier w', 'meierw'}),
+ (('weg => wg',), "Meier Weg", {'meier wg'}),
+ (('weg -> wg',), "Meier Weg", {'meier weg', 'meier wg'}),
+ (('~strasse,~straße -> str', '~weg => weg'), "Bauwegstraße",
{'bauweg straße', 'bauweg str', 'bauwegstraße', 'bauwegstr'}),
-(('am => a', 'bach => b'), "am bach", {'a b'}),
-(('am => a', '~bach => b'), "am bach", {'a b'}),
-(('am -> a', '~bach -> b'), "am bach", {'am bach', 'a bach', 'am b', 'a b'}),
-(('am -> a', '~bach -> b'), "ambach", {'ambach', 'am bach', 'amb', 'am b'}),
-(('saint -> s,st', 'street -> st'), "Saint Johns Street",
+ (('am => a', 'bach => b'), "am bach", {'a b'}),
+ (('am => a', '~bach => b'), "am bach", {'a b'}),
+ (('am -> a', '~bach -> b'), "am bach", {'am bach', 'a bach', 'am b', 'a b'}),
+ (('am -> a', '~bach -> b'), "ambach", {'ambach', 'am bach', 'amb', 'am b'}),
+ (('saint -> s,st', 'street -> st'), "Saint Johns Street",
{'saint johns street', 's johns street', 'st johns street',
'saint johns st', 's johns st', 'st johns st'}),
-(('river$ -> r',), "River Bend Road", {'river bend road'}),
-(('river$ -> r',), "Bent River", {'bent river', 'bent r'}),
-(('^north => n',), "North 2nd Street", {'n 2nd street'}),
-(('^north => n',), "Airport North", {'airport north'}),
-(('am -> a',), "am am am am am am am am", {'am am am am am am am am'}),
-(('am => a',), "am am am am am am am am", {'a a a a a a a a'})
-]
+ (('river$ -> r',), "River Bend Road", {'river bend road'}),
+ (('river$ -> r',), "Bent River", {'bent river', 'bent r'}),
+ (('^north => n',), "North 2nd Street", {'n 2nd street'}),
+ (('^north => n',), "Airport North", {'airport north'}),
+ (('am -> a',), "am am am am am am am am", {'am am am am am am am am'}),
+ (('am => a',), "am am am am am am am am", {'a a a a a a a a'})
+ ]
+
@pytest.mark.parametrize("rules,name,variants", VARIANT_TESTS)
def test_variants(rules, name, variants):
@@ -103,10 +105,11 @@ def test_variants(rules, name, variants):
VARIANT_ONLY_TESTS = [
-(('weg => wg',), "hallo", set()),
-(('weg => wg',), "Meier Weg", {'meier wg'}),
-(('weg -> wg',), "Meier Weg", {'meier wg'}),
-]
+ (('weg => wg',), "hallo", set()),
+ (('weg => wg',), "Meier Weg", {'meier wg'}),
+ (('weg -> wg',), "Meier Weg", {'meier wg'}),
+ ]
+
@pytest.mark.parametrize("rules,name,variants", VARIANT_ONLY_TESTS)
def test_variants_only(rules, name, variants):
@@ -122,17 +125,15 @@ class TestGetReplacements:
@staticmethod
def configure_rules(*variants):
- rules = { 'analyzer': 'generic', 'variants': [{'words': variants}]}
+ rules = {'analyzer': 'generic', 'variants': [{'words': variants}]}
trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION)
norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION)
return module.configure(rules, norm, trans)
-
def get_replacements(self, *variants):
config = self.configure_rules(*variants)
- return sorted((k, sorted(v)) for k,v in config['replacements'])
-
+ return sorted((k, sorted(v)) for k, v in config['replacements'])
@pytest.mark.parametrize("variant", ['foo > bar', 'foo -> bar -> bar',
'~foo~ -> bar', 'fo~ o -> bar'])
@@ -140,38 +141,32 @@ class TestGetReplacements:
with pytest.raises(UsageError):
self.configure_rules(variant)
-
@pytest.mark.parametrize("rule", ["!!! -> bar", "bar => !!!"])
def test_ignore_unnormalizable_terms(self, rule):
repl = self.get_replacements(rule)
assert repl == []
-
def test_add_full(self):
repl = self.get_replacements("foo -> bar")
assert repl == [(' foo ', [' bar', ' foo'])]
-
def test_replace_full(self):
repl = self.get_replacements("foo => bar")
assert repl == [(' foo ', [' bar'])]
-
def test_add_suffix_no_decompose(self):
repl = self.get_replacements("~berg |-> bg")
assert repl == [(' berg ', [' berg', ' bg']),
('berg ', ['berg', 'bg'])]
-
def test_replace_suffix_no_decompose(self):
repl = self.get_replacements("~berg |=> bg")
- assert repl == [(' berg ', [' bg']),('berg ', ['bg'])]
-
+ assert repl == [(' berg ', [' bg']), ('berg ', ['bg'])]
def test_add_suffix_decompose(self):
repl = self.get_replacements("~berg -> bg")
@@ -179,26 +174,22 @@ class TestGetReplacements:
assert repl == [(' berg ', [' berg', ' bg', 'berg', 'bg']),
('berg ', [' berg', ' bg', 'berg', 'bg'])]
-
def test_replace_suffix_decompose(self):
repl = self.get_replacements("~berg => bg")
assert repl == [(' berg ', [' bg', 'bg']),
('berg ', [' bg', 'bg'])]
-
def test_add_prefix_no_compose(self):
repl = self.get_replacements("hinter~ |-> hnt")
assert repl == [(' hinter', [' hinter', ' hnt']),
(' hinter ', [' hinter', ' hnt'])]
-
def test_replace_prefix_no_compose(self):
repl = self.get_replacements("hinter~ |=> hnt")
- assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])]
-
+ assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])]
def test_add_prefix_compose(self):
repl = self.get_replacements("hinter~-> h")
@@ -206,45 +197,38 @@ class TestGetReplacements:
assert repl == [(' hinter', [' h', ' h ', ' hinter', ' hinter ']),
(' hinter ', [' h', ' h', ' hinter', ' hinter'])]
-
def test_replace_prefix_compose(self):
repl = self.get_replacements("hinter~=> h")
assert repl == [(' hinter', [' h', ' h ']),
(' hinter ', [' h', ' h'])]
-
def test_add_beginning_only(self):
repl = self.get_replacements("^Premier -> Pr")
assert repl == [('^ premier ', ['^ pr', '^ premier'])]
-
def test_replace_beginning_only(self):
repl = self.get_replacements("^Premier => Pr")
assert repl == [('^ premier ', ['^ pr'])]
-
def test_add_final_only(self):
repl = self.get_replacements("road$ -> rd")
assert repl == [(' road ^', [' rd ^', ' road ^'])]
-
def test_replace_final_only(self):
repl = self.get_replacements("road$ => rd")
assert repl == [(' road ^', [' rd ^'])]
-
def test_decompose_only(self):
repl = self.get_replacements("~foo -> foo")
assert repl == [(' foo ', [' foo', 'foo']),
('foo ', [' foo', 'foo'])]
-
def test_add_suffix_decompose_end_only(self):
repl = self.get_replacements("~berg |-> bg", "~berg$ -> bg")
@@ -253,7 +237,6 @@ class TestGetReplacements:
('berg ', ['berg', 'bg']),
('berg ^', [' berg ^', ' bg ^', 'berg ^', 'bg ^'])]
-
def test_replace_suffix_decompose_end_only(self):
repl = self.get_replacements("~berg |=> bg", "~berg$ => bg")
@@ -262,7 +245,6 @@ class TestGetReplacements:
('berg ', ['bg']),
('berg ^', [' bg ^', 'bg ^'])]
-
@pytest.mark.parametrize('rule', ["~berg,~burg -> bg",
"~berg, ~burg -> bg",
"~berg,,~burg -> bg"])
diff --git a/test/python/tokenizer/token_analysis/test_generic_mutation.py b/test/python/tokenizer/token_analysis/test_generic_mutation.py
index 7d0db925..2ce2236a 100644
--- a/test/python/tokenizer/token_analysis/test_generic_mutation.py
+++ b/test/python/tokenizer/token_analysis/test_generic_mutation.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for generic token analysis, mutation part.
@@ -24,37 +24,34 @@ DEFAULT_TRANSLITERATION = """ :: Latin ();
'🜵' > ' ';
"""
+
class TestMutationNoVariants:
def make_analyser(self, *mutations):
- rules = { 'analyzer': 'generic',
- 'mutations': [ {'pattern': m[0], 'replacements': m[1]}
- for m in mutations]
- }
+ rules = {'analyzer': 'generic',
+ 'mutations': [{'pattern': m[0], 'replacements': m[1]}
+ for m in mutations]
+ }
trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION)
norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION)
config = module.configure(rules, norm, trans)
self.analysis = module.create(norm, trans, config)
-
def variants(self, name):
norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION)
return set(self.analysis.compute_variants(norm.transliterate(name).strip()))
-
@pytest.mark.parametrize('pattern', ('(capture)', ['a list']))
def test_bad_pattern(self, pattern):
with pytest.raises(UsageError):
self.make_analyser((pattern, ['b']))
-
@pytest.mark.parametrize('replacements', (None, 'a string'))
def test_bad_replacement(self, replacements):
with pytest.raises(UsageError):
self.make_analyser(('a', replacements))
-
def test_simple_replacement(self):
self.make_analyser(('a', ['b']))
@@ -62,27 +59,23 @@ class TestMutationNoVariants:
assert self.variants('abba') == {'bbbb'}
assert self.variants('2 aar') == {'2 bbr'}
-
def test_multichar_replacement(self):
self.make_analyser(('1 1', ['1 1 1']))
assert self.variants('1 1456') == {'1 1 1456'}
assert self.variants('1 1 1') == {'1 1 1 1'}
-
def test_removement_replacement(self):
self.make_analyser((' ', [' ', '']))
assert self.variants('A 345') == {'a 345', 'a345'}
assert self.variants('a g b') == {'a g b', 'ag b', 'a gb', 'agb'}
-
def test_regex_pattern(self):
self.make_analyser(('[^a-z]+', ['XXX', ' ']))
assert self.variants('a-34n12') == {'aXXXnXXX', 'aXXXn', 'a nXXX', 'a n'}
-
def test_multiple_mutations(self):
self.make_analyser(('ä', ['ä', 'ae']), ('ö', ['ö', 'oe']))
diff --git a/test/python/tokenizer/token_analysis/test_simple_trie.py b/test/python/tokenizer/token_analysis/test_simple_trie.py
index 0384a456..6ce66580 100644
--- a/test/python/tokenizer/token_analysis/test_simple_trie.py
+++ b/test/python/tokenizer/token_analysis/test_simple_trie.py
@@ -10,6 +10,7 @@ Tests for simplified trie structure.
from nominatim_db.tokenizer.token_analysis.simple_trie import SimpleTrie
+
def test_single_item_trie():
t = SimpleTrie([('foob', 42)])
@@ -18,6 +19,7 @@ def test_single_item_trie():
assert t.longest_prefix('foob') == (42, 4)
assert t.longest_prefix('123foofoo', 3) == (None, 3)
+
def test_complex_item_tree():
t = SimpleTrie([('a', 1),
('b', 2),
diff --git a/test/python/tools/conftest.py b/test/python/tools/conftest.py
index 0098747e..dc9346c8 100644
--- a/test/python/tools/conftest.py
+++ b/test/python/tools/conftest.py
@@ -2,10 +2,11 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
import pytest
+
@pytest.fixture
def osm2pgsql_options(temp_db, tmp_path):
""" A standard set of options for osm2pgsql
diff --git a/test/python/tools/test_add_osm_data.py b/test/python/tools/test_add_osm_data.py
index c5aaaaae..38cf87c4 100644
--- a/test/python/tools/test_add_osm_data.py
+++ b/test/python/tools/test_add_osm_data.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for functions to add additional data to the database.
@@ -13,6 +13,7 @@ import pytest
from nominatim_db.tools import add_osm_data
+
class CaptureGetUrl:
def __init__(self, monkeypatch):
@@ -29,6 +30,7 @@ def setup_delete_postprocessing(temp_db_cursor):
temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION flush_deleted_places()
RETURNS INTEGER AS $$ SELECT 1 $$ LANGUAGE SQL""")
+
def test_import_osm_file_simple(dsn, table_factory, osm2pgsql_options, capfd):
assert add_osm_data.add_data_from_file(dsn, Path('change.osm'), osm2pgsql_options) == 0
diff --git a/test/python/tools/test_admin.py b/test/python/tools/test_admin.py
index 1e1f0e29..e758bca2 100644
--- a/test/python/tools/test_admin.py
+++ b/test/python/tools/test_admin.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for maintenance and analysis functions.
@@ -14,6 +14,7 @@ from nominatim_db.tools import admin
from nominatim_db.tokenizer import factory
from nominatim_db.db.sql_preprocessor import SQLPreprocessor
+
@pytest.fixture(autouse=True)
def create_placex_table(project_env, tokenizer_mock, temp_db_cursor, placex_table):
""" All tests in this module require the placex table to be set up.
@@ -76,7 +77,8 @@ def test_analyse_indexing_with_osm_id(project_env, temp_db_cursor):
class TestAdminCleanDeleted:
@pytest.fixture(autouse=True)
- def setup_polygon_delete(self, project_env, table_factory, place_table, osmline_table, temp_db_cursor, temp_db_conn, def_config, src_dir):
+ def setup_polygon_delete(self, project_env, table_factory, place_table,
+ osmline_table, temp_db_cursor, temp_db_conn, def_config, src_dir):
""" Set up place_force_delete function and related tables
"""
self.project_env = project_env
@@ -87,12 +89,14 @@ class TestAdminCleanDeleted:
class TEXT NOT NULL,
type TEXT NOT NULL""",
((100, 'N', 'boundary', 'administrative'),
- (145, 'N', 'boundary', 'administrative'),
- (175, 'R', 'landcover', 'grass')))
- temp_db_cursor.execute("""INSERT INTO placex (place_id, osm_id, osm_type, class, type, indexed_date, indexed_status)
- VALUES(1, 100, 'N', 'boundary', 'administrative', current_date - INTERVAL '1 month', 1),
- (2, 145, 'N', 'boundary', 'administrative', current_date - INTERVAL '3 month', 1),
- (3, 175, 'R', 'landcover', 'grass', current_date - INTERVAL '3 months', 1)""")
+ (145, 'N', 'boundary', 'administrative'),
+ (175, 'R', 'landcover', 'grass')))
+ temp_db_cursor.execute("""
+ INSERT INTO placex (place_id, osm_id, osm_type, class, type,
+ indexed_date, indexed_status)
+ VALUES(1, 100, 'N', 'boundary', 'administrative', current_date - INTERVAL '1 month', 1),
+ (2, 145, 'N', 'boundary', 'administrative', current_date - INTERVAL '3 month', 1),
+ (3, 175, 'R', 'landcover', 'grass', current_date - INTERVAL '3 months', 1)""")
# set up tables and triggers for utils function
table_factory('place_to_be_deleted',
"""osm_id BIGINT,
@@ -116,33 +120,42 @@ class TestAdminCleanDeleted:
sqlproc = SQLPreprocessor(temp_db_conn, def_config)
sqlproc.run_sql_file(temp_db_conn, 'functions/utils.sql')
def_config.lib_dir.sql = orig_sql
-
def test_admin_clean_deleted_no_records(self):
admin.clean_deleted_relations(self.project_env, age='1 year')
- assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 1),
- (145, 'N', 'boundary', 'administrative', 1),
- (175, 'R', 'landcover', 'grass', 1)}
- assert self.temp_db_cursor.table_rows('import_polygon_delete') == 3
+ rowset = self.temp_db_cursor.row_set(
+ 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex')
+
+ assert rowset == {(100, 'N', 'boundary', 'administrative', 1),
+ (145, 'N', 'boundary', 'administrative', 1),
+ (175, 'R', 'landcover', 'grass', 1)}
+ assert self.temp_db_cursor.table_rows('import_polygon_delete') == 3
@pytest.mark.parametrize('test_age', ['T week', '1 welk', 'P1E'])
def test_admin_clean_deleted_bad_age(self, test_age):
with pytest.raises(UsageError):
- admin.clean_deleted_relations(self.project_env, age = test_age)
-
+ admin.clean_deleted_relations(self.project_env, age=test_age)
def test_admin_clean_deleted_partial(self):
- admin.clean_deleted_relations(self.project_env, age = '2 months')
- assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 1),
- (145, 'N', 'boundary', 'administrative', 100),
- (175, 'R', 'landcover', 'grass', 100)}
+ admin.clean_deleted_relations(self.project_env, age='2 months')
+
+ rowset = self.temp_db_cursor.row_set(
+ 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex')
+
+ assert rowset == {(100, 'N', 'boundary', 'administrative', 1),
+ (145, 'N', 'boundary', 'administrative', 100),
+ (175, 'R', 'landcover', 'grass', 100)}
assert self.temp_db_cursor.table_rows('import_polygon_delete') == 1
@pytest.mark.parametrize('test_age', ['1 week', 'P3D', '5 hours'])
def test_admin_clean_deleted(self, test_age):
- admin.clean_deleted_relations(self.project_env, age = test_age)
- assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 100),
- (145, 'N', 'boundary', 'administrative', 100),
- (175, 'R', 'landcover', 'grass', 100)}
+ admin.clean_deleted_relations(self.project_env, age=test_age)
+
+ rowset = self.temp_db_cursor.row_set(
+ 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex')
+
+ assert rowset == {(100, 'N', 'boundary', 'administrative', 100),
+ (145, 'N', 'boundary', 'administrative', 100),
+ (175, 'R', 'landcover', 'grass', 100)}
assert self.temp_db_cursor.table_rows('import_polygon_delete') == 0
diff --git a/test/python/tools/test_check_database.py b/test/python/tools/test_check_database.py
index 886bd75b..66506f56 100644
--- a/test/python/tools/test_check_database.py
+++ b/test/python/tools/test_check_database.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for database integrity checks.
@@ -12,6 +12,7 @@ import pytest
from nominatim_db.tools import check_database as chkdb
import nominatim_db.version
+
def test_check_database_unknown_db(def_config, monkeypatch):
monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'pgsql:dbname=fjgkhughwgh2423gsags')
assert chkdb.check_database(def_config) == 1
@@ -35,6 +36,7 @@ def test_check_database_version_good(property_table, temp_db_conn, def_config):
str(nominatim_db.version.NOMINATIM_VERSION))
assert chkdb.check_database_version(temp_db_conn, def_config) == chkdb.CheckState.OK
+
def test_check_database_version_bad(property_table, temp_db_conn, def_config):
property_table.set('database_version', '3.9.9-9')
assert chkdb.check_database_version(temp_db_conn, def_config) == chkdb.CheckState.FATAL
diff --git a/test/python/tools/test_database_import.py b/test/python/tools/test_database_import.py
index df204298..f8cea2cc 100644
--- a/test/python/tools/test_database_import.py
+++ b/test/python/tools/test_database_import.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for functions to import a new database.
@@ -10,13 +10,14 @@ Tests for functions to import a new database.
from pathlib import Path
import pytest
-import pytest_asyncio
+import pytest_asyncio # noqa
import psycopg
from psycopg import sql as pysql
from nominatim_db.tools import database_import
from nominatim_db.errors import UsageError
+
class TestDatabaseSetup:
DBNAME = 'test_nominatim_python_unittest'
@@ -31,18 +32,15 @@ class TestDatabaseSetup:
with conn.cursor() as cur:
cur.execute(f'DROP DATABASE IF EXISTS {self.DBNAME}')
-
@pytest.fixture
def cursor(self):
with psycopg.connect(dbname=self.DBNAME) as conn:
with conn.cursor() as cur:
yield cur
-
def conn(self):
return psycopg.connect(dbname=self.DBNAME)
-
def test_setup_skeleton(self):
database_import.setup_database_skeleton(f'dbname={self.DBNAME}')
@@ -51,25 +49,21 @@ class TestDatabaseSetup:
with conn.cursor() as cur:
cur.execute('CREATE TABLE t (h HSTORE, geom GEOMETRY(Geometry, 4326))')
-
def test_unsupported_pg_version(self, monkeypatch):
monkeypatch.setattr(database_import, 'POSTGRESQL_REQUIRED_VERSION', (100, 4))
with pytest.raises(UsageError, match='PostgreSQL server is too old.'):
database_import.setup_database_skeleton(f'dbname={self.DBNAME}')
-
def test_create_db_explicit_ro_user(self):
database_import.setup_database_skeleton(f'dbname={self.DBNAME}',
rouser='postgres')
-
def test_create_db_missing_ro_user(self):
with pytest.raises(UsageError, match='Missing read-only user.'):
database_import.setup_database_skeleton(f'dbname={self.DBNAME}',
rouser='sdfwkjkjgdugu2;jgsafkljas;')
-
def test_setup_extensions_old_postgis(self, monkeypatch):
monkeypatch.setattr(database_import, 'POSTGIS_REQUIRED_VERSION', (50, 50))
@@ -173,7 +167,7 @@ def test_truncate_database_tables(temp_db_conn, temp_db_cursor, table_factory, w
@pytest.mark.parametrize("threads", (1, 5))
@pytest.mark.asyncio
async def test_load_data(dsn, place_row, placex_table, osmline_table,
- temp_db_cursor, threads):
+ temp_db_cursor, threads):
for func in ('precompute_words', 'getorcreate_housenumber_id', 'make_standard_name'):
temp_db_cursor.execute(pysql.SQL("""CREATE FUNCTION {} (src TEXT)
RETURNS TEXT AS $$ SELECT 'a'::TEXT $$ LANGUAGE SQL
@@ -198,11 +192,9 @@ class TestSetupSQL:
self.config = def_config
-
def write_sql(self, fname, content):
(self.config.lib_dir.sql / fname).write_text(content)
-
@pytest.mark.parametrize("reverse", [True, False])
def test_create_tables(self, temp_db_conn, temp_db_cursor, reverse):
self.write_sql('tables.sql',
@@ -213,7 +205,6 @@ class TestSetupSQL:
temp_db_cursor.scalar('SELECT test()') == reverse
-
def test_create_table_triggers(self, temp_db_conn, temp_db_cursor):
self.write_sql('table-triggers.sql',
"""CREATE FUNCTION test() RETURNS TEXT
@@ -223,7 +214,6 @@ class TestSetupSQL:
temp_db_cursor.scalar('SELECT test()') == 'a'
-
def test_create_partition_tables(self, temp_db_conn, temp_db_cursor):
self.write_sql('partition-tables.src.sql',
"""CREATE FUNCTION test() RETURNS TEXT
@@ -233,7 +223,6 @@ class TestSetupSQL:
temp_db_cursor.scalar('SELECT test()') == 'b'
-
@pytest.mark.parametrize("drop", [True, False])
@pytest.mark.asyncio
async def test_create_search_indices(self, temp_db_conn, temp_db_cursor, drop):
diff --git a/test/python/tools/test_exec_utils.py b/test/python/tools/test_exec_utils.py
index 666ef0b8..216f1a40 100644
--- a/test/python/tools/test_exec_utils.py
+++ b/test/python/tools/test_exec_utils.py
@@ -2,19 +2,14 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for tools.exec_utils module.
"""
-from pathlib import Path
-import subprocess
-
-import pytest
-
-from nominatim_db.config import Configuration
import nominatim_db.tools.exec_utils as exec_utils
+
def test_run_osm2pgsql(osm2pgsql_options):
osm2pgsql_options['append'] = False
osm2pgsql_options['import_file'] = 'foo.bar'
diff --git a/test/python/tools/test_freeze.py b/test/python/tools/test_freeze.py
index f64850fb..21e49b8d 100644
--- a/test/python/tools/test_freeze.py
+++ b/test/python/tools/test_freeze.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for freeze functions (removing unused database parts).
@@ -26,6 +26,7 @@ NOMINATIM_DROP_TABLES = [
'wikipedia_article', 'wikipedia_redirect'
]
+
def test_drop_tables(temp_db_conn, temp_db_cursor, table_factory):
for table in NOMINATIM_RUNTIME_TABLES + NOMINATIM_DROP_TABLES:
table_factory(table)
@@ -42,6 +43,7 @@ def test_drop_tables(temp_db_conn, temp_db_cursor, table_factory):
assert freeze.is_frozen(temp_db_conn)
+
def test_drop_flatnode_file_no_file():
freeze.drop_flatnode_file(None)
diff --git a/test/python/tools/test_import_special_phrases.py b/test/python/tools/test_import_special_phrases.py
index 0d33e6e0..d8fe8946 100644
--- a/test/python/tools/test_import_special_phrases.py
+++ b/test/python/tools/test_import_special_phrases.py
@@ -2,20 +2,17 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for import special phrases methods
of the class SPImporter.
"""
-from shutil import copyfile
import pytest
from nominatim_db.tools.special_phrases.sp_importer import SPImporter
from nominatim_db.tools.special_phrases.sp_wiki_loader import SPWikiLoader
from nominatim_db.tools.special_phrases.special_phrase import SpecialPhrase
-from nominatim_db.errors import UsageError
-from cursor import CursorForTesting
@pytest.fixture
def sp_importer(temp_db_conn, def_config, monkeypatch):
@@ -53,6 +50,7 @@ def test_fetch_existing_place_classtype_tables(sp_importer, table_factory):
contained_table = sp_importer.table_phrases_to_delete.pop()
assert contained_table == 'place_classtype_testclasstypetable'
+
def test_check_sanity_class(sp_importer):
"""
Check for _check_sanity() method.
@@ -65,6 +63,7 @@ def test_check_sanity_class(sp_importer):
assert sp_importer._check_sanity(SpecialPhrase('en', 'class', 'type', ''))
+
def test_load_white_and_black_lists(sp_importer):
"""
Test that _load_white_and_black_lists() well return
@@ -93,6 +92,7 @@ def test_create_place_classtype_indexes(temp_db_with_extensions,
assert check_placeid_and_centroid_indexes(temp_db_cursor, phrase_class, phrase_type)
+
def test_create_place_classtype_table(temp_db_conn, temp_db_cursor, placex_table, sp_importer):
"""
Test that _create_place_classtype_table() create
@@ -105,6 +105,7 @@ def test_create_place_classtype_table(temp_db_conn, temp_db_cursor, placex_table
assert check_table_exist(temp_db_cursor, phrase_class, phrase_type)
+
def test_grant_access_to_web_user(temp_db_conn, temp_db_cursor, table_factory,
def_config, sp_importer):
"""
@@ -120,7 +121,9 @@ def test_grant_access_to_web_user(temp_db_conn, temp_db_cursor, table_factory,
sp_importer._grant_access_to_webuser(phrase_class, phrase_type)
temp_db_conn.commit()
- assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER, phrase_class, phrase_type)
+ assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER,
+ phrase_class, phrase_type)
+
def test_create_place_classtype_table_and_indexes(
temp_db_cursor, def_config, placex_table,
@@ -141,6 +144,7 @@ def test_create_place_classtype_table_and_indexes(
assert check_placeid_and_centroid_indexes(temp_db_cursor, pair[0], pair[1])
assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER, pair[0], pair[1])
+
def test_remove_non_existent_tables_from_db(sp_importer, default_phrases,
temp_db_conn, temp_db_cursor):
"""
@@ -168,7 +172,7 @@ def test_remove_non_existent_tables_from_db(sp_importer, default_phrases,
temp_db_conn.commit()
assert temp_db_cursor.row_set(query_tables) \
- == {('place_classtype_testclasstypetable_to_keep', )}
+ == {('place_classtype_testclasstypetable_to_keep', )}
@pytest.mark.parametrize("should_replace", [(True), (False)])
@@ -182,8 +186,8 @@ def test_import_phrases(monkeypatch, temp_db_cursor, def_config, sp_importer,
It should also update the database well by deleting or preserving existing entries
of the database.
"""
- #Add some data to the database before execution in order to test
- #what is deleted and what is preserved.
+ # Add some data to the database before execution in order to test
+ # what is deleted and what is preserved.
table_factory('place_classtype_amenity_animal_shelter')
table_factory('place_classtype_wrongclass_wrongtype')
@@ -209,6 +213,7 @@ def test_import_phrases(monkeypatch, temp_db_cursor, def_config, sp_importer,
if should_replace:
assert not temp_db_cursor.table_exists('place_classtype_wrongclass_wrongtype')
+
def check_table_exist(temp_db_cursor, phrase_class, phrase_type):
"""
Verify that the place_classtype table exists for the given
@@ -231,6 +236,7 @@ def check_grant_access(temp_db_cursor, user, phrase_class, phrase_type):
AND privilege_type='SELECT'""".format(table_name, user))
return temp_db_cursor.fetchone()
+
def check_placeid_and_centroid_indexes(temp_db_cursor, phrase_class, phrase_type):
"""
Check that the place_id index and centroid index exist for the
diff --git a/test/python/tools/test_migration.py b/test/python/tools/test_migration.py
index 0b4d2ec6..00f6a7d7 100644
--- a/test/python/tools/test_migration.py
+++ b/test/python/tools/test_migration.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for migration functions
@@ -11,9 +11,9 @@ import pytest
from nominatim_db.tools import migration
from nominatim_db.errors import UsageError
-from nominatim_db.db.connection import server_version_tuple
import nominatim_db.version
+
class DummyTokenizer:
def update_sql_functions(self, config):
@@ -49,6 +49,7 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso
str(nominatim_db.version.NominatimVersion(*oldversion)))
done = {'old': False, 'new': False}
+
def _migration(**_):
""" Dummy migration"""
done['new'] = True
@@ -69,7 +70,7 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso
assert property_table.get('database_version') == str(nominatim_db.version.NOMINATIM_VERSION)
-###### Tests for specific migrations
+# Tests for specific migrations
#
# Each migration should come with two tests:
# 1. Test that migration from old to new state works as expected.
diff --git a/test/python/tools/test_postcodes.py b/test/python/tools/test_postcodes.py
index f035bb19..b03c9748 100644
--- a/test/python/tools/test_postcodes.py
+++ b/test/python/tools/test_postcodes.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for functions to maintain the artificial postcode table.
@@ -15,6 +15,7 @@ from nominatim_db.tools import postcodes
from nominatim_db.data import country_info
import dummy_tokenizer
+
class MockPostcodeTable:
""" A location_postcode table for testing.
"""
@@ -35,7 +36,7 @@ class MockPostcodeTable:
RETURNS TEXT AS $$ BEGIN RETURN postcode; END; $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_country_code(place geometry)
- RETURNS TEXT AS $$ BEGIN
+ RETURNS TEXT AS $$ BEGIN
RETURN null;
END; $$ LANGUAGE plpgsql;
""")
@@ -51,7 +52,6 @@ class MockPostcodeTable:
(country, postcode, x, y))
self.conn.commit()
-
@property
def row_set(self):
with self.conn.cursor() as cur:
@@ -180,7 +180,7 @@ def test_postcodes_extern(dsn, postcode_table, tmp_path,
('xx', 'CD 4511', -10, -5)}
-def test_postcodes_extern_bad_column(dsn, postcode_table, tmp_path,
+def test_postcodes_extern_bad_column(dsn, postcode_table, tmp_path,
insert_implicit_postcode, tokenizer):
insert_implicit_postcode(1, 'xx', 'POINT(10 12)', dict(postcode='AB 4511'))
@@ -204,6 +204,7 @@ def test_postcodes_extern_bad_number(dsn, insert_implicit_postcode,
assert postcode_table.row_set == {('xx', 'AB 4511', 10, 12),
('xx', 'CD 4511', -10, -5)}
+
def test_can_compute(dsn, table_factory):
assert not postcodes.can_compute(dsn)
table_factory('place')
@@ -211,10 +212,10 @@ def test_can_compute(dsn, table_factory):
def test_no_placex_entry(dsn, tmp_path, temp_db_cursor, place_row, postcode_table, tokenizer):
- #Rewrite the get_country_code function to verify its execution.
+ # Rewrite the get_country_code function to verify its execution.
temp_db_cursor.execute("""
CREATE OR REPLACE FUNCTION get_country_code(place geometry)
- RETURNS TEXT AS $$ BEGIN
+ RETURNS TEXT AS $$ BEGIN
RETURN 'yy';
END; $$ LANGUAGE plpgsql;
""")
@@ -224,11 +225,12 @@ def test_no_placex_entry(dsn, tmp_path, temp_db_cursor, place_row, postcode_tabl
assert postcode_table.row_set == {('yy', 'AB 4511', 10, 12)}
-def test_discard_badly_formatted_postcodes(dsn, tmp_path, temp_db_cursor, place_row, postcode_table, tokenizer):
- #Rewrite the get_country_code function to verify its execution.
+def test_discard_badly_formatted_postcodes(dsn, tmp_path, temp_db_cursor, place_row,
+ postcode_table, tokenizer):
+ # Rewrite the get_country_code function to verify its execution.
temp_db_cursor.execute("""
CREATE OR REPLACE FUNCTION get_country_code(place geometry)
- RETURNS TEXT AS $$ BEGIN
+ RETURNS TEXT AS $$ BEGIN
RETURN 'fr';
END; $$ LANGUAGE plpgsql;
""")
diff --git a/test/python/tools/test_refresh.py b/test/python/tools/test_refresh.py
index 8f735180..95feef0d 100644
--- a/test/python/tools/test_refresh.py
+++ b/test/python/tools/test_refresh.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for various refresh functions.
@@ -12,7 +12,7 @@ from pathlib import Path
import pytest
from nominatim_db.tools import refresh
-from nominatim_db.db.connection import postgis_version_tuple
+
def test_refresh_import_wikipedia_not_existing(dsn):
assert refresh.import_wikipedia_articles(dsn, Path('.')) == 1
@@ -21,6 +21,7 @@ def test_refresh_import_wikipedia_not_existing(dsn):
def test_refresh_import_secondary_importance_non_existing(dsn):
assert refresh.import_secondary_importance(dsn, Path('.')) == 1
+
def test_refresh_import_secondary_importance_testdb(dsn, src_dir, temp_db_conn, temp_db_cursor):
temp_db_cursor.execute('CREATE EXTENSION postgis')
temp_db_cursor.execute('CREATE EXTENSION postgis_raster')
diff --git a/test/python/tools/test_refresh_address_levels.py b/test/python/tools/test_refresh_address_levels.py
index 6e094cdc..f2bfdea6 100644
--- a/test/python/tools/test_refresh_address_levels.py
+++ b/test/python/tools/test_refresh_address_levels.py
@@ -2,23 +2,24 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for function for importing address ranks.
"""
import json
-from pathlib import Path
import pytest
from nominatim_db.tools.refresh import load_address_levels, load_address_levels_from_config
+
def test_load_ranks_def_config(temp_db_conn, temp_db_cursor, def_config):
load_address_levels_from_config(temp_db_conn, def_config)
assert temp_db_cursor.table_rows('address_levels') > 0
+
def test_load_ranks_from_project_dir(project_env, temp_db_conn, temp_db_cursor):
test_file = project_env.project_dir / 'address-levels.json'
test_file.write_text('[{"tags":{"place":{"sea":2}}}]')
@@ -43,14 +44,14 @@ def test_load_ranks_country(temp_db_conn, temp_db_cursor):
"tags": {"place": {"village": 15}}},
{"countries": ['uk', 'us'],
"tags": {"place": {"village": 16}}}
- ])
+ ])
assert temp_db_cursor.row_set('SELECT * FROM levels') == \
set([(None, 'place', 'village', 14, 14),
('de', 'place', 'village', 15, 15),
('uk', 'place', 'village', 16, 16),
('us', 'place', 'village', 16, 16),
- ])
+ ])
def test_load_ranks_default_value(temp_db_conn, temp_db_cursor):
@@ -58,33 +59,33 @@ def test_load_ranks_default_value(temp_db_conn, temp_db_cursor):
[{"tags": {"boundary": {"": 28}}},
{"countries": ['hu'],
"tags": {"boundary": {"": 29}}}
- ])
+ ])
assert temp_db_cursor.row_set('SELECT * FROM levels') == \
set([(None, 'boundary', None, 28, 28),
('hu', 'boundary', None, 29, 29),
- ])
+ ])
def test_load_ranks_multiple_keys(temp_db_conn, temp_db_cursor):
load_address_levels(temp_db_conn, 'levels',
[{"tags": {"place": {"city": 14},
- "boundary": {"administrative2" : 4}}
- }])
+ "boundary": {"administrative2": 4}}
+ }])
assert temp_db_cursor.row_set('SELECT * FROM levels') == \
set([(None, 'place', 'city', 14, 14),
(None, 'boundary', 'administrative2', 4, 4),
- ])
+ ])
def test_load_ranks_address(temp_db_conn, temp_db_cursor):
load_address_levels(temp_db_conn, 'levels',
[{"tags": {"place": {"city": 14,
- "town" : [14, 13]}}
- }])
+ "town": [14, 13]}}
+ }])
assert temp_db_cursor.row_set('SELECT * FROM levels') == \
set([(None, 'place', 'city', 14, 14),
(None, 'place', 'town', 14, 13),
- ])
+ ])
diff --git a/test/python/tools/test_refresh_create_functions.py b/test/python/tools/test_refresh_create_functions.py
index 984a1610..bd8724d6 100644
--- a/test/python/tools/test_refresh_create_functions.py
+++ b/test/python/tools/test_refresh_create_functions.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for creating PL/pgSQL functions for Nominatim.
@@ -11,6 +11,7 @@ import pytest
from nominatim_db.tools.refresh import create_functions
+
class TestCreateFunctions:
@pytest.fixture(autouse=True)
def init_env(self, sql_preprocessor, temp_db_conn, def_config, tmp_path):
@@ -18,12 +19,10 @@ class TestCreateFunctions:
self.config = def_config
def_config.lib_dir.sql = tmp_path
-
def write_functions(self, content):
sqlfile = self.config.lib_dir.sql / 'functions.sql'
sqlfile.write_text(content)
-
def test_create_functions(self, temp_db_cursor):
self.write_functions("""CREATE OR REPLACE FUNCTION test() RETURNS INTEGER
AS $$
@@ -37,7 +36,6 @@ class TestCreateFunctions:
assert temp_db_cursor.scalar('SELECT test()') == 43
-
@pytest.mark.parametrize("dbg,ret", ((True, 43), (False, 22)))
def test_create_functions_with_template(self, temp_db_cursor, dbg, ret):
self.write_functions("""CREATE OR REPLACE FUNCTION test() RETURNS INTEGER
diff --git a/test/python/tools/test_refresh_wiki_data.py b/test/python/tools/test_refresh_wiki_data.py
index 997ba04d..046e9191 100644
--- a/test/python/tools/test_refresh_wiki_data.py
+++ b/test/python/tools/test_refresh_wiki_data.py
@@ -12,7 +12,10 @@ import csv
import pytest
-from nominatim_db.tools.refresh import import_wikipedia_articles, recompute_importance, create_functions
+from nominatim_db.tools.refresh import (import_wikipedia_articles,
+ recompute_importance,
+ create_functions)
+
@pytest.fixture
def wiki_csv(tmp_path, sql_preprocessor):
@@ -25,7 +28,7 @@ def wiki_csv(tmp_path, sql_preprocessor):
for lang, title, importance, wd in data:
writer.writerow({'language': lang, 'type': 'a',
'title': title, 'importance': str(importance),
- 'wikidata_id' : wd})
+ 'wikidata_id': wd})
return tmp_path
return _import
diff --git a/test/python/tools/test_replication.py b/test/python/tools/test_replication.py
index 392ea075..347899bd 100644
--- a/test/python/tools/test_replication.py
+++ b/test/python/tools/test_replication.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for replication functionality.
@@ -22,13 +22,15 @@ OSM_NODE_DATA = """\
-"""
+""" # noqa
+
@pytest.fixture(autouse=True)
def setup_status_table(status_table):
pass
-### init replication
+
+# init replication
def test_init_replication_bad_base_url(monkeypatch, place_row, temp_db_conn):
place_row(osm_type='N', osm_id=100)
@@ -50,13 +52,13 @@ def test_init_replication_success(monkeypatch, place_row, temp_db_conn, temp_db_
nominatim_db.tools.replication.init_replication(temp_db_conn, 'https://test.io')
expected_date = dt.datetime.strptime('2006-01-27T19:09:10', status.ISODATE_FORMAT)\
- .replace(tzinfo=dt.timezone.utc)
+ .replace(tzinfo=dt.timezone.utc)
assert temp_db_cursor.row_set("SELECT * FROM import_status") \
- == {(expected_date, 234, True)}
+ == {(expected_date, 234, True)}
-### checking for updates
+# checking for updates
def test_check_for_updates_empty_status_table(temp_db_conn):
assert nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io') == 254
@@ -87,10 +89,11 @@ def test_check_for_updates_no_new_data(monkeypatch, temp_db_conn,
"get_state_info",
lambda self: OsmosisState(server_sequence, date))
- assert nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io') == result
+ assert result == \
+ nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io')
-### updating
+# updating
@pytest.fixture
def update_options(tmpdir):
@@ -100,6 +103,7 @@ def update_options(tmpdir):
import_file=tmpdir / 'foo.osm',
max_diff_size=1)
+
def test_update_empty_status_table(dsn):
with pytest.raises(UsageError):
nominatim_db.tools.replication.update(dsn, {})
@@ -109,7 +113,7 @@ def test_update_already_indexed(temp_db_conn, dsn):
status.set_status(temp_db_conn, dt.datetime.now(dt.timezone.utc), seq=34, indexed=False)
assert nominatim_db.tools.replication.update(dsn, dict(indexed_only=True)) \
- == nominatim_db.tools.replication.UpdateState.MORE_PENDING
+ == nominatim_db.tools.replication.UpdateState.MORE_PENDING
def test_update_no_data_no_sleep(monkeypatch, temp_db_conn, dsn, update_options):
@@ -124,7 +128,7 @@ def test_update_no_data_no_sleep(monkeypatch, temp_db_conn, dsn, update_options)
monkeypatch.setattr(time, 'sleep', sleeptime.append)
assert nominatim_db.tools.replication.update(dsn, update_options) \
- == nominatim_db.tools.replication.UpdateState.NO_CHANGES
+ == nominatim_db.tools.replication.UpdateState.NO_CHANGES
assert not sleeptime
@@ -141,7 +145,7 @@ def test_update_no_data_sleep(monkeypatch, temp_db_conn, dsn, update_options):
monkeypatch.setattr(time, 'sleep', sleeptime.append)
assert nominatim_db.tools.replication.update(dsn, update_options) \
- == nominatim_db.tools.replication.UpdateState.NO_CHANGES
+ == nominatim_db.tools.replication.UpdateState.NO_CHANGES
assert len(sleeptime) == 1
assert sleeptime[0] < 3600
diff --git a/test/python/tools/test_sp_csv_loader.py b/test/python/tools/test_sp_csv_loader.py
index 9d0ad9cc..67d6eed5 100644
--- a/test/python/tools/test_sp_csv_loader.py
+++ b/test/python/tools/test_sp_csv_loader.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for methods of the SPCsvLoader class.
@@ -13,6 +13,7 @@ from nominatim_db.errors import UsageError
from nominatim_db.tools.special_phrases.sp_csv_loader import SPCsvLoader
from nominatim_db.tools.special_phrases.special_phrase import SpecialPhrase
+
@pytest.fixture
def sp_csv_loader(src_dir):
"""
diff --git a/test/python/tools/test_sp_wiki_loader.py b/test/python/tools/test_sp_wiki_loader.py
index 5c37c32f..b8e41cbe 100644
--- a/test/python/tools/test_sp_wiki_loader.py
+++ b/test/python/tools/test_sp_wiki_loader.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for methods of the SPWikiLoader class.
@@ -36,22 +36,22 @@ def test_generate_phrases(sp_wiki_loader):
"""
phrases = list(sp_wiki_loader.generate_phrases())
- assert set((p.p_label, p.p_class, p.p_type, p.p_operator) for p in phrases) ==\
- {('Zip Line', 'aerialway', 'zip_line', '-'),
- ('Zip Lines', 'aerialway', 'zip_line', '-'),
- ('Zip Line in', 'aerialway', 'zip_line', 'in'),
- ('Zip Lines in', 'aerialway', 'zip_line', 'in'),
- ('Zip Line near', 'aerialway', 'zip_line', 'near'),
- ('Animal shelter', 'amenity', 'animal_shelter', '-'),
- ('Animal shelters', 'amenity', 'animal_shelter', '-'),
- ('Animal shelter in', 'amenity', 'animal_shelter', 'in'),
- ('Animal shelters in', 'amenity', 'animal_shelter', 'in'),
- ('Animal shelter near', 'amenity', 'animal_shelter', 'near'),
- ('Animal shelters near', 'amenity', 'animal_shelter', 'near'),
- ('Drinking Water near', 'amenity', 'drinking_water', 'near'),
- ('Water', 'amenity', 'drinking_water', '-'),
- ('Water in', 'amenity', 'drinking_water', 'in'),
- ('Water near', 'amenity', 'drinking_water', 'near'),
- ('Embassy', 'amenity', 'embassy', '-'),
- ('Embassys', 'amenity', 'embassy', '-'),
- ('Embassies', 'amenity', 'embassy', '-')}
+ assert set((p.p_label, p.p_class, p.p_type, p.p_operator) for p in phrases) == \
+ {('Zip Line', 'aerialway', 'zip_line', '-'),
+ ('Zip Lines', 'aerialway', 'zip_line', '-'),
+ ('Zip Line in', 'aerialway', 'zip_line', 'in'),
+ ('Zip Lines in', 'aerialway', 'zip_line', 'in'),
+ ('Zip Line near', 'aerialway', 'zip_line', 'near'),
+ ('Animal shelter', 'amenity', 'animal_shelter', '-'),
+ ('Animal shelters', 'amenity', 'animal_shelter', '-'),
+ ('Animal shelter in', 'amenity', 'animal_shelter', 'in'),
+ ('Animal shelters in', 'amenity', 'animal_shelter', 'in'),
+ ('Animal shelter near', 'amenity', 'animal_shelter', 'near'),
+ ('Animal shelters near', 'amenity', 'animal_shelter', 'near'),
+ ('Drinking Water near', 'amenity', 'drinking_water', 'near'),
+ ('Water', 'amenity', 'drinking_water', '-'),
+ ('Water in', 'amenity', 'drinking_water', 'in'),
+ ('Water near', 'amenity', 'drinking_water', 'near'),
+ ('Embassy', 'amenity', 'embassy', '-'),
+ ('Embassys', 'amenity', 'embassy', '-'),
+ ('Embassies', 'amenity', 'embassy', '-')}
diff --git a/test/python/tools/test_tiger_data.py b/test/python/tools/test_tiger_data.py
index 5d65fafb..f7dfe32e 100644
--- a/test/python/tools/test_tiger_data.py
+++ b/test/python/tools/test_tiger_data.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Test for tiger data function
@@ -11,12 +11,13 @@ import tarfile
from textwrap import dedent
import pytest
-import pytest_asyncio
+import pytest_asyncio # noqa: F401
from nominatim_db.db.connection import execute_scalar
from nominatim_db.tools import tiger_data, freeze
from nominatim_db.errors import UsageError
+
class MockTigerTable:
def __init__(self, conn):
@@ -40,6 +41,7 @@ class MockTigerTable:
cur.execute("SELECT * FROM tiger LIMIT 1")
return cur.fetchone()
+
@pytest.fixture
def tiger_table(def_config, temp_db_conn, sql_preprocessor,
temp_db_with_extensions, tmp_path):
@@ -87,7 +89,7 @@ async def test_add_tiger_data(def_config, src_dir, tiger_table, tokenizer_mock,
@pytest.mark.asyncio
async def test_add_tiger_data_database_frozen(def_config, temp_db_conn, tiger_table, tokenizer_mock,
- tmp_path):
+ tmp_path):
freeze.drop_update_tables(temp_db_conn)
with pytest.raises(UsageError) as excinfo:
@@ -100,7 +102,7 @@ async def test_add_tiger_data_database_frozen(def_config, temp_db_conn, tiger_ta
@pytest.mark.asyncio
async def test_add_tiger_data_no_files(def_config, tiger_table, tokenizer_mock,
- tmp_path):
+ tmp_path):
await tiger_data.add_tiger_data(str(tmp_path), def_config, 1, tokenizer_mock())
assert tiger_table.count() == 0
@@ -108,7 +110,7 @@ async def test_add_tiger_data_no_files(def_config, tiger_table, tokenizer_mock,
@pytest.mark.asyncio
async def test_add_tiger_data_bad_file(def_config, tiger_table, tokenizer_mock,
- tmp_path):
+ tmp_path):
sqlfile = tmp_path / '1010.csv'
sqlfile.write_text("""Random text""")
@@ -119,7 +121,7 @@ async def test_add_tiger_data_bad_file(def_config, tiger_table, tokenizer_mock,
@pytest.mark.asyncio
async def test_add_tiger_data_hnr_nan(def_config, tiger_table, tokenizer_mock,
- csv_factory, tmp_path):
+ csv_factory, tmp_path):
csv_factory('file1', hnr_from=99)
csv_factory('file2', hnr_from='L12')
csv_factory('file3', hnr_to='12.4')
@@ -133,7 +135,7 @@ async def test_add_tiger_data_hnr_nan(def_config, tiger_table, tokenizer_mock,
@pytest.mark.parametrize("threads", (1, 5))
@pytest.mark.asyncio
async def test_add_tiger_data_tarfile(def_config, tiger_table, tokenizer_mock,
- tmp_path, src_dir, threads):
+ tmp_path, src_dir, threads):
tar = tarfile.open(str(tmp_path / 'sample.tar.gz'), "w:gz")
tar.add(str(src_dir / 'test' / 'testdb' / 'tiger' / '01001.csv'))
tar.close()
@@ -146,7 +148,7 @@ async def test_add_tiger_data_tarfile(def_config, tiger_table, tokenizer_mock,
@pytest.mark.asyncio
async def test_add_tiger_data_bad_tarfile(def_config, tiger_table, tokenizer_mock,
- tmp_path):
+ tmp_path):
tarfile = tmp_path / 'sample.tar.gz'
tarfile.write_text("""Random text""")
@@ -156,7 +158,7 @@ async def test_add_tiger_data_bad_tarfile(def_config, tiger_table, tokenizer_moc
@pytest.mark.asyncio
async def test_add_tiger_data_empty_tarfile(def_config, tiger_table, tokenizer_mock,
- tmp_path):
+ tmp_path):
tar = tarfile.open(str(tmp_path / 'sample.tar.gz'), "w:gz")
tar.add(__file__)
tar.close()
diff --git a/test/python/utils/test_centroid.py b/test/python/utils/test_centroid.py
index bac0edb3..664d5cd7 100644
--- a/test/python/utils/test_centroid.py
+++ b/test/python/utils/test_centroid.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for centroid computation.
@@ -11,6 +11,7 @@ import pytest
from nominatim_db.utils.centroid import PointsCentroid
+
def test_empty_set():
c = PointsCentroid()
@@ -18,7 +19,7 @@ def test_empty_set():
c.centroid()
-@pytest.mark.parametrize("centroid", [(0,0), (-1, 3), [0.0000032, 88.4938]])
+@pytest.mark.parametrize("centroid", [(0, 0), (-1, 3), [0.0000032, 88.4938]])
def test_one_point_centroid(centroid):
c = PointsCentroid()
diff --git a/test/python/utils/test_json_writer.py b/test/python/utils/test_json_writer.py
index 53e3f4d3..c0946f01 100644
--- a/test/python/utils/test_json_writer.py
+++ b/test/python/utils/test_json_writer.py
@@ -2,7 +2,7 @@
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the streaming JSON writer.
@@ -13,6 +13,7 @@ import pytest
from nominatim_api.utils.json_writer import JsonWriter
+
@pytest.mark.parametrize("inval,outstr", [(None, 'null'),
(True, 'true'), (False, 'false'),
(23, '23'), (0, '0'), (-1.3, '-1.3'),
@@ -71,6 +72,7 @@ def test_object_single_entry():
assert writer() == '{"something":5}'
json.loads(writer())
+
def test_object_many_values():
writer = JsonWriter()\
.start_object()\
@@ -82,6 +84,7 @@ def test_object_many_values():
assert writer() == '{"foo":null,"bar":{},"baz":"b\\taz"}'
json.loads(writer())
+
def test_object_many_values_without_none():
writer = JsonWriter()\
.start_object()\
@@ -89,7 +92,7 @@ def test_object_many_values_without_none():
.keyval_not_none('bar', None)\
.keyval_not_none('baz', '')\
.keyval_not_none('eve', False,
- transform = lambda v: 'yes' if v else 'no')\
+ transform=lambda v: 'yes' if v else 'no')\
.end_object()
assert writer() == '{"foo":0,"baz":"","eve":"no"}'