]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/api.py
Merge pull request #2937 from lonvia/python-server-stub
[nominatim.git] / nominatim / api.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Implementation of classes for API access via libraries.
9 """
10 from typing import Mapping, Optional, cast, Any
11 import asyncio
12 from pathlib import Path
13
14 from sqlalchemy import text, event
15 from sqlalchemy.engine.url import URL
16 from sqlalchemy.ext.asyncio import create_async_engine
17 import asyncpg
18
19 from nominatim.config import Configuration
20 from nominatim.apicmd.status import get_status, StatusResult
21
22 class NominatimAPIAsync:
23     """ API loader asynchornous version.
24     """
25     def __init__(self, project_dir: Path,
26                  environ: Optional[Mapping[str, str]] = None) -> None:
27         self.config = Configuration(project_dir, environ)
28
29         dsn = self.config.get_database_params()
30
31         dburl = URL.create(
32                    'postgresql+asyncpg',
33                    database=dsn.get('dbname'),
34                    username=dsn.get('user'), password=dsn.get('password'),
35                    host=dsn.get('host'), port=int(dsn['port']) if 'port' in dsn else None,
36                    query={k: v for k, v in dsn.items()
37                           if k not in ('user', 'password', 'dbname', 'host', 'port')})
38         self.engine = create_async_engine(
39                          dburl, future=True,
40                          connect_args={'server_settings': {
41                             'DateStyle': 'sql,european',
42                             'max_parallel_workers_per_gather': '0'
43                          }})
44         asyncio.get_event_loop().run_until_complete(self._query_server_version())
45         asyncio.get_event_loop().run_until_complete(self.close())
46
47         if self.server_version >= 110000:
48             @event.listens_for(self.engine.sync_engine, "connect") # type: ignore[misc]
49             def _on_connect(dbapi_con: Any, _: Any) -> None:
50                 cursor = dbapi_con.cursor()
51                 cursor.execute("SET jit_above_cost TO '-1'")
52
53
54     async def _query_server_version(self) -> None:
55         try:
56             async with self.engine.begin() as conn:
57                 result = await conn.scalar(text('SHOW server_version_num'))
58                 self.server_version = int(cast(str, result))
59         except asyncpg.PostgresError:
60             self.server_version = 0
61
62     async def close(self) -> None:
63         """ Close all active connections to the database. The NominatimAPIAsync
64             object remains usable after closing. If a new API functions is
65             called, new connections are created.
66         """
67         await self.engine.dispose()
68
69
70     async def status(self) -> StatusResult:
71         """ Return the status of the database.
72         """
73         return await get_status(self.engine)
74
75
76 class NominatimAPI:
77     """ API loader, synchronous version.
78     """
79
80     def __init__(self, project_dir: Path,
81                  environ: Optional[Mapping[str, str]] = None) -> None:
82         self.async_api = NominatimAPIAsync(project_dir, environ)
83
84
85     def close(self) -> None:
86         """ Close all active connections to the database. The NominatimAPIAsync
87             object remains usable after closing. If a new API functions is
88             called, new connections are created.
89         """
90         asyncio.get_event_loop().run_until_complete(self.async_api.close())
91
92
93     def status(self) -> StatusResult:
94         """ Return the status of the database.
95         """
96         return asyncio.get_event_loop().run_until_complete(self.async_api.status())