]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/cli.py
api: generalize error handling
[nominatim.git] / nominatim / cli.py
index f911023b9507fd99fb6115d0c6f015dd9a959088..d34ef118ed9a48609c455616207d36387e006802 100644 (file)
@@ -8,6 +8,8 @@
 Command-line interface to the Nominatim functions for import, update,
 database administration and querying.
 """
 Command-line interface to the Nominatim functions for import, update,
 database administration and querying.
 """
+from typing import Optional, Any, List, Union
+import importlib
 import logging
 import os
 import sys
 import logging
 import os
 import sys
@@ -19,16 +21,15 @@ from nominatim.tools.exec_utils import run_legacy_script, run_php_server
 from nominatim.errors import UsageError
 from nominatim import clicmd
 from nominatim import version
 from nominatim.errors import UsageError
 from nominatim import clicmd
 from nominatim import version
-from nominatim.clicmd.args import NominatimArgs
+from nominatim.clicmd.args import NominatimArgs, Subcommand
 
 LOG = logging.getLogger()
 
 
 LOG = logging.getLogger()
 
-
 class CommandlineParser:
     """ Wraps some of the common functions for parsing the command line
         and setting up subcommands.
     """
 class CommandlineParser:
     """ Wraps some of the common functions for parsing the command line
         and setting up subcommands.
     """
-    def __init__(self, prog, description):
+    def __init__(self, prog: str, description: Optional[str]):
         self.parser = argparse.ArgumentParser(
             prog=prog,
             description=description,
         self.parser = argparse.ArgumentParser(
             prog=prog,
             description=description,
@@ -56,20 +57,23 @@ class CommandlineParser:
         group.add_argument('-j', '--threads', metavar='NUM', type=int,
                            help='Number of parallel threads to use')
 
         group.add_argument('-j', '--threads', metavar='NUM', type=int,
                            help='Number of parallel threads to use')
 
-    @staticmethod
-    def nominatim_version_text():
+
+    def nominatim_version_text(self) -> str:
         """ Program name and version number as string
         """
         """ 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
 
         if version.GIT_COMMIT_HASH is not None:
             text += f' ({version.GIT_COMMIT_HASH})'
         return text
 
-    def add_subcommand(self, name, cmd):
+
+    def add_subcommand(self, name: str, cmd: Subcommand) -> None:
         """ Add a subcommand to the parser. The subcommand must be a class
             with a function add_args() that adds the parameters for the
             subcommand and a run() function that executes the command.
         """
         """ Add a subcommand to the parser. The subcommand must be a class
             with a function add_args() that adds the parameters for the
             subcommand and a run() function that executes the command.
         """
+        assert cmd.__doc__ is not None
+
         parser = self.subs.add_parser(name, parents=[self.default_args],
                                       help=cmd.__doc__.split('\n', 1)[0],
                                       description=cmd.__doc__,
         parser = self.subs.add_parser(name, parents=[self.default_args],
                                       help=cmd.__doc__.split('\n', 1)[0],
                                       description=cmd.__doc__,
@@ -78,7 +82,8 @@ class CommandlineParser:
         parser.set_defaults(command=cmd)
         cmd.add_args(parser)
 
         parser.set_defaults(command=cmd)
         cmd.add_args(parser)
 
-    def run(self, **kwargs):
+
+    def run(self, **kwargs: Any) -> int:
         """ Parse the command line arguments of the program and execute the
             appropriate subcommand.
         """
         """ Parse the command line arguments of the program and execute the
             appropriate subcommand.
         """
@@ -89,16 +94,14 @@ class CommandlineParser:
             return 1
 
         if args.version:
             return 1
 
         if args.version:
-            print(CommandlineParser.nominatim_version_text())
+            print(self.nominatim_version_text())
             return 0
 
         if args.subcommand is None:
             self.parser.print_help()
             return 1
 
             return 0
 
         if args.subcommand is None:
             self.parser.print_help()
             return 1
 
-        for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
-                    'data_dir', 'config_dir', 'phpcgi_path'):
-            setattr(args, arg, Path(kwargs[arg]))
+        args.phpcgi_path = Path(kwargs['phpcgi_path'])
         args.project_dir = Path(args.project_dir).resolve()
 
         if 'cli_args' not in kwargs:
         args.project_dir = Path(args.project_dir).resolve()
 
         if 'cli_args' not in kwargs:
@@ -107,13 +110,10 @@ class CommandlineParser:
                                 datefmt='%Y-%m-%d %H:%M:%S',
                                 level=max(4 - args.verbose, 1) * 10)
 
                                 datefmt='%Y-%m-%d %H:%M:%S',
                                 level=max(4 - args.verbose, 1) * 10)
 
-        args.config = Configuration(args.project_dir, args.config_dir,
+        args.config = Configuration(args.project_dir,
                                     environ=kwargs.get('environ', os.environ))
                                     environ=kwargs.get('environ', os.environ))
-        args.config.set_libdirs(module=args.module_dir,
-                                osm2pgsql=args.osm2pgsql_path,
-                                php=args.phplib_dir,
-                                sql=args.sqllib_dir,
-                                data=args.data_dir)
+        args.config.set_libdirs(module=kwargs['module_dir'],
+                                osm2pgsql=kwargs['osm2pgsql_path'])
 
         log = logging.getLogger()
         log.warning('Using project directory: %s', str(args.project_dir))
 
         log = logging.getLogger()
         log.warning('Using project directory: %s', str(args.project_dir))
@@ -145,8 +145,7 @@ class QueryExport:
     Export addresses as CSV file from the database.
     """
 
     Export addresses as CSV file from the database.
     """
 
-    @staticmethod
-    def add_args(parser):
+    def add_args(self, parser: argparse.ArgumentParser) -> None:
         group = parser.add_argument_group('Output arguments')
         group.add_argument('--output-type', default='street',
                            choices=('continent', 'country', 'state', 'county',
         group = parser.add_argument_group('Output arguments')
         group.add_argument('--output-type', default='street',
                            choices=('continent', 'country', 'state', 'county',
@@ -175,11 +174,10 @@ class QueryExport:
                            help='Export only children of this OSM relation')
 
 
                            help='Export only children of this OSM relation')
 
 
-    @staticmethod
-    def run(args):
-        params = ['export.php',
-                  '--output-type', args.output_type,
-                  '--output-format', args.output_format]
+    def run(self, args: NominatimArgs) -> int:
+        params: List[Union[int, str]] = [
+                             '--output-type', args.output_type,
+                             '--output-format', args.output_format]
         if args.output_all_postcodes:
             params.append('--output-all-postcodes')
         if args.language:
         if args.output_all_postcodes:
             params.append('--output-all-postcodes')
         if args.language:
@@ -193,65 +191,101 @@ class QueryExport:
         if args.restrict_to_osm_relation:
             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
 
         if args.restrict_to_osm_relation:
             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
 
-        return run_legacy_script(*params, nominatim_env=args)
+        return run_legacy_script('export.php', *params, config=args.config)
 
 
 class AdminServe:
     """\
     Start a simple web server for serving the API.
 
 
 
 class AdminServe:
     """\
     Start a simple web server for serving the API.
 
-    This command starts the built-in PHP webserver to serve the website
+    This command starts a built-in webserver to serve the website
     from the current project directory. This webserver is only suitable
     for testing and development. Do not use it in production setups!
 
     from the current project directory. This webserver is only suitable
     for testing and development. Do not use it in production setups!
 
+    There are different webservers available. The default 'php' engine
+    runs the classic PHP frontend. The other engines are Python servers
+    which run the new Python frontend code. This is highly experimental
+    at the moment and may not include the full API.
+
     By the default, the webserver can be accessed at: http://127.0.0.1:8088
     """
 
     By the default, the webserver can be accessed at: http://127.0.0.1:8088
     """
 
-    @staticmethod
-    def add_args(parser):
+    def add_args(self, parser: argparse.ArgumentParser) -> None:
         group = parser.add_argument_group('Server arguments')
         group.add_argument('--server', default='127.0.0.1:8088',
                            help='The address the server will listen to.')
         group = parser.add_argument_group('Server arguments')
         group.add_argument('--server', default='127.0.0.1:8088',
                            help='The address the server will listen to.')
+        group.add_argument('--engine', default='php',
+                           choices=('php', 'sanic', 'falcon', 'starlette'),
+                           help='Webserver framework to run. (default: php)')
+
+
+    def run(self, args: NominatimArgs) -> int:
+        if args.engine == 'php':
+            run_php_server(args.server, args.project_dir / 'website')
+        else:
+            server_info = args.server.split(':', 1)
+            host = server_info[0]
+            if len(server_info) > 1:
+                if not server_info[1].isdigit():
+                    raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
+                port = int(server_info[1])
+            else:
+                port = 8088
+
+            if args.engine == 'sanic':
+                server_module = importlib.import_module('nominatim.server.sanic.server')
+
+                app = server_module.get_application(args.project_dir)
+                app.run(host=host, port=port, debug=True, single_process=True)
+            else:
+                import uvicorn # pylint: disable=import-outside-toplevel
+
+                if args.engine == 'falcon':
+                    server_module = importlib.import_module('nominatim.server.falcon.server')
+                elif args.engine == 'starlette':
+                    server_module = importlib.import_module('nominatim.server.starlette.server')
+
+                app = server_module.get_application(args.project_dir)
+                uvicorn.run(app, host=host, port=port)
+
+        return 0
 
 
-    @staticmethod
-    def run(args):
-        run_php_server(args.server, args.project_dir / 'website')
 
 
-def get_set_parser(**kwargs):
+def get_set_parser(**kwargs: Any) -> CommandlineParser:
     """\
     Initializes the parser and adds various subcommands for
     nominatim cli.
     """
     parser = CommandlineParser('nominatim', nominatim.__doc__)
 
     """\
     Initializes the parser and adds various subcommands for
     nominatim cli.
     """
     parser = CommandlineParser('nominatim', nominatim.__doc__)
 
-    parser.add_subcommand('import', clicmd.SetupAll)
-    parser.add_subcommand('freeze', clicmd.SetupFreeze)
-    parser.add_subcommand('replication', clicmd.UpdateReplication)
+    parser.add_subcommand('import', clicmd.SetupAll())
+    parser.add_subcommand('freeze', clicmd.SetupFreeze())
+    parser.add_subcommand('replication', clicmd.UpdateReplication())
 
 
-    parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
+    parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
 
 
-    parser.add_subcommand('add-data', clicmd.UpdateAddData)
-    parser.add_subcommand('index', clicmd.UpdateIndex)
+    parser.add_subcommand('add-data', clicmd.UpdateAddData())
+    parser.add_subcommand('index', clicmd.UpdateIndex())
     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
 
     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
 
-    parser.add_subcommand('admin', clicmd.AdminFuncs)
+    parser.add_subcommand('admin', clicmd.AdminFuncs())
 
 
-    parser.add_subcommand('export', QueryExport)
-    parser.add_subcommand('serve', AdminServe)
+    parser.add_subcommand('export', QueryExport())
+    parser.add_subcommand('serve', AdminServe())
 
     if kwargs.get('phpcgi_path'):
 
     if kwargs.get('phpcgi_path'):
-        parser.add_subcommand('search', clicmd.APISearch)
-        parser.add_subcommand('reverse', clicmd.APIReverse)
-        parser.add_subcommand('lookup', clicmd.APILookup)
-        parser.add_subcommand('details', clicmd.APIDetails)
-        parser.add_subcommand('status', clicmd.APIStatus)
+        parser.add_subcommand('search', clicmd.APISearch())
+        parser.add_subcommand('reverse', clicmd.APIReverse())
+        parser.add_subcommand('lookup', clicmd.APILookup())
+        parser.add_subcommand('details', clicmd.APIDetails())
+        parser.add_subcommand('status', clicmd.APIStatus())
     else:
         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
 
     return parser
 
 
     else:
         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
 
     return parser
 
 
-def nominatim(**kwargs):
+def nominatim(**kwargs: Any) -> int:
     """\
     Command-line tools for importing, updating, administrating and
     querying the Nominatim database.
     """\
     Command-line tools for importing, updating, administrating and
     querying the Nominatim database.