1 # SPDX-License-Identifier: GPL-2.0-only
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2023 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Helper fixtures for API call tests.
10 from pathlib import Path
15 import sqlalchemy as sa
17 import nominatim.api as napi
18 from nominatim.db.sql_preprocessor import SQLPreprocessor
19 from nominatim.tools import convert_sqlite
20 import nominatim.api.logging as loglib
25 self.api = napi.NominatimAPI(Path('/invalid'))
26 self.async_to_sync(self.api._async_api.setup_database())
29 def async_to_sync(self, func):
30 """ Run an asynchronous function until completion using the
31 internal loop of the API.
33 return self.api._loop.run_until_complete(func)
36 def add_data(self, table, data):
37 """ Insert data into the given table.
39 sql = getattr(self.api._async_api._tables, table).insert()
40 self.async_to_sync(self.exec_async(sql, data))
43 def add_placex(self, **kw):
45 if isinstance(name, str):
48 centroid = kw.get('centroid', (23.0, 34.0))
49 geometry = kw.get('geometry', 'POINT(%f %f)' % centroid)
51 self.add_data('placex',
52 {'place_id': kw.get('place_id', 1000),
53 'osm_type': kw.get('osm_type', 'W'),
54 'osm_id': kw.get('osm_id', 4),
55 'class_': kw.get('class_', 'highway'),
56 'type': kw.get('type', 'residential'),
58 'address': kw.get('address'),
59 'extratags': kw.get('extratags'),
60 'parent_place_id': kw.get('parent_place_id'),
61 'linked_place_id': kw.get('linked_place_id'),
62 'admin_level': kw.get('admin_level', 15),
63 'country_code': kw.get('country_code'),
64 'housenumber': kw.get('housenumber'),
65 'postcode': kw.get('postcode'),
66 'wikipedia': kw.get('wikipedia'),
67 'rank_search': kw.get('rank_search', 30),
68 'rank_address': kw.get('rank_address', 30),
69 'importance': kw.get('importance'),
70 'centroid': 'POINT(%f %f)' % centroid,
71 'indexed_status': kw.get('indexed_status', 0),
72 'indexed_date': kw.get('indexed_date',
73 dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
74 'geometry': geometry})
77 def add_address_placex(self, object_id, **kw):
79 self.add_data('addressline',
80 {'place_id': object_id,
81 'address_place_id': kw.get('place_id', 1000),
82 'distance': kw.get('distance', 0.0),
83 'cached_rank_address': kw.get('rank_address', 30),
84 'fromarea': kw.get('fromarea', False),
85 'isaddress': kw.get('isaddress', True)})
88 def add_osmline(self, **kw):
89 self.add_data('osmline',
90 {'place_id': kw.get('place_id', 10000),
91 'osm_id': kw.get('osm_id', 4004),
92 'parent_place_id': kw.get('parent_place_id'),
93 'indexed_date': kw.get('indexed_date',
94 dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
95 'startnumber': kw.get('startnumber', 2),
96 'endnumber': kw.get('endnumber', 6),
97 'step': kw.get('step', 2),
98 'address': kw.get('address'),
99 'postcode': kw.get('postcode'),
100 'country_code': kw.get('country_code'),
101 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
104 def add_tiger(self, **kw):
105 self.add_data('tiger',
106 {'place_id': kw.get('place_id', 30000),
107 'parent_place_id': kw.get('parent_place_id'),
108 'startnumber': kw.get('startnumber', 2),
109 'endnumber': kw.get('endnumber', 6),
110 'step': kw.get('step', 2),
111 'postcode': kw.get('postcode'),
112 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')})
115 def add_postcode(self, **kw):
116 self.add_data('postcode',
117 {'place_id': kw.get('place_id', 1000),
118 'parent_place_id': kw.get('parent_place_id'),
119 'country_code': kw.get('country_code'),
120 'postcode': kw.get('postcode'),
121 'rank_search': kw.get('rank_search', 20),
122 'rank_address': kw.get('rank_address', 22),
123 'indexed_date': kw.get('indexed_date',
124 dt.datetime(2022, 12, 7, 14, 14, 46, 0)),
125 'geometry': kw.get('geometry', 'POINT(23 34)')})
128 def add_country(self, country_code, geometry):
129 self.add_data('country_grid',
130 {'country_code': country_code,
132 'geometry': geometry})
135 def add_country_name(self, country_code, names, partition=0):
136 self.add_data('country_name',
137 {'country_code': country_code,
139 'partition': partition})
142 def add_search_name(self, place_id, **kw):
143 centroid = kw.get('centroid', (23.0, 34.0))
144 self.add_data('search_name',
145 {'place_id': place_id,
146 'importance': kw.get('importance', 0.00001),
147 'search_rank': kw.get('search_rank', 30),
148 'address_rank': kw.get('address_rank', 30),
149 'name_vector': kw.get('names', []),
150 'nameaddress_vector': kw.get('address', []),
151 'country_code': kw.get('country_code', 'xx'),
152 'centroid': 'POINT(%f %f)' % centroid})
155 def add_class_type_table(self, cls, typ):
157 self.exec_async(sa.text(f"""CREATE TABLE place_classtype_{cls}_{typ}
158 AS (SELECT place_id, centroid FROM placex
159 WHERE class = '{cls}' AND type = '{typ}')
163 async def exec_async(self, sql, *args, **kwargs):
164 async with self.api._async_api.begin() as conn:
165 return await conn.execute(sql, *args, **kwargs)
168 async def create_tables(self):
169 async with self.api._async_api._engine.begin() as conn:
170 await conn.run_sync(self.api._async_api._tables.meta.create_all)
174 def apiobj(temp_db_with_extensions, temp_db_conn, monkeypatch):
175 """ Create an asynchronous SQLAlchemy engine for the test DB.
177 monkeypatch.setenv('NOMINATIM_USE_US_TIGER_DATA', 'yes')
178 testapi = APITester()
179 testapi.async_to_sync(testapi.create_tables())
181 proc = SQLPreprocessor(temp_db_conn, testapi.api.config)
182 proc.run_sql_file(temp_db_conn, 'functions/ranking.sql')
184 loglib.set_log_output('text')
186 print(loglib.get_and_disable())
191 @pytest.fixture(params=['postgres_db', 'sqlite_db'])
192 def frontend(request, event_loop, tmp_path):
193 if request.param == 'sqlite_db':
194 db = str(tmp_path / 'test_nominatim_python_unittest.sqlite')
196 def mkapi(apiobj, options={'reverse'}):
197 event_loop.run_until_complete(convert_sqlite.convert(Path('/invalid'),
199 return napi.NominatimAPI(Path('/invalid'),
200 {'NOMINATIM_DATABASE_DSN': f"sqlite:dbname={db}",
201 'NOMINATIM_USE_US_TIGER_DATA': 'yes'})
202 elif request.param == 'postgres_db':
203 def mkapi(apiobj, options=None):