1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Command-line interface to the Nominatim functions for import, update,
9 database administration and querying.
11 from typing import Optional, Any
18 from pathlib import Path
20 from .config import Configuration
21 from .errors import UsageError
24 from .clicmd.args import NominatimArgs, Subcommand
26 LOG = logging.getLogger()
29 class CommandlineParser:
30 """ Wraps some of the common functions for parsing the command line
31 and setting up subcommands.
33 def __init__(self, prog: str, description: Optional[str]):
34 self.parser = argparse.ArgumentParser(
36 description=description,
37 formatter_class=argparse.RawDescriptionHelpFormatter)
39 self.subs = self.parser.add_subparsers(title='available commands',
42 # Global arguments that only work if no sub-command given
43 self.parser.add_argument('--version', action='store_true',
44 help='Print Nominatim version and exit')
46 # Arguments added to every sub-command
47 self.default_args = argparse.ArgumentParser(add_help=False)
48 group = self.default_args.add_argument_group('Default arguments')
49 group.add_argument('-h', '--help', action='help',
50 help='Show this help message and exit')
51 group.add_argument('-q', '--quiet', action='store_const', const=0,
52 dest='verbose', default=1,
53 help='Print only error messages')
54 group.add_argument('-v', '--verbose', action='count', default=1,
55 help='Increase verboseness of output')
56 group.add_argument('--project-dir', metavar='DIR', default='.',
57 help='Base directory of the Nominatim installation (default:.)')
58 group.add_argument('-j', '--threads', metavar='NUM', type=int,
59 help='Number of parallel threads to use')
61 def nominatim_version_text(self) -> str:
62 """ Program name and version number as string
64 text = f'Nominatim version {version.NOMINATIM_VERSION!s}'
65 if version.GIT_COMMIT_HASH is not None:
66 text += f' ({version.GIT_COMMIT_HASH})'
69 def add_subcommand(self, name: str, cmd: Subcommand) -> None:
70 """ Add a subcommand to the parser. The subcommand must be a class
71 with a function add_args() that adds the parameters for the
72 subcommand and a run() function that executes the command.
74 assert cmd.__doc__ is not None
76 parser = self.subs.add_parser(name, parents=[self.default_args],
77 help=cmd.__doc__.split('\n', 1)[0],
78 description=cmd.__doc__,
79 formatter_class=argparse.RawDescriptionHelpFormatter,
81 parser.set_defaults(command=cmd)
84 def run(self, **kwargs: Any) -> int:
85 """ Parse the command line arguments of the program and execute the
86 appropriate subcommand.
88 args = NominatimArgs()
90 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
95 print(self.nominatim_version_text())
98 if args.subcommand is None:
99 self.parser.print_help()
102 args.project_dir = Path(args.project_dir).resolve()
104 if 'cli_args' not in kwargs:
105 logging.basicConfig(stream=sys.stderr,
106 format='%(asctime)s: %(message)s',
107 datefmt='%Y-%m-%d %H:%M:%S',
108 level=max(4 - args.verbose, 1) * 10)
110 args.config = Configuration(args.project_dir,
111 environ=kwargs.get('environ', os.environ))
112 args.config.set_libdirs(osm2pgsql=kwargs['osm2pgsql_path'])
114 log = logging.getLogger()
115 log.warning('Using project directory: %s', str(args.project_dir))
118 ret = args.command.run(args)
121 except UsageError as exception:
122 if log.isEnabledFor(logging.DEBUG):
123 raise # use Python's exception printing
124 log.fatal('FATAL: %s', exception)
126 # If we get here, then execution has failed in some way.
132 # Each class needs to implement two functions: add_args() adds the CLI parameters
133 # for the subfunction, run() executes the subcommand.
135 # The class documentation doubles as the help text for the command. The
136 # first line is also used in the summary when calling the program without
139 # No need to document the functions each time.
142 Start a simple web server for serving the API.
144 This command starts a built-in webserver to serve the website
145 from the current project directory. This webserver is only suitable
146 for testing and development. Do not use it in production setups!
148 There are two different webserver implementations for Python available:
149 falcon (the default) and starlette. You need to make sure the
150 appropriate Python packages as well as the uvicorn package are
151 installed to use this function.
153 By the default, the webserver can be accessed at: http://127.0.0.1:8088
156 def add_args(self, parser: argparse.ArgumentParser) -> None:
157 group = parser.add_argument_group('Server arguments')
158 group.add_argument('--server', default='127.0.0.1:8088',
159 help='The address the server will listen to.')
160 group.add_argument('--engine', default='falcon',
161 choices=('falcon', 'starlette'),
162 help='Webserver framework to run. (default: falcon)')
164 def run(self, args: NominatimArgs) -> int:
165 asyncio.run(self.run_uvicorn(args))
169 async def run_uvicorn(self, args: NominatimArgs) -> None:
172 server_info = args.server.split(':', 1)
173 host = server_info[0]
174 if len(server_info) > 1:
175 if not server_info[1].isdigit():
176 raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
177 port = int(server_info[1])
181 server_module = importlib.import_module(f'nominatim_api.server.{args.engine}.server')
183 app = server_module.get_application(args.project_dir)
185 config = uvicorn.Config(app, host=host, port=port)
186 server = uvicorn.Server(config)
190 def get_set_parser() -> CommandlineParser:
192 Initializes the parser and adds various subcommands for
195 parser = CommandlineParser('nominatim', nominatim.__doc__)
197 parser.add_subcommand('import', clicmd.SetupAll())
198 parser.add_subcommand('freeze', clicmd.SetupFreeze())
199 parser.add_subcommand('replication', clicmd.UpdateReplication())
201 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
203 parser.add_subcommand('add-data', clicmd.UpdateAddData())
204 parser.add_subcommand('index', clicmd.UpdateIndex())
205 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
207 parser.add_subcommand('admin', clicmd.AdminFuncs())
210 exportcmd = importlib.import_module('nominatim_db.clicmd.export')
211 apicmd = importlib.import_module('nominatim_db.clicmd.api')
212 convertcmd = importlib.import_module('nominatim_db.clicmd.convert')
214 parser.add_subcommand('export', exportcmd.QueryExport())
215 parser.add_subcommand('convert', convertcmd.ConvertDB())
216 parser.add_subcommand('serve', AdminServe())
218 parser.add_subcommand('search', apicmd.APISearch())
219 parser.add_subcommand('reverse', apicmd.APIReverse())
220 parser.add_subcommand('lookup', apicmd.APILookup())
221 parser.add_subcommand('details', apicmd.APIDetails())
222 parser.add_subcommand('status', apicmd.APIStatus())
223 except ModuleNotFoundError as ex:
224 if not ex.name or 'nominatim_api' not in ex.name:
227 parser.parser.epilog = \
228 f'\n\nNominatim API package not found (was looking for module: {ex.name}).'\
229 '\nThe following commands are not available:'\
230 '\n export, convert, serve, search, reverse, lookup, details, status'\
231 "\n\nRun 'pip install nominatim-api' to install the package."
236 def nominatim(**kwargs: Any) -> int:
238 Command-line tools for importing, updating, administrating and
239 querying the Nominatim database.
241 return get_set_parser().run(**kwargs)