forked from hans/Nominatim
With the internal osm2pgsql gone, configuration of the binary location via settings is the only option left that makes sense.
245 lines
9.3 KiB
Python
245 lines
9.3 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# This file is part of Nominatim. (https://nominatim.org)
|
|
#
|
|
# 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, List, Mapping
|
|
import importlib
|
|
import logging
|
|
import sys
|
|
import argparse
|
|
import asyncio
|
|
from pathlib import Path
|
|
|
|
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.
|
|
"""
|
|
def __init__(self, prog: str, description: Optional[str]):
|
|
self.parser = argparse.ArgumentParser(
|
|
prog=prog,
|
|
description=description,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
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')
|
|
group.add_argument('-h', '--help', action='help',
|
|
help='Show this help message and exit')
|
|
group.add_argument('-q', '--quiet', action='store_const', const=0,
|
|
dest='verbose', default=1,
|
|
help='Print only error messages')
|
|
group.add_argument('-v', '--verbose', action='count', default=1,
|
|
help='Increase verboseness of output')
|
|
group.add_argument('--project-dir', metavar='DIR', default='.',
|
|
help='Base directory of the Nominatim installation (default:.)')
|
|
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'Nominatim version {version.NOMINATIM_VERSION!s}'
|
|
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.
|
|
"""
|
|
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__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
add_help=False)
|
|
parser.set_defaults(command=cmd)
|
|
cmd.add_args(parser)
|
|
|
|
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=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
|
|
|
|
args.project_dir = Path(args.project_dir).resolve()
|
|
|
|
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=environ)
|
|
|
|
log = logging.getLogger()
|
|
log.warning('Using project directory: %s', str(args.project_dir))
|
|
|
|
try:
|
|
return args.command.run(args)
|
|
except UsageError as exception:
|
|
if log.isEnabledFor(logging.DEBUG):
|
|
raise # use Python's exception printing
|
|
log.fatal('FATAL: %s', exception)
|
|
|
|
# If we get here, then execution has failed in some way.
|
|
return 1
|
|
|
|
|
|
# Subcommand classes
|
|
#
|
|
# Each class needs to implement two functions: add_args() adds the CLI parameters
|
|
# for the subfunction, run() executes the subcommand.
|
|
#
|
|
# The class documentation doubles as the help text for the command. The
|
|
# first line is also used in the summary when calling the program without
|
|
# a subcommand.
|
|
#
|
|
# No need to document the functions each time.
|
|
class AdminServe:
|
|
"""\
|
|
Start a simple web server for serving the API.
|
|
|
|
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!
|
|
|
|
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
|
|
"""
|
|
|
|
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.add_argument('--engine', default='falcon',
|
|
choices=('falcon', 'starlette'),
|
|
help='Webserver framework to run. (default: falcon)')
|
|
|
|
def run(self, args: NominatimArgs) -> int:
|
|
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:
|
|
port = 8088
|
|
|
|
server_module = importlib.import_module(f'nominatim_api.server.{args.engine}.server')
|
|
|
|
app = server_module.get_application(args.project_dir)
|
|
|
|
config = uvicorn.Config(app, host=host, port=port)
|
|
server = uvicorn.Server(config)
|
|
await server.serve()
|
|
|
|
|
|
def get_set_parser() -> CommandlineParser:
|
|
"""\
|
|
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('special-phrases', clicmd.ImportSpecialPhrases())
|
|
|
|
parser.add_subcommand('add-data', clicmd.UpdateAddData())
|
|
parser.add_subcommand('index', clicmd.UpdateIndex())
|
|
parser.add_subcommand('refresh', clicmd.UpdateRefresh())
|
|
|
|
parser.add_subcommand('admin', clicmd.AdminFuncs())
|
|
|
|
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(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(cli_args=cli_args, environ=environ)
|