allow None and str for project_dir in NominatimAPI init

This commit is contained in:
Sarah Hoffmann 2024-08-22 22:49:12 +02:00
parent f535340d5a
commit 7f11de0db9
8 changed files with 45 additions and 50 deletions

View File

@ -7,7 +7,8 @@
""" """
Implementation of classes for API access via libraries. Implementation of classes for API access via libraries.
""" """
from typing import Mapping, Optional, Any, AsyncIterator, Dict, Sequence, List, Tuple, cast from typing import Mapping, Optional, Any, AsyncIterator, Dict, Sequence, List,\
Union, Tuple, cast
import asyncio import asyncio
import sys import sys
import contextlib import contextlib
@ -41,7 +42,7 @@ class NominatimAPIAsync: #pylint: disable=too-many-instance-attributes
This class should usually be used as a context manager in 'with' context. This class should usually be used as a context manager in 'with' context.
""" """
def __init__(self, project_dir: Path, def __init__(self, project_dir: Optional[Union[str, Path]] = None,
environ: Optional[Mapping[str, str]] = None, environ: Optional[Mapping[str, str]] = None,
loop: Optional[asyncio.AbstractEventLoop] = None) -> None: loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
""" Initiate a new frontend object with synchronous API functions. """ Initiate a new frontend object with synchronous API functions.
@ -365,7 +366,7 @@ class NominatimAPI:
This class should usually be used as a context manager in 'with' context. This class should usually be used as a context manager in 'with' context.
""" """
def __init__(self, project_dir: Path, def __init__(self, project_dir: Optional[Union[str, Path]] = None,
environ: Optional[Mapping[str, str]] = None) -> None: environ: Optional[Mapping[str, str]] = None) -> None:
""" Initiate a new frontend object with synchronous API functions. """ Initiate a new frontend object with synchronous API functions.

View File

@ -59,15 +59,17 @@ class Configuration:
other than string. other than string.
""" """
def __init__(self, project_dir: Optional[Path], def __init__(self, project_dir: Optional[Union[Path, str]],
environ: Optional[Mapping[str, str]] = None) -> None: environ: Optional[Mapping[str, str]] = None) -> None:
self.environ = environ or os.environ self.environ = environ or os.environ
self.project_dir = project_dir
self.config_dir = paths.CONFIG_DIR self.config_dir = paths.CONFIG_DIR
self._config = dotenv_values(str(self.config_dir / 'env.defaults')) self._config = dotenv_values(str(self.config_dir / 'env.defaults'))
if self.project_dir is not None and (self.project_dir / '.env').is_file(): if project_dir is not None:
self.project_dir = self.project_dir.resolve() self.project_dir: Optional[Path] = Path(project_dir).resolve()
self._config.update(dotenv_values(str(self.project_dir / '.env'))) if (self.project_dir / '.env').is_file():
self._config.update(dotenv_values(str(self.project_dir / '.env')))
else:
self.project_dir = None
class _LibDirs: class _LibDirs:
module: Path module: Path

View File

@ -7,7 +7,7 @@
""" """
Exporting a Nominatim database to SQlite. Exporting a Nominatim database to SQlite.
""" """
from typing import Set, Any from typing import Set, Any, Optional, Union
import datetime as dt import datetime as dt
import logging import logging
from pathlib import Path from pathlib import Path
@ -21,7 +21,8 @@ from nominatim_api.sql.sqlalchemy_types import Geometry, IntArray
LOG = logging.getLogger() LOG = logging.getLogger()
async def convert(project_dir: Path, outfile: Path, options: Set[str]) -> None: async def convert(project_dir: Optional[Union[str, Path]],
outfile: Path, options: Set[str]) -> None:
""" Export an existing database to sqlite. The resulting database """ Export an existing database to sqlite. The resulting database
will be usable against the Python frontend of Nominatim. will be usable against the Python frontend of Nominatim.
""" """

View File

@ -7,7 +7,6 @@
""" """
Helper fixtures for API call tests. Helper fixtures for API call tests.
""" """
from pathlib import Path
import pytest import pytest
import pytest_asyncio import pytest_asyncio
import time import time
@ -24,7 +23,7 @@ import nominatim_api.logging as loglib
class APITester: class APITester:
def __init__(self): def __init__(self):
self.api = napi.NominatimAPI(Path('/invalid')) self.api = napi.NominatimAPI()
self.async_to_sync(self.api._async_api.setup_database()) self.async_to_sync(self.api._async_api.setup_database())
@ -229,11 +228,9 @@ def frontend(request, event_loop, tmp_path):
apiobj.async_to_sync(_do_sql()) apiobj.async_to_sync(_do_sql())
event_loop.run_until_complete(convert_sqlite.convert(Path('/invalid'), event_loop.run_until_complete(convert_sqlite.convert(None, db, options))
db, options)) outapi = napi.NominatimAPI(environ={'NOMINATIM_DATABASE_DSN': f"sqlite:dbname={db}",
outapi = napi.NominatimAPI(Path('/invalid'), 'NOMINATIM_USE_US_TIGER_DATA': 'yes'})
{'NOMINATIM_DATABASE_DSN': f"sqlite:dbname={db}",
'NOMINATIM_USE_US_TIGER_DATA': 'yes'})
testapis.append(outapi) testapis.append(outapi)
return outapi return outapi
@ -249,5 +246,5 @@ def frontend(request, event_loop, tmp_path):
@pytest_asyncio.fixture @pytest_asyncio.fixture
async def api(temp_db): async def api(temp_db):
async with napi.NominatimAPIAsync(Path('/invalid')) as api: async with napi.NominatimAPIAsync() as api:
yield api yield api

View File

@ -7,8 +7,6 @@
""" """
Tests for query analyzer for ICU tokenizer. Tests for query analyzer for ICU tokenizer.
""" """
from pathlib import Path
import pytest import pytest
import pytest_asyncio import pytest_asyncio
@ -40,7 +38,7 @@ async def conn(table_factory):
table_factory('word', table_factory('word',
definition='word_id INT, word_token TEXT, type TEXT, word TEXT, info JSONB') definition='word_id INT, word_token TEXT, type TEXT, word TEXT, info JSONB')
async with NominatimAPIAsync(Path('/invalid'), {}) as api: async with NominatimAPIAsync(environ={}) as api:
async with api.begin() as conn: async with api.begin() as conn:
yield conn yield conn

View File

@ -7,8 +7,6 @@
""" """
Tests for query analyzer for legacy tokenizer. Tests for query analyzer for legacy tokenizer.
""" """
from pathlib import Path
import pytest import pytest
import pytest_asyncio import pytest_asyncio
@ -74,7 +72,7 @@ async def conn(table_factory, temp_db_cursor):
temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
RETURNS TEXT AS $$ SELECT lower(name); $$ LANGUAGE SQL;""") RETURNS TEXT AS $$ SELECT lower(name); $$ LANGUAGE SQL;""")
async with NominatimAPIAsync(Path('/invalid'), {}) as api: async with NominatimAPIAsync(environ={}) as api:
async with api.begin() as conn: async with api.begin() as conn:
yield conn yield conn

View File

@ -7,7 +7,6 @@
""" """
Tests for the status API call. Tests for the status API call.
""" """
from pathlib import Path
import datetime as dt import datetime as dt
import pytest import pytest
@ -46,7 +45,7 @@ def test_status_full(apiobj, frontend):
def test_status_database_not_found(monkeypatch): def test_status_database_not_found(monkeypatch):
monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'dbname=rgjdfkgjedkrgdfkngdfkg') monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'dbname=rgjdfkgjedkrgdfkngdfkg')
api = napi.NominatimAPI(Path('/invalid'), {}) api = napi.NominatimAPI(environ={})
result = api.status() result = api.status()

View File

@ -9,7 +9,6 @@ Tests for the Python web frameworks adaptor, v1 API.
""" """
import json import json
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path
import pytest import pytest
@ -242,7 +241,7 @@ class TestStatusEndpoint:
a = FakeAdaptor() a = FakeAdaptor()
self.status = napi.StatusResult(0, 'foo') self.status = napi.StatusResult(0, 'foo')
resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 200 assert resp.status == 200
@ -254,7 +253,7 @@ class TestStatusEndpoint:
a = FakeAdaptor() a = FakeAdaptor()
self.status = napi.StatusResult(405, 'foo') self.status = napi.StatusResult(405, 'foo')
resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 500 assert resp.status == 500
@ -266,7 +265,7 @@ class TestStatusEndpoint:
a = FakeAdaptor(params={'format': 'json'}) a = FakeAdaptor(params={'format': 'json'})
self.status = napi.StatusResult(405, 'foo') self.status = napi.StatusResult(405, 'foo')
resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 200 assert resp.status == 200
@ -279,7 +278,7 @@ class TestStatusEndpoint:
self.status = napi.StatusResult(0, 'foo') self.status = napi.StatusResult(0, 'foo')
with pytest.raises(FakeError): with pytest.raises(FakeError):
await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.status_endpoint(napi.NominatimAPIAsync(), a)
# details_endpoint() # details_endpoint()
@ -305,14 +304,14 @@ class TestDetailsEndpoint:
a = FakeAdaptor() a = FakeAdaptor()
with pytest.raises(FakeError, match='^400 -- .*Missing'): with pytest.raises(FakeError, match='^400 -- .*Missing'):
await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.details_endpoint(napi.NominatimAPIAsync(), a)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_details_by_place_id(self): async def test_details_by_place_id(self):
a = FakeAdaptor(params={'place_id': '4573'}) a = FakeAdaptor(params={'place_id': '4573'})
await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.details_endpoint(napi.NominatimAPIAsync(), a)
assert self.lookup_args[0].place_id == 4573 assert self.lookup_args[0].place_id == 4573
@ -321,7 +320,7 @@ class TestDetailsEndpoint:
async def test_details_by_osm_id(self): async def test_details_by_osm_id(self):
a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45'}) a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45'})
await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.details_endpoint(napi.NominatimAPIAsync(), a)
assert self.lookup_args[0].osm_type == 'N' assert self.lookup_args[0].osm_type == 'N'
assert self.lookup_args[0].osm_id == 45 assert self.lookup_args[0].osm_id == 45
@ -332,7 +331,7 @@ class TestDetailsEndpoint:
async def test_details_with_debugging(self): async def test_details_with_debugging(self):
a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45', 'debug': '1'}) a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45', 'debug': '1'})
resp = await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) resp = await glue.details_endpoint(napi.NominatimAPIAsync(), a)
content = ET.fromstring(resp.output) content = ET.fromstring(resp.output)
assert resp.content_type == 'text/html; charset=utf-8' assert resp.content_type == 'text/html; charset=utf-8'
@ -345,7 +344,7 @@ class TestDetailsEndpoint:
self.result = None self.result = None
with pytest.raises(FakeError, match='^404 -- .*found'): with pytest.raises(FakeError, match='^404 -- .*found'):
await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.details_endpoint(napi.NominatimAPIAsync(), a)
# reverse_endpoint() # reverse_endpoint()
@ -370,7 +369,7 @@ class TestReverseEndPoint:
a.params['format'] = 'xml' a.params['format'] = 'xml'
with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'): with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'):
await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -380,7 +379,7 @@ class TestReverseEndPoint:
a.params = params a.params = params
a.params['format'] = 'json' a.params['format'] = 'json'
res = await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
assert res == '' assert res == ''
@ -391,7 +390,7 @@ class TestReverseEndPoint:
a.params['lat'] = '56.3' a.params['lat'] = '56.3'
a.params['lon'] = '6.8' a.params['lon'] = '6.8'
assert await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) assert await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -400,7 +399,7 @@ class TestReverseEndPoint:
a.params['q'] = '34.6 2.56' a.params['q'] = '34.6 2.56'
a.params['format'] = 'json' a.params['format'] = 'json'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -425,7 +424,7 @@ class TestLookupEndpoint:
a = FakeAdaptor() a = FakeAdaptor()
a.params['format'] = 'json' a.params['format'] = 'json'
res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
assert res.output == '[]' assert res.output == '[]'
@ -437,7 +436,7 @@ class TestLookupEndpoint:
a.params['format'] = 'json' a.params['format'] = 'json'
a.params['osm_ids'] = f'W34,{param},N33333' a.params['osm_ids'] = f'W34,{param},N33333'
res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -449,7 +448,7 @@ class TestLookupEndpoint:
a.params['format'] = 'json' a.params['format'] = 'json'
a.params['osm_ids'] = f'W34,{param},N33333' a.params['osm_ids'] = f'W34,{param},N33333'
res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -460,7 +459,7 @@ class TestLookupEndpoint:
a.params['format'] = 'json' a.params['format'] = 'json'
a.params['osm_ids'] = 'N23,W34' a.params['osm_ids'] = 'N23,W34'
res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -485,7 +484,7 @@ class TestSearchEndPointSearch:
a = FakeAdaptor() a = FakeAdaptor()
a.params['q'] = 'something' a.params['q'] = 'something'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -496,7 +495,7 @@ class TestSearchEndPointSearch:
a.params['q'] = 'something' a.params['q'] = 'something'
a.params['format'] = 'xml' a.params['format'] = 'xml'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert res.status == 200 assert res.status == 200
assert res.output.index('something') > 0 assert res.output.index('something') > 0
@ -509,7 +508,7 @@ class TestSearchEndPointSearch:
a.params['city'] = 'ignored' a.params['city'] = 'ignored'
with pytest.raises(FakeError, match='^400 -- .*cannot be used together'): with pytest.raises(FakeError, match='^400 -- .*cannot be used together'):
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -521,7 +520,7 @@ class TestSearchEndPointSearch:
if not dedupe: if not dedupe:
a.params['dedupe'] = '0' a.params['dedupe'] = '0'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == numres assert len(json.loads(res.output)) == numres
@ -544,7 +543,7 @@ class TestSearchEndPointSearchAddress:
a = FakeAdaptor() a = FakeAdaptor()
a.params['street'] = 'something' a.params['street'] = 'something'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1
@ -567,6 +566,6 @@ class TestSearchEndPointSearchCategory:
a = FakeAdaptor() a = FakeAdaptor()
a.params['q'] = '[shop=fog]' a.params['q'] = '[shop=fog]'
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a) res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
assert len(json.loads(res.output)) == 1 assert len(json.loads(res.output)) == 1