1 # SPDX-License-Identifier: GPL-2.0-only
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2022 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, List, Union
17 from pathlib import Path
19 from nominatim.config import Configuration
20 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
21 from nominatim.errors import UsageError
22 from nominatim import clicmd
23 from nominatim import version
24 from nominatim.clicmd.args import NominatimArgs, Subcommand
26 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')
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})'
70 def add_subcommand(self, name: str, cmd: Subcommand) -> None:
71 """ Add a subcommand to the parser. The subcommand must be a class
72 with a function add_args() that adds the parameters for the
73 subcommand and a run() function that executes the command.
75 assert cmd.__doc__ is not None
77 parser = self.subs.add_parser(name, parents=[self.default_args],
78 help=cmd.__doc__.split('\n', 1)[0],
79 description=cmd.__doc__,
80 formatter_class=argparse.RawDescriptionHelpFormatter,
82 parser.set_defaults(command=cmd)
86 def run(self, **kwargs: Any) -> int:
87 """ Parse the command line arguments of the program and execute the
88 appropriate subcommand.
90 args = NominatimArgs()
92 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
97 print(self.nominatim_version_text())
100 if args.subcommand is None:
101 self.parser.print_help()
104 args.phpcgi_path = Path(kwargs['phpcgi_path'])
105 args.project_dir = Path(args.project_dir).resolve()
107 if 'cli_args' not in kwargs:
108 logging.basicConfig(stream=sys.stderr,
109 format='%(asctime)s: %(message)s',
110 datefmt='%Y-%m-%d %H:%M:%S',
111 level=max(4 - args.verbose, 1) * 10)
113 args.config = Configuration(args.project_dir,
114 environ=kwargs.get('environ', os.environ))
115 args.config.set_libdirs(module=kwargs['module_dir'],
116 osm2pgsql=kwargs['osm2pgsql_path'])
118 log = logging.getLogger()
119 log.warning('Using project directory: %s', str(args.project_dir))
122 return args.command.run(args)
123 except UsageError as exception:
124 if log.isEnabledFor(logging.DEBUG):
125 raise # use Python's exception printing
126 log.fatal('FATAL: %s', exception)
128 # If we get here, then execution has failed in some way.
134 # Each class needs to implement two functions: add_args() adds the CLI parameters
135 # for the subfunction, run() executes the subcommand.
137 # The class documentation doubles as the help text for the command. The
138 # first line is also used in the summary when calling the program without
141 # No need to document the functions each time.
142 # pylint: disable=C0111
145 Export addresses as CSV file from the database.
148 def add_args(self, parser: argparse.ArgumentParser) -> None:
149 group = parser.add_argument_group('Output arguments')
150 group.add_argument('--output-type', default='street',
151 choices=('continent', 'country', 'state', 'county',
152 'city', 'suburb', 'street', 'path'),
153 help='Type of places to output (default: street)')
154 group.add_argument('--output-format',
155 default='street;suburb;city;county;state;country',
156 help=("Semicolon-separated list of address types "
157 "(see --output-type). Multiple ranks can be "
158 "merged into one column by simply using a "
159 "comma-separated list."))
160 group.add_argument('--output-all-postcodes', action='store_true',
161 help=("List all postcodes for address instead of "
162 "just the most likely one"))
163 group.add_argument('--language',
164 help=("Preferred language for output "
165 "(use local name, if omitted)"))
166 group = parser.add_argument_group('Filter arguments')
167 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
168 help='Export only objects within country')
169 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
170 help='Export only children of this OSM node')
171 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
172 help='Export only children of this OSM way')
173 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
174 help='Export only children of this OSM relation')
177 def run(self, args: NominatimArgs) -> int:
178 params: List[Union[int, str]] = [
179 '--output-type', args.output_type,
180 '--output-format', args.output_format]
181 if args.output_all_postcodes:
182 params.append('--output-all-postcodes')
184 params.extend(('--language', args.language))
185 if args.restrict_to_country:
186 params.extend(('--restrict-to-country', args.restrict_to_country))
187 if args.restrict_to_osm_node:
188 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
189 if args.restrict_to_osm_way:
190 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
191 if args.restrict_to_osm_relation:
192 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
194 return run_legacy_script('export.php', *params, config=args.config)
199 Start a simple web server for serving the API.
201 This command starts a built-in webserver to serve the website
202 from the current project directory. This webserver is only suitable
203 for testing and development. Do not use it in production setups!
205 There are different webservers available. The default 'php' engine
206 runs the classic PHP frontend. The other engines are Python servers
207 which run the new Python frontend code. This is highly experimental
208 at the moment and may not include the full API.
210 By the default, the webserver can be accessed at: http://127.0.0.1:8088
213 def add_args(self, parser: argparse.ArgumentParser) -> None:
214 group = parser.add_argument_group('Server arguments')
215 group.add_argument('--server', default='127.0.0.1:8088',
216 help='The address the server will listen to.')
217 group.add_argument('--engine', default='php',
218 choices=('php', 'sanic', 'falcon', 'starlette'),
219 help='Webserver framework to run. (default: php)')
222 def run(self, args: NominatimArgs) -> int:
223 if args.engine == 'php':
224 run_php_server(args.server, args.project_dir / 'website')
226 server_info = args.server.split(':', 1)
227 host = server_info[0]
228 if len(server_info) > 1:
229 if not server_info[1].isdigit():
230 raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
231 port = int(server_info[1])
235 if args.engine == 'sanic':
236 server_module = importlib.import_module('nominatim.server.sanic.server')
238 app = server_module.get_application(args.project_dir)
239 app.run(host=host, port=port, debug=True)
241 import uvicorn # pylint: disable=import-outside-toplevel
243 if args.engine == 'falcon':
244 server_module = importlib.import_module('nominatim.server.falcon.server')
245 elif args.engine == 'starlette':
246 server_module = importlib.import_module('nominatim.server.starlette.server')
248 app = server_module.get_application(args.project_dir)
249 uvicorn.run(app, host=host, port=port)
254 def get_set_parser(**kwargs: Any) -> CommandlineParser:
256 Initializes the parser and adds various subcommands for
259 parser = CommandlineParser('nominatim', nominatim.__doc__)
261 parser.add_subcommand('import', clicmd.SetupAll())
262 parser.add_subcommand('freeze', clicmd.SetupFreeze())
263 parser.add_subcommand('replication', clicmd.UpdateReplication())
265 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
267 parser.add_subcommand('add-data', clicmd.UpdateAddData())
268 parser.add_subcommand('index', clicmd.UpdateIndex())
269 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
271 parser.add_subcommand('admin', clicmd.AdminFuncs())
273 parser.add_subcommand('export', QueryExport())
274 parser.add_subcommand('serve', AdminServe())
276 if kwargs.get('phpcgi_path'):
277 parser.add_subcommand('search', clicmd.APISearch())
278 parser.add_subcommand('reverse', clicmd.APIReverse())
279 parser.add_subcommand('lookup', clicmd.APILookup())
280 parser.add_subcommand('details', clicmd.APIDetails())
281 parser.add_subcommand('status', clicmd.APIStatus())
283 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
288 def nominatim(**kwargs: Any) -> int:
290 Command-line tools for importing, updating, administrating and
291 querying the Nominatim database.
293 parser = get_set_parser(**kwargs)
295 return parser.run(**kwargs)