]> git.openstreetmap.org Git - nominatim.git/commitdiff
convert version to named tuple
authorSarah Hoffmann <lonvia@denofr.de>
Tue, 13 Dec 2022 09:36:19 +0000 (10:36 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Tue, 3 Jan 2023 09:03:00 +0000 (10:03 +0100)
Also return the new NominatimVersion rather than a string in the
status result.

nominatim/apicmd/status.py
nominatim/cli.py
nominatim/clicmd/setup.py
nominatim/result_formatter/v1.py
nominatim/tools/collect_os_info.py
nominatim/tools/exec_utils.py
nominatim/tools/migration.py
nominatim/tools/refresh.py
nominatim/version.py
test/python/api/test_api_status.py
test/python/result_formatter/test_v1.py

index 07a1c321789eeeb4b0992d39b71ec719da431c51..85071db9397853c64eb789c620a466a2cd81c313 100644 (file)
@@ -23,10 +23,9 @@ class StatusResult:
     def __init__(self, status: int, msg: str):
         self.status = status
         self.message = msg
-        # XXX versions really should stay tuples here
-        self.software_version = version.version_str()
+        self.software_version = version.NOMINATIM_VERSION
         self.data_updated: Optional[dt.datetime]  = None
-        self.database_version: Optional[str] = None
+        self.database_version: Optional[version.NominatimVersion] = None
 
 
 async def _get_database_date(conn: AsyncConnection) -> Optional[dt.datetime]:
@@ -41,13 +40,13 @@ async def _get_database_date(conn: AsyncConnection) -> Optional[dt.datetime]:
     return None
 
 
-async def _get_database_version(conn: AsyncConnection) -> Optional[str]:
+async def _get_database_version(conn: AsyncConnection) -> Optional[version.NominatimVersion]:
     sql = sqla.text("""SELECT value FROM nominatim_properties
                        WHERE property = 'database_version'""")
     result = await conn.execute(sql)
 
     for row in result:
-        return cast(str, row[0])
+        return version.parse_version(cast(str, row[0]))
 
     return None
 
index e6214af815724d54d63c3fdcf5767e8f034ff345..cedbdb4a5f984b0dc9b2bd5e60e450fad2adebd0 100644 (file)
@@ -61,7 +61,7 @@ class CommandlineParser:
     def nominatim_version_text(self) -> str:
         """ Program name and version number as string
         """
-        text = f'Nominatim version {version.version_str()}'
+        text = f'Nominatim version {version.NOMINATIM_VERSION!s}'
         if version.GIT_COMMIT_HASH is not None:
             text += f' ({version.GIT_COMMIT_HASH})'
         return text
index 68884fe121ad28a8e988cd801c7ee7142a254660..8464e151f4f1534034c442446a44bc7c27bce22c 100644 (file)
@@ -18,7 +18,7 @@ from nominatim.config import Configuration
 from nominatim.db.connection import connect
 from nominatim.db import status, properties
 from nominatim.tokenizer.base import AbstractTokenizer
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION
 from nominatim.clicmd.args import NominatimArgs
 from nominatim.errors import UsageError
 
@@ -205,4 +205,4 @@ class SetupAll:
                 except Exception as exc: # pylint: disable=broad-except
                     LOG.error('Cannot determine date of database: %s', exc)
 
-            properties.set_property(conn, 'database_version', version_str())
+            properties.set_property(conn, 'database_version', str(NOMINATIM_VERSION))
index 6d25ce6cf3c7ad70c348a3249a382b25db541483..1d437af7ed667a92247088c35cb316faa3b86874 100644 (file)
@@ -26,14 +26,13 @@ def _format_status_text(result: StatusResult) -> str:
 
 @create.format_func(StatusResult, 'json')
 def _format_status_json(result: StatusResult) -> str:
-    # XXX write a simple JSON serializer
     out: Dict[str, Any] = OrderedDict()
     out['status'] = result.status
     out['message'] = result.message
     if result.data_updated is not None:
         out['data_updated'] = result.data_updated.isoformat()
-    out['software_version'] = result.software_version
+    out['software_version'] = str(result.software_version)
     if result.database_version is not None:
-        out['database_version'] = result.database_version
+        out['database_version'] = str(result.database_version)
 
     return json.dumps(out)
index 9d76f229efd031aec445979a532ce994c39ba13d..29e1cd535672c768daaecc6151a343166a29a522 100644 (file)
@@ -20,7 +20,7 @@ from psycopg2.extensions import make_dsn, parse_dsn
 from nominatim.config import Configuration
 from nominatim.db.connection import connect
 from nominatim.typing import DictCursorResults
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION
 
 
 def convert_version(ver_tup: Tuple[int, int]) -> str:
@@ -135,8 +135,8 @@ def report_system_information(config: Configuration) -> None:
 
     **Software Environment:**
     - Python version: {sys.version}
-    - Nominatim version: {version_str()} 
-    - PostgreSQL version: {postgresql_ver} 
+    - Nominatim version: {NOMINATIM_VERSION!s}
+    - PostgreSQL version: {postgresql_ver}
     - PostGIS version: {postgis_ver}
     - OS: {os_name_info()}
     
index ab2ccc7cf9d83d4047cd5a7f5b7c14ba634bb29a..8417f146412c48fc08ccd2cced69c98c8557b6c5 100644 (file)
@@ -17,7 +17,7 @@ from urllib.parse import urlencode
 
 from nominatim.config import Configuration
 from nominatim.typing import StrPath
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION
 from nominatim.db.connection import get_pg_env
 
 LOG = logging.getLogger()
@@ -162,7 +162,7 @@ def run_osm2pgsql(options: Mapping[str, Any]) -> None:
 def get_url(url: str) -> str:
     """ Get the contents from the given URL and return it as a UTF-8 string.
     """
-    headers = {"User-Agent": f"Nominatim/{version_str()}"}
+    headers = {"User-Agent": f"Nominatim/{NOMINATIM_VERSION!s}"}
 
     try:
         request = urlrequest.Request(url, headers=headers)
index 10bca15c117c78e0210f82aaf68aab0ad96b6920..545f5c486a9c15e76106f081e4a1f1beb34ef761 100644 (file)
@@ -15,16 +15,14 @@ from psycopg2 import sql as pysql
 from nominatim.config import Configuration
 from nominatim.db import properties
 from nominatim.db.connection import connect, Connection
-from nominatim.version import NOMINATIM_VERSION, version_str
+from nominatim.version import NominatimVersion, NOMINATIM_VERSION, parse_version
 from nominatim.tools import refresh
 from nominatim.tokenizer import factory as tokenizer_factory
 from nominatim.errors import UsageError
 
 LOG = logging.getLogger()
 
-VersionTuple = Tuple[int, int, int, int]
-
-_MIGRATION_FUNCTIONS : List[Tuple[VersionTuple, Callable[..., None]]] = []
+_MIGRATION_FUNCTIONS : List[Tuple[NominatimVersion, Callable[..., None]]] = []
 
 def migrate(config: Configuration, paths: Any) -> int:
     """ Check for the current database version and execute migrations,
@@ -37,8 +35,7 @@ def migrate(config: Configuration, paths: Any) -> int:
             db_version_str = None
 
         if db_version_str is not None:
-            parts = db_version_str.split('.')
-            db_version = tuple(int(x) for x in parts[:2] + parts[2].split('-'))
+            db_version = parse_version(db_version_str)
 
             if db_version == NOMINATIM_VERSION:
                 LOG.warning("Database already at latest version (%s)", db_version_str)
@@ -53,8 +50,7 @@ def migrate(config: Configuration, paths: Any) -> int:
         for version, func in _MIGRATION_FUNCTIONS:
             if db_version <= version:
                 title = func.__doc__ or ''
-                LOG.warning("Running: %s (%s)", title.split('\n', 1)[0],
-                            version_str(version))
+                LOG.warning("Running: %s (%s)", title.split('\n', 1)[0], version)
                 kwargs = dict(conn=conn, config=config, paths=paths)
                 func(**kwargs)
                 conn.commit()
@@ -66,14 +62,14 @@ def migrate(config: Configuration, paths: Any) -> int:
             tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
             tokenizer.update_sql_functions(config)
 
-        properties.set_property(conn, 'database_version', version_str())
+        properties.set_property(conn, 'database_version', str(NOMINATIM_VERSION))
 
         conn.commit()
 
     return 0
 
 
-def _guess_version(conn: Connection) -> VersionTuple:
+def _guess_version(conn: Connection) -> NominatimVersion:
     """ Guess a database version when there is no property table yet.
         Only migrations for 3.6 and later are supported, so bail out
         when the version seems older.
@@ -89,7 +85,7 @@ def _guess_version(conn: Connection) -> VersionTuple:
                       'prior to 3.6.0. Automatic migration not possible.')
             raise UsageError('Migration not possible.')
 
-    return (3, 5, 0, 99)
+    return NominatimVersion(3, 5, 0, 99)
 
 
 
@@ -108,7 +104,8 @@ def _migration(major: int, minor: int, patch: int = 0,
         there.
     """
     def decorator(func: Callable[..., None]) -> Callable[..., None]:
-        _MIGRATION_FUNCTIONS.append(((major, minor, patch, dbpatch), func))
+        version = (NominatimVersion(major, minor, patch, dbpatch))
+        _MIGRATION_FUNCTIONS.append((version, func))
         return func
 
     return decorator
index b35d3aae1c6a2f269ca8c132da82d4d0f3713007..457960146b6b0343d1f9934ee42cfa0f0e9db697 100644 (file)
@@ -18,7 +18,7 @@ from nominatim.config import Configuration
 from nominatim.db.connection import Connection, connect
 from nominatim.db.utils import execute_file
 from nominatim.db.sql_preprocessor import SQLPreprocessor
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION
 
 LOG = logging.getLogger()
 
@@ -223,7 +223,7 @@ def setup_website(basedir: Path, config: Configuration, conn: Connection) -> Non
                       @define('CONST_Debug', $_GET['debug'] ?? false);
                       @define('CONST_LibDir', '{config.lib_dir.php}');
                       @define('CONST_TokenizerDir', '{config.project_dir / 'tokenizer'}');
-                      @define('CONST_NominatimVersion', '{version_str()}');
+                      @define('CONST_NominatimVersion', '{NOMINATIM_VERSION!s}');
 
                       """)
 
index 43a30d9e3bbfc17eb979dd0849c293c670e34363..40e3bda42d9f6f2a06fad2bf8a588b803d98c9b8 100644 (file)
@@ -7,25 +7,34 @@
 """
 Version information for Nominatim.
 """
-from typing import Optional, Tuple
+from typing import Optional, NamedTuple
 
-# Version information: major, minor, patch level, database patch level
-#
-# The first three numbers refer to the last released version.
-#
-# The database patch level tracks important changes between releases
-# and must always be increased when there is a change to the database or code
-# that requires a migration.
-#
-# When adding a migration on the development branch, raise the patch level
-# to 99 to make sure that the migration is applied when updating from a
-# patch release to the next minor version. Patch releases usually shouldn't
-# have migrations in them. When they are needed, then make sure that the
-# migration can be reapplied and set the migration version to the appropriate
-# patch level when cherry-picking the commit with the migration.
-#
-# Released versions always have a database patch level of 0.
-NOMINATIM_VERSION = (4, 2, 99, 0)
+class NominatimVersion(NamedTuple):
+    """ Version information for Nominatim. We follow semantic versioning.
+
+        Major, minor and patch_level refer to the last released version.
+        The database patch level tracks important changes between releases
+        and must always be increased when there is a change to the database or code
+        that requires a migration.
+
+        When adding a migration on the development branch, raise the patch level
+        to 99 to make sure that the migration is applied when updating from a
+        patch release to the next minor version. Patch releases usually shouldn't
+        have migrations in them. When they are needed, then make sure that the
+        migration can be reapplied and set the migration version to the appropriate
+        patch level when cherry-picking the commit with the migration.
+    """
+
+    major: int
+    minor: int
+    patch_level: int
+    db_patch_level: int
+
+    def __str__(self) -> str:
+        return f"{self.major}.{self.minor}.{self.patch_level}-{self.db_patch_level}"
+
+
+NOMINATIM_VERSION = NominatimVersion(4, 2, 99, 0)
 
 POSTGRESQL_REQUIRED_VERSION = (9, 6)
 POSTGIS_REQUIRED_VERSION = (2, 2)
@@ -37,9 +46,11 @@ POSTGIS_REQUIRED_VERSION = (2, 2)
 GIT_COMMIT_HASH : Optional[str] = None
 
 
-# pylint: disable=consider-using-f-string
-def version_str(version:Tuple[int, int, int, int] = NOMINATIM_VERSION) -> str:
-    """
-    Return a human-readable string of the version.
+def parse_version(version: str) -> NominatimVersion:
+    """ Parse a version string into a version consisting of a tuple of
+        four ints: major, minor, patch level, database patch level
+
+        This is the reverse operation of `version_str()`.
     """
-    return '{}.{}.{}-{}'.format(*version)
+    parts = version.split('.')
+    return NominatimVersion(*[int(x) for x in parts[:2] + parts[2].split('-')])
index 38c0fa7fe6b23078d29ffbc333df567d1b8665b2..6bc1fccc095d15572139558274f98ad8919eabcb 100644 (file)
@@ -11,7 +11,7 @@ from pathlib import Path
 import datetime as dt
 import pytest
 
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION, NominatimVersion
 from nominatim.api import NominatimAPI
 
 def test_status_no_extra_info(apiobj, table_factory):
@@ -24,7 +24,7 @@ def test_status_no_extra_info(apiobj, table_factory):
 
     assert result.status == 0
     assert result.message == 'OK'
-    assert result.software_version == version_str()
+    assert result.software_version == NOMINATIM_VERSION
     assert result.database_version is None
     assert result.data_updated is None
 
@@ -41,8 +41,8 @@ def test_status_full(apiobj, table_factory):
 
     assert result.status == 0
     assert result.message == 'OK'
-    assert result.software_version == version_str()
-    assert result.database_version == '99.5.4-2'
+    assert result.software_version == NOMINATIM_VERSION
+    assert result.database_version == NominatimVersion(99, 5, 4, 2)
     assert result.data_updated == dt.datetime(2022, 12, 7, 14, 14, 46, 0, tzinfo=dt.timezone.utc)
 
 
@@ -55,6 +55,6 @@ def test_status_database_not_found(monkeypatch):
 
     assert result.status == 700
     assert result.message == 'Database connection failed'
-    assert result.software_version == version_str()
+    assert result.software_version == NOMINATIM_VERSION
     assert result.database_version is None
     assert result.data_updated is None
index 5b7d2bfc4ff78c8d0dd0c452000b4113dfecf9f8..919f5b8091e988956f782d1d9ff8d6b7d06778a3 100644 (file)
@@ -12,7 +12,7 @@ import pytest
 
 import nominatim.result_formatter.v1 as format_module
 from nominatim.apicmd.status import StatusResult
-from nominatim.version import version_str
+from nominatim.version import NOMINATIM_VERSION
 
 STATUS_FORMATS = {'text', 'json'}
 
@@ -50,7 +50,7 @@ class TestStatusResultFormat:
 
         result = self.formatter.format(status, 'json')
 
-        assert result == '{"status": 700, "message": "Bad format.", "software_version": "%s"}' % (version_str())
+        assert result == '{"status": 700, "message": "Bad format.", "software_version": "%s"}' % (NOMINATIM_VERSION, )
 
 
     def test_format_json_full(self):
@@ -60,4 +60,4 @@ class TestStatusResultFormat:
 
         result = self.formatter.format(status, 'json')
 
-        assert result == '{"status": 0, "message": "OK", "data_updated": "2010-02-07T20:20:03+00:00", "software_version": "%s", "database_version": "5.6"}' % (version_str())
+        assert result == '{"status": 0, "message": "OK", "data_updated": "2010-02-07T20:20:03+00:00", "software_version": "%s", "database_version": "5.6"}' % (NOMINATIM_VERSION, )