]> git.openstreetmap.org Git - nominatim.git/commitdiff
introduce custom UsageError
authorSarah Hoffmann <lonvia@denofr.de>
Sat, 30 Jan 2021 15:20:10 +0000 (16:20 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Sat, 30 Jan 2021 15:20:10 +0000 (16:20 +0100)
This is a exception to be thrown when the error occures because
of bad user data. We don't want to print a full stack trace in
these cases but just tell the user what went wrong.

nominatim/cli.py
nominatim/config.py
nominatim/db/connection.py
nominatim/db/status.py
nominatim/errors.py [new file with mode: 0644]
nominatim/tools/replication.py
test/python/test_cli.py
test/python/test_config.py
test/python/test_db_connection.py
test/python/test_db_status.py
test/python/test_tools_replication.py

index f02277aebbce8e476e98f7989218eb395f839ac7..cc591ea583c5c7ec9457aa2e758662148c121809 100644 (file)
@@ -14,6 +14,7 @@ from .config import Configuration
 from .tools.exec_utils import run_legacy_script, run_api_script
 from .db.connection import connect
 from .db import status
 from .tools.exec_utils import run_legacy_script, run_api_script
 from .db.connection import connect
 from .db import status
+from .errors import UsageError
 
 LOG = logging.getLogger()
 
 
 LOG = logging.getLogger()
 
@@ -89,7 +90,16 @@ class CommandlineParser:
 
         args.config = Configuration(args.project_dir, args.data_dir / 'settings')
 
 
         args.config = Configuration(args.project_dir, args.data_dir / 'settings')
 
-        return args.command.run(args)
+        try:
+            return args.command.run(args)
+        except UsageError as e:
+            log = logging.getLogger()
+            if log.isEnabledFor(logging.DEBUG):
+                raise # use Python's exception printing
+            log.fatal('FATAL: ' + str(e))
+
+        # If we get here, then execution has failed in some way.
+        return 1
 
 
 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
 
 
 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
@@ -292,12 +302,12 @@ class UpdateReplication:
                       "Please check install documentation "
                       "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
                       "setting-up-the-update-process).")
                       "Please check install documentation "
                       "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
                       "setting-up-the-update-process).")
-            raise RuntimeError("Invalid replication update interval setting.")
+            raise UsageError("Invalid replication update interval setting.")
 
         if not args.once:
             if not args.do_index:
                 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
 
         if not args.once:
             if not args.do_index:
                 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
-                raise RuntimeError("Bad arguments.")
+                raise UsageError("Bad argument '--no-index'.")
             recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
 
         while True:
             recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
 
         while True:
index 271d2d4d4ee20b0d37fde49de1429b5b9da7f0fc..4de2052ee4987ff4892ef4a5a92b6fa3e53d21dd 100644 (file)
@@ -7,6 +7,8 @@ from pathlib import Path
 
 from dotenv import dotenv_values
 
 
 from dotenv import dotenv_values
 
+from .errors import UsageError
+
 LOG = logging.getLogger()
 
 class Configuration:
 LOG = logging.getLogger()
 
 class Configuration:
@@ -57,7 +59,7 @@ class Configuration:
             return int(self.__getattr__(name))
         except ValueError:
             LOG.fatal("Invalid setting NOMINATIM_%s. Needs to be a number.", name)
             return int(self.__getattr__(name))
         except ValueError:
             LOG.fatal("Invalid setting NOMINATIM_%s. Needs to be a number.", name)
-            raise
+            raise UsageError("Configuration error.")
 
 
     def get_libpq_dsn(self):
 
 
     def get_libpq_dsn(self):
index 8e75d7a271578f559d7015845d5cde867faa08cb..4d30151d7f20acb8d4e8e8a18e2d1d5e8856d5fb 100644 (file)
@@ -27,7 +27,7 @@ class _Cursor(psycopg2.extras.DictCursor):
         self.execute(sql, args)
 
         if self.rowcount != 1:
         self.execute(sql, args)
 
         if self.rowcount != 1:
-            raise ValueError("Query did not return a single row.")
+            raise RuntimeError("Query did not return a single row.")
 
         return self.fetchone()[0]
 
 
         return self.fetchone()[0]
 
index 9d7344c48754e93313be543a60d3120ed02fcb61..75da3c166029fa8cc4266b9a76aed5fe97b7dcdc 100644 (file)
@@ -6,6 +6,7 @@ import logging
 import re
 
 from ..tools.exec_utils import get_url
 import re
 
 from ..tools.exec_utils import get_url
+from ..errors import UsageError
 
 LOG = logging.getLogger()
 
 
 LOG = logging.getLogger()
 
@@ -19,7 +20,7 @@ def compute_database_date(conn):
 
         if osmid is None:
             LOG.fatal("No data found in the database.")
 
         if osmid is None:
             LOG.fatal("No data found in the database.")
-            raise RuntimeError("No data found in the database.")
+            raise UsageError("No data found in the database.")
 
     LOG.info("Using node id %d for timestamp lookup", osmid)
     # Get the node from the API to find the timestamp when it was created.
 
     LOG.info("Using node id %d for timestamp lookup", osmid)
     # Get the node from the API to find the timestamp when it was created.
@@ -31,7 +32,7 @@ def compute_database_date(conn):
     if match is None:
         LOG.fatal("The node data downloaded from the API does not contain valid data.\n"
                   "URL used: %s", node_url)
     if match is None:
         LOG.fatal("The node data downloaded from the API does not contain valid data.\n"
                   "URL used: %s", node_url)
-        raise RuntimeError("Bad API data.")
+        raise UsageError("Bad API data.")
 
     LOG.debug("Found timestamp %s", match[1])
 
 
     LOG.debug("Found timestamp %s", match[1])
 
diff --git a/nominatim/errors.py b/nominatim/errors.py
new file mode 100644 (file)
index 0000000..80181ab
--- /dev/null
@@ -0,0 +1,9 @@
+"""
+Custom exception and error classes for Nominatim.
+"""
+
+class UsageError(Exception):
+    """ An error raised because of bad user input. This error will usually
+        not cause a stack trace to be printed unless debugging is enabled.
+    """
+    pass
index 04f1c45b9cd728b7b0abe7cbb5ff892eb0fd7bef..c7d0d3e5d8e2621d410c8e40bbb9adee1c2b68c8 100644 (file)
@@ -11,6 +11,7 @@ from osmium import WriteHandler
 
 from ..db import status
 from .exec_utils import run_osm2pgsql
 
 from ..db import status
 from .exec_utils import run_osm2pgsql
+from ..errors import UsageError
 
 LOG = logging.getLogger()
 
 
 LOG = logging.getLogger()
 
@@ -31,7 +32,7 @@ def init_replication(conn, base_url):
         LOG.fatal("Cannot reach the configured replication service '%s'.\n"
                   "Does the URL point to a directory containing OSM update data?",
                   base_url)
         LOG.fatal("Cannot reach the configured replication service '%s'.\n"
                   "Does the URL point to a directory containing OSM update data?",
                   base_url)
-        raise RuntimeError("Failed to reach replication service")
+        raise UsageError("Failed to reach replication service")
 
     status.set_status(conn, date=date, seq=seq)
 
 
     status.set_status(conn, date=date, seq=seq)
 
@@ -80,7 +81,7 @@ def update(conn, options):
     if startseq is None:
         LOG.error("Replication not set up. "
                   "Please run 'nominatim replication --init' first.")
     if startseq is None:
         LOG.error("Replication not set up. "
                   "Please run 'nominatim replication --init' first.")
-        raise RuntimeError("Replication not set up.")
+        raise UsageError("Replication not set up.")
 
     if not indexed and options['indexed_only']:
         LOG.info("Skipping update. There is data that needs indexing.")
 
     if not indexed and options['indexed_only']:
         LOG.info("Skipping update. There is data that needs indexing.")
index fc2454cd219a3f85edf14b3c2eac7c13f7f851b6..c4f3ef36e3dc3c095c4eedd1d3a53e9a69c735f5 100644 (file)
@@ -13,6 +13,7 @@ import nominatim.cli
 import nominatim.indexer.indexer
 import nominatim.tools.refresh
 import nominatim.tools.replication
 import nominatim.indexer.indexer
 import nominatim.tools.refresh
 import nominatim.tools.replication
+from nominatim.errors import UsageError
 
 def call_nominatim(*args):
     return nominatim.cli.nominatim(module_dir='build/module',
 
 def call_nominatim(*args):
     return nominatim.cli.nominatim(module_dir='build/module',
@@ -150,16 +151,14 @@ def test_replication_command(monkeypatch, temp_db, params, func):
 def test_replication_update_bad_interval(monkeypatch, temp_db):
     monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', 'xx')
 
 def test_replication_update_bad_interval(monkeypatch, temp_db):
     monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', 'xx')
 
-    with pytest.raises(ValueError):
-        call_nominatim('replication')
+    assert call_nominatim('replication') == 1
 
 
 def test_replication_update_bad_interval_for_geofabrik(monkeypatch, temp_db):
     monkeypatch.setenv('NOMINATIM_REPLICATION_URL',
                        'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates')
 
 
 
 def test_replication_update_bad_interval_for_geofabrik(monkeypatch, temp_db):
     monkeypatch.setenv('NOMINATIM_REPLICATION_URL',
                        'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates')
 
-    with pytest.raises(RuntimeError, match='Invalid replication.*'):
-        call_nominatim('replication')
+    assert call_nominatim('replication') == 1
 
 
 @pytest.mark.parametrize("state, retval", [
 
 
 @pytest.mark.parametrize("state, retval", [
index f9fefeb2215c900279a9bad81d22ecce35ff6a5c..4578be1379cfa680ba413db33ebfdfb95be1708c 100644 (file)
@@ -7,6 +7,7 @@ import tempfile
 import pytest
 
 from nominatim.config import Configuration
 import pytest
 
 from nominatim.config import Configuration
+from nominatim.errors import UsageError
 
 DEFCFG_DIR = Path(__file__) / '..' / '..' / '..' / 'settings'
 
 
 DEFCFG_DIR = Path(__file__) / '..' / '..' / '..' / 'settings'
 
@@ -123,7 +124,7 @@ def test_get_int_bad_values(monkeypatch, value):
 
     monkeypatch.setenv('NOMINATIM_FOOBAR', value)
 
 
     monkeypatch.setenv('NOMINATIM_FOOBAR', value)
 
-    with pytest.raises(ValueError):
+    with pytest.raises(UsageError):
         config.get_int('FOOBAR')
 
 
         config.get_int('FOOBAR')
 
 
@@ -132,7 +133,7 @@ def test_get_int_empty():
 
     assert config.DATABASE_MODULE_PATH == ''
 
 
     assert config.DATABASE_MODULE_PATH == ''
 
-    with pytest.raises(ValueError):
+    with pytest.raises(UsageError):
         config.get_int('DATABASE_MODULE_PATH')
 
 
         config.get_int('DATABASE_MODULE_PATH')
 
 
index 5c484558b8016821b9f755330f109c794bc61b6e..ef1ae7416cc9d23a3d3c11433d5ea444c3c9262b 100644 (file)
@@ -28,5 +28,5 @@ def test_cursor_scalar(db, temp_db_cursor):
 
 def test_cursor_scalar_many_rows(db):
     with db.cursor() as cur:
 
 def test_cursor_scalar_many_rows(db):
     with db.cursor() as cur:
-        with pytest.raises(ValueError):
+        with pytest.raises(RuntimeError):
             cur.scalar('SELECT * FROM pg_tables')
             cur.scalar('SELECT * FROM pg_tables')
index 1a538aec2156f2687d360565bedb03db23f64ce1..399a0036ba96186597deb1038dd14f9e60da0082 100644 (file)
@@ -6,9 +6,10 @@ import datetime as dt
 import pytest
 
 import nominatim.db.status
 import pytest
 
 import nominatim.db.status
+from nominatim.errors import UsageError
 
 def test_compute_database_date_place_empty(status_table, place_table, temp_db_conn):
 
 def test_compute_database_date_place_empty(status_table, place_table, temp_db_conn):
-    with pytest.raises(RuntimeError):
+    with pytest.raises(UsageError):
         nominatim.db.status.compute_database_date(temp_db_conn)
 
 OSM_NODE_DATA = """\
         nominatim.db.status.compute_database_date(temp_db_conn)
 
 OSM_NODE_DATA = """\
@@ -44,7 +45,7 @@ def test_compute_database_broken_api(monkeypatch, status_table, place_row, temp_
 
     monkeypatch.setattr(nominatim.db.status, "get_url", mock_url)
 
 
     monkeypatch.setattr(nominatim.db.status, "get_url", mock_url)
 
-    with pytest.raises(RuntimeError):
+    with pytest.raises(UsageError):
         date = nominatim.db.status.compute_database_date(temp_db_conn)
 
 
         date = nominatim.db.status.compute_database_date(temp_db_conn)
 
 
index b94563ffa849f8c7c2ffd3a77b8cef1ce0cf3f99..156385ad8bb337e1403b1a55e737c7ef766d90ef 100644 (file)
@@ -9,6 +9,7 @@ from osmium.replication.server import OsmosisState
 
 import nominatim.tools.replication
 import nominatim.db.status as status
 
 import nominatim.tools.replication
 import nominatim.db.status as status
+from nominatim.errors import UsageError
 
 OSM_NODE_DATA = """\
 <osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
 
 OSM_NODE_DATA = """\
 <osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
@@ -24,7 +25,7 @@ def test_init_replication_bad_base_url(monkeypatch, status_table, place_row, tem
 
     monkeypatch.setattr(nominatim.db.status, "get_url", lambda u : OSM_NODE_DATA)
 
 
     monkeypatch.setattr(nominatim.db.status, "get_url", lambda u : OSM_NODE_DATA)
 
-    with pytest.raises(RuntimeError, match="Failed to reach replication service"):
+    with pytest.raises(UsageError, match="Failed to reach replication service"):
         nominatim.tools.replication.init_replication(temp_db_conn, 'https://test.io')
 
 
         nominatim.tools.replication.init_replication(temp_db_conn, 'https://test.io')
 
 
@@ -90,7 +91,7 @@ def update_options(tmpdir):
                    max_diff_size=1)
 
 def test_update_empty_status_table(status_table, temp_db_conn):
                    max_diff_size=1)
 
 def test_update_empty_status_table(status_table, temp_db_conn):
-    with pytest.raises(RuntimeError):
+    with pytest.raises(UsageError):
         nominatim.tools.replication.update(temp_db_conn, {})
 
 
         nominatim.tools.replication.update(temp_db_conn, {})