]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/cli.py
fix liniting issues and add type annotations
[nominatim.git] / nominatim / cli.py
index 6c01a68ae5cebfe44711b59856c0e6a18abed2fd..e6214af815724d54d63c3fdcf5767e8f034ff345 100644 (file)
@@ -1,7 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This file is part of Nominatim. (https://nominatim.org)
+#
+# Copyright (C) 2022 by the Nominatim developer community.
+# For a full list of authors see the git log.
 """
 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
@@ -12,16 +20,16 @@ from nominatim.config import Configuration
 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
 from nominatim.errors import UsageError
 from nominatim import clicmd
 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
 from nominatim.errors import UsageError
 from nominatim import clicmd
-from nominatim.clicmd.args import NominatimArgs
+from nominatim import version
+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,
@@ -30,6 +38,10 @@ class CommandlineParser:
         self.subs = self.parser.add_subparsers(title='available commands',
                                                dest='subcommand')
 
         self.subs = self.parser.add_subparsers(title='available commands',
                                                dest='subcommand')
 
+        # Global arguments that only work if no sub-command given
+        self.parser.add_argument('--version', action='store_true',
+                                 help='Print Nominatim version and exit')
+
         # Arguments added to every sub-command
         self.default_args = argparse.ArgumentParser(add_help=False)
         group = self.default_args.add_argument_group('Default arguments')
         # Arguments added to every sub-command
         self.default_args = argparse.ArgumentParser(add_help=False)
         group = self.default_args.add_argument_group('Default arguments')
@@ -46,11 +58,22 @@ class CommandlineParser:
                            help='Number of parallel threads to use')
 
 
                            help='Number of parallel threads to use')
 
 
-    def add_subcommand(self, name, cmd):
+    def nominatim_version_text(self) -> str:
+        """ Program name and version number as string
+        """
+        text = f'Nominatim version {version.version_str()}'
+        if version.GIT_COMMIT_HASH is not None:
+            text += f' ({version.GIT_COMMIT_HASH})'
+        return text
+
+
+    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__,
@@ -59,20 +82,26 @@ 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.
         """
         args = NominatimArgs()
         """ Parse the command line arguments of the program and execute the
             appropriate subcommand.
         """
         args = NominatimArgs()
-        self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
+        try:
+            self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
+        except SystemExit:
+            return 1
+
+        if args.version:
+            print(self.nominatim_version_text())
+            return 0
 
         if args.subcommand is None:
             self.parser.print_help()
             return 1
 
 
         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:
@@ -81,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))
@@ -119,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',
@@ -149,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:
@@ -167,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)
+            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.