#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 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.
"""
-from typing import Optional, Any
+from typing import Optional, List, Mapping
import importlib
import logging
-import os
import sys
import argparse
+import asyncio
from pathlib import Path
-from nominatim_core.config import Configuration
-from nominatim_core.errors import UsageError
-from .tools.exec_utils import run_php_server
+from .config import Configuration
+from .errors import UsageError
from . import clicmd
from . import version
from .clicmd.args import NominatimArgs, Subcommand
LOG = logging.getLogger()
+
class CommandlineParser:
""" Wraps some of the common functions for parsing the command line
and setting up subcommands.
group.add_argument('-j', '--threads', metavar='NUM', type=int,
help='Number of parallel threads to use')
-
def nominatim_version_text(self) -> str:
""" Program name and version number as string
"""
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
parser.set_defaults(command=cmd)
cmd.add_args(parser)
-
- def run(self, **kwargs: Any) -> int:
+ def run(self, cli_args: Optional[List[str]],
+ environ: Optional[Mapping[str, str]]) -> int:
""" Parse the command line arguments of the program and execute the
appropriate subcommand.
"""
args = NominatimArgs()
try:
- self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
+ self.parser.parse_args(args=cli_args, namespace=args)
except SystemExit:
return 1
args.project_dir = Path(args.project_dir).resolve()
- if 'cli_args' not in kwargs:
+ if cli_args is None:
logging.basicConfig(stream=sys.stderr,
format='%(asctime)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=max(4 - args.verbose, 1) * 10)
- args.config = Configuration(args.project_dir,
- environ=kwargs.get('environ', os.environ))
- args.config.set_libdirs(module=kwargs['module_dir'],
- osm2pgsql=kwargs['osm2pgsql_path'])
+ args.config = Configuration(args.project_dir, environ=environ)
log = logging.getLogger()
log.warning('Using project directory: %s', str(args.project_dir))
return args.command.run(args)
except UsageError as exception:
if log.isEnabledFor(logging.DEBUG):
- raise # use Python's exception printing
+ raise # use Python's exception printing
log.fatal('FATAL: %s', exception)
# If we get here, then execution has failed in some way.
# a subcommand.
#
# No need to document the functions each time.
-# pylint: disable=C0111
class AdminServe:
"""\
Start a simple web server for serving the API.
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.
+ There are two different webserver implementations for Python available:
+ falcon (the default) and starlette. You need to make sure the
+ appropriate Python packages as well as the uvicorn package are
+ installed to use this function.
By the default, the webserver can be accessed at: http://127.0.0.1:8088
"""
group.add_argument('--server', default='127.0.0.1:8088',
help='The address the server will listen to.')
group.add_argument('--engine', default='falcon',
- choices=('php', 'falcon', 'starlette'),
+ choices=('falcon', 'starlette'),
help='Webserver framework to run. (default: falcon)')
-
def run(self, args: NominatimArgs) -> int:
- if args.engine == 'php':
- if args.config.lib_dir.php is None:
- raise UsageError("PHP frontend not configured.")
- run_php_server(args.server, args.project_dir / 'website')
+ asyncio.run(self.run_uvicorn(args))
+
+ return 0
+
+ async def run_uvicorn(self, args: NominatimArgs) -> None:
+ import uvicorn
+
+ 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:
- import uvicorn # pylint: disable=import-outside-toplevel
- 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
+ port = 8088
- server_module = importlib.import_module(f'nominatim.server.{args.engine}.server')
+ server_module = importlib.import_module(f'nominatim_api.server.{args.engine}.server')
- app = server_module.get_application(args.project_dir)
- uvicorn.run(app, host=host, port=port)
+ app = server_module.get_application(args.project_dir)
- return 0
+ config = uvicorn.Config(app, host=host, port=port)
+ server = uvicorn.Server(config)
+ await server.serve()
def get_set_parser() -> CommandlineParser:
parser.add_subcommand('admin', clicmd.AdminFuncs())
- parser.add_subcommand('export', clicmd.QueryExport())
- parser.add_subcommand('convert', clicmd.ConvertDB())
- parser.add_subcommand('serve', AdminServe())
-
- 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())
+ try:
+ exportcmd = importlib.import_module('nominatim_db.clicmd.export')
+ apicmd = importlib.import_module('nominatim_db.clicmd.api')
+ convertcmd = importlib.import_module('nominatim_db.clicmd.convert')
+
+ parser.add_subcommand('export', exportcmd.QueryExport())
+ parser.add_subcommand('convert', convertcmd.ConvertDB())
+ parser.add_subcommand('serve', AdminServe())
+
+ parser.add_subcommand('search', apicmd.APISearch())
+ parser.add_subcommand('reverse', apicmd.APIReverse())
+ parser.add_subcommand('lookup', apicmd.APILookup())
+ parser.add_subcommand('details', apicmd.APIDetails())
+ parser.add_subcommand('status', apicmd.APIStatus())
+ except ModuleNotFoundError as ex:
+ if not ex.name or 'nominatim_api' not in ex.name:
+ raise ex
+
+ parser.parser.epilog = \
+ f'\n\nNominatim API package not found (was looking for module: {ex.name}).'\
+ '\nThe following commands are not available:'\
+ '\n export, convert, serve, search, reverse, lookup, details, status'\
+ "\n\nRun 'pip install nominatim-api' to install the package."
return parser
-def nominatim(**kwargs: Any) -> int:
+def nominatim(cli_args: Optional[List[str]] = None,
+ environ: Optional[Mapping[str, str]] = None) -> int:
"""\
Command-line tools for importing, updating, administrating and
querying the Nominatim database.
+
+ 'cli_args' is a list of parameters for the command to run. If not given,
+ sys.args will be used.
+
+ 'environ' is the dictionary of environment variables containing the
+ Nominatim configuration. When None, the os.environ is inherited.
"""
- return get_set_parser().run(**kwargs)
+ return get_set_parser().run(cli_args=cli_args, environ=environ)