1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2025 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, List, Mapping
17 from pathlib import Path
19 from .config import Configuration
20 from .errors import UsageError
23 from .clicmd.args import NominatimArgs, Subcommand
25 LOG = logging.getLogger()
28 class CommandlineParser:
29 """ Wraps some of the common functions for parsing the command line
30 and setting up subcommands.
32 def __init__(self, prog: str, description: Optional[str]):
33 self.parser = argparse.ArgumentParser(
35 description=description,
36 formatter_class=argparse.RawDescriptionHelpFormatter)
38 self.subs = self.parser.add_subparsers(title='available commands',
41 # Global arguments that only work if no sub-command given
42 self.parser.add_argument('--version', action='store_true',
43 help='Print Nominatim version and exit')
45 # Arguments added to every sub-command
46 self.default_args = argparse.ArgumentParser(add_help=False)
47 group = self.default_args.add_argument_group('Default arguments')
48 group.add_argument('-h', '--help', action='help',
49 help='Show this help message and exit')
50 group.add_argument('-q', '--quiet', action='store_const', const=0,
51 dest='verbose', default=1,
52 help='Print only error messages')
53 group.add_argument('-v', '--verbose', action='count', default=1,
54 help='Increase verboseness of output')
55 group.add_argument('--project-dir', metavar='DIR', default='.',
56 help='Base directory of the Nominatim installation (default:.)')
57 group.add_argument('-j', '--threads', metavar='NUM', type=int,
58 help='Number of parallel threads to use')
60 def nominatim_version_text(self) -> str:
61 """ Program name and version number as string
63 text = f'Nominatim version {version.NOMINATIM_VERSION!s}'
64 if version.GIT_COMMIT_HASH is not None:
65 text += f' ({version.GIT_COMMIT_HASH})'
68 def add_subcommand(self, name: str, cmd: Subcommand) -> None:
69 """ Add a subcommand to the parser. The subcommand must be a class
70 with a function add_args() that adds the parameters for the
71 subcommand and a run() function that executes the command.
73 assert cmd.__doc__ is not None
75 parser = self.subs.add_parser(name, parents=[self.default_args],
76 help=cmd.__doc__.split('\n', 1)[0],
77 description=cmd.__doc__,
78 formatter_class=argparse.RawDescriptionHelpFormatter,
80 parser.set_defaults(command=cmd)
83 def run(self, cli_args: Optional[List[str]],
84 environ: Optional[Mapping[str, str]]) -> 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=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()
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, environ=environ)
112 log = logging.getLogger()
113 log.warning('Using project directory: %s', str(args.project_dir))
116 return args.command.run(args)
117 except UsageError as exception:
118 if log.isEnabledFor(logging.DEBUG):
119 raise # use Python's exception printing
120 log.fatal('FATAL: %s', exception)
122 # If we get here, then execution has failed in some way.
128 # Each class needs to implement two functions: add_args() adds the CLI parameters
129 # for the subfunction, run() executes the subcommand.
131 # The class documentation doubles as the help text for the command. The
132 # first line is also used in the summary when calling the program without
135 # No need to document the functions each time.
138 Start a simple web server for serving the API.
140 This command starts a built-in webserver to serve the website
141 from the current project directory. This webserver is only suitable
142 for testing and development. Do not use it in production setups!
144 There are two different webserver implementations for Python available:
145 falcon (the default) and starlette. You need to make sure the
146 appropriate Python packages as well as the uvicorn package are
147 installed to use this function.
149 By the default, the webserver can be accessed at: http://127.0.0.1:8088
152 def add_args(self, parser: argparse.ArgumentParser) -> None:
153 group = parser.add_argument_group('Server arguments')
154 group.add_argument('--server', default='127.0.0.1:8088',
155 help='The address the server will listen to.')
156 group.add_argument('--engine', default='falcon',
157 choices=('falcon', 'starlette'),
158 help='Webserver framework to run. (default: falcon)')
160 def run(self, args: NominatimArgs) -> int:
161 asyncio.run(self.run_uvicorn(args))
165 async def run_uvicorn(self, args: NominatimArgs) -> None:
168 server_info = args.server.split(':', 1)
169 host = server_info[0]
170 if len(server_info) > 1:
171 if not server_info[1].isdigit():
172 raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
173 port = int(server_info[1])
177 server_module = importlib.import_module(f'nominatim_api.server.{args.engine}.server')
179 app = server_module.get_application(args.project_dir)
181 config = uvicorn.Config(app, host=host, port=port)
182 server = uvicorn.Server(config)
186 def get_set_parser() -> CommandlineParser:
188 Initializes the parser and adds various subcommands for
191 parser = CommandlineParser('nominatim', nominatim.__doc__)
193 parser.add_subcommand('import', clicmd.SetupAll())
194 parser.add_subcommand('freeze', clicmd.SetupFreeze())
195 parser.add_subcommand('replication', clicmd.UpdateReplication())
197 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
199 parser.add_subcommand('add-data', clicmd.UpdateAddData())
200 parser.add_subcommand('index', clicmd.UpdateIndex())
201 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
203 parser.add_subcommand('admin', clicmd.AdminFuncs())
206 exportcmd = importlib.import_module('nominatim_db.clicmd.export')
207 apicmd = importlib.import_module('nominatim_db.clicmd.api')
208 convertcmd = importlib.import_module('nominatim_db.clicmd.convert')
210 parser.add_subcommand('export', exportcmd.QueryExport())
211 parser.add_subcommand('convert', convertcmd.ConvertDB())
212 parser.add_subcommand('serve', AdminServe())
214 parser.add_subcommand('search', apicmd.APISearch())
215 parser.add_subcommand('reverse', apicmd.APIReverse())
216 parser.add_subcommand('lookup', apicmd.APILookup())
217 parser.add_subcommand('details', apicmd.APIDetails())
218 parser.add_subcommand('status', apicmd.APIStatus())
219 except ModuleNotFoundError as ex:
220 if not ex.name or 'nominatim_api' not in ex.name:
223 parser.parser.epilog = \
224 f'\n\nNominatim API package not found (was looking for module: {ex.name}).'\
225 '\nThe following commands are not available:'\
226 '\n export, convert, serve, search, reverse, lookup, details, status'\
227 "\n\nRun 'pip install nominatim-api' to install the package."
232 def nominatim(cli_args: Optional[List[str]] = None,
233 environ: Optional[Mapping[str, str]] = None) -> int:
235 Command-line tools for importing, updating, administrating and
236 querying the Nominatim database.
238 'cli_args' is a list of parameters for the command to run. If not given,
239 sys.args will be used.
241 'environ' is the dictionary of environment variables containing the
242 Nominatim configuration. When None, the os.environ is inherited.
244 return get_set_parser().run(cli_args=cli_args, environ=environ)