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
16 from pathlib import Path
18 from nominatim.config import Configuration
19 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
20 from nominatim.errors import UsageError
21 from nominatim import clicmd
22 from nominatim import version
23 from nominatim.clicmd.args import NominatimArgs, Subcommand
25 LOG = logging.getLogger()
27 class CommandlineParser:
28 """ Wraps some of the common functions for parsing the command line
29 and setting up subcommands.
31 def __init__(self, prog: str, description: Optional[str]):
32 self.parser = argparse.ArgumentParser(
34 description=description,
35 formatter_class=argparse.RawDescriptionHelpFormatter)
37 self.subs = self.parser.add_subparsers(title='available commands',
40 # Global arguments that only work if no sub-command given
41 self.parser.add_argument('--version', action='store_true',
42 help='Print Nominatim version and exit')
44 # Arguments added to every sub-command
45 self.default_args = argparse.ArgumentParser(add_help=False)
46 group = self.default_args.add_argument_group('Default arguments')
47 group.add_argument('-h', '--help', action='help',
48 help='Show this help message and exit')
49 group.add_argument('-q', '--quiet', action='store_const', const=0,
50 dest='verbose', default=1,
51 help='Print only error messages')
52 group.add_argument('-v', '--verbose', action='count', default=1,
53 help='Increase verboseness of output')
54 group.add_argument('--project-dir', metavar='DIR', default='.',
55 help='Base directory of the Nominatim installation (default:.)')
56 group.add_argument('-j', '--threads', metavar='NUM', type=int,
57 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.version_str()}'
64 if version.GIT_COMMIT_HASH is not None:
65 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)
85 def run(self, **kwargs: Any) -> int:
86 """ Parse the command line arguments of the program and execute the
87 appropriate subcommand.
89 args = NominatimArgs()
91 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
96 print(self.nominatim_version_text())
99 if args.subcommand is None:
100 self.parser.print_help()
103 args.phpcgi_path = Path(kwargs['phpcgi_path'])
104 args.project_dir = Path(args.project_dir).resolve()
106 if 'cli_args' not in kwargs:
107 logging.basicConfig(stream=sys.stderr,
108 format='%(asctime)s: %(message)s',
109 datefmt='%Y-%m-%d %H:%M:%S',
110 level=max(4 - args.verbose, 1) * 10)
112 args.config = Configuration(args.project_dir,
113 environ=kwargs.get('environ', os.environ))
114 args.config.set_libdirs(module=kwargs['module_dir'],
115 osm2pgsql=kwargs['osm2pgsql_path'])
117 log = logging.getLogger()
118 log.warning('Using project directory: %s', str(args.project_dir))
121 return args.command.run(args)
122 except UsageError as exception:
123 if log.isEnabledFor(logging.DEBUG):
124 raise # use Python's exception printing
125 log.fatal('FATAL: %s', exception)
127 # If we get here, then execution has failed in some way.
133 # Each class needs to implement two functions: add_args() adds the CLI parameters
134 # for the subfunction, run() executes the subcommand.
136 # The class documentation doubles as the help text for the command. The
137 # first line is also used in the summary when calling the program without
140 # No need to document the functions each time.
141 # pylint: disable=C0111
144 Export addresses as CSV file from the database.
147 def add_args(self, parser: argparse.ArgumentParser) -> None:
148 group = parser.add_argument_group('Output arguments')
149 group.add_argument('--output-type', default='street',
150 choices=('continent', 'country', 'state', 'county',
151 'city', 'suburb', 'street', 'path'),
152 help='Type of places to output (default: street)')
153 group.add_argument('--output-format',
154 default='street;suburb;city;county;state;country',
155 help=("Semicolon-separated list of address types "
156 "(see --output-type). Multiple ranks can be "
157 "merged into one column by simply using a "
158 "comma-separated list."))
159 group.add_argument('--output-all-postcodes', action='store_true',
160 help=("List all postcodes for address instead of "
161 "just the most likely one"))
162 group.add_argument('--language',
163 help=("Preferred language for output "
164 "(use local name, if omitted)"))
165 group = parser.add_argument_group('Filter arguments')
166 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
167 help='Export only objects within country')
168 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
169 help='Export only children of this OSM node')
170 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
171 help='Export only children of this OSM way')
172 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
173 help='Export only children of this OSM relation')
176 def run(self, args: NominatimArgs) -> int:
177 params: List[Union[int, str]] = [
178 '--output-type', args.output_type,
179 '--output-format', args.output_format]
180 if args.output_all_postcodes:
181 params.append('--output-all-postcodes')
183 params.extend(('--language', args.language))
184 if args.restrict_to_country:
185 params.extend(('--restrict-to-country', args.restrict_to_country))
186 if args.restrict_to_osm_node:
187 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
188 if args.restrict_to_osm_way:
189 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
190 if args.restrict_to_osm_relation:
191 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
193 return run_legacy_script('export.php', *params, config=args.config)
198 Start a simple web server for serving the API.
200 This command starts a built-in webserver to serve the website
201 from the current project directory. This webserver is only suitable
202 for testing and development. Do not use it in production setups!
204 There are different webservers available. The default 'php' engine
205 runs the classic PHP frontend. 'sanic' and 'falcon' are Python servers
206 which run the new Python frontend code. This is highly experimental
207 at the moment and may not include the full API.
209 By the default, the webserver can be accessed at: http://127.0.0.1:8088
212 def add_args(self, parser: argparse.ArgumentParser) -> None:
213 group = parser.add_argument_group('Server arguments')
214 group.add_argument('--server', default='127.0.0.1:8088',
215 help='The address the server will listen to.')
216 group.add_argument('--engine', default='php',
217 choices=('php', 'sanic', 'falcon'),
218 help='Webserver framework to run. (default: php)')
221 def run(self, args: NominatimArgs) -> int:
222 if args.engine == 'php':
223 run_php_server(args.server, args.project_dir / 'website')
224 elif args.engine == 'sanic':
225 import nominatim.server.sanic.server
227 app = nominatim.server.sanic.server.get_application(args.project_dir)
229 server_info = args.server.split(':', 1)
230 host = server_info[0]
231 if len(server_info) > 1:
232 if not server_info[1].isdigit():
233 raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
234 port = int(server_info[1])
237 app.run(host=host, port=port, debug=True)
238 elif args.engine == 'falcon':
239 raise NotImplementedError('Support for falcon not yet available.')
244 def get_set_parser(**kwargs: Any) -> CommandlineParser:
246 Initializes the parser and adds various subcommands for
249 parser = CommandlineParser('nominatim', nominatim.__doc__)
251 parser.add_subcommand('import', clicmd.SetupAll())
252 parser.add_subcommand('freeze', clicmd.SetupFreeze())
253 parser.add_subcommand('replication', clicmd.UpdateReplication())
255 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
257 parser.add_subcommand('add-data', clicmd.UpdateAddData())
258 parser.add_subcommand('index', clicmd.UpdateIndex())
259 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
261 parser.add_subcommand('admin', clicmd.AdminFuncs())
263 parser.add_subcommand('export', QueryExport())
264 parser.add_subcommand('serve', AdminServe())
266 if kwargs.get('phpcgi_path'):
267 parser.add_subcommand('search', clicmd.APISearch())
268 parser.add_subcommand('reverse', clicmd.APIReverse())
269 parser.add_subcommand('lookup', clicmd.APILookup())
270 parser.add_subcommand('details', clicmd.APIDetails())
271 parser.add_subcommand('status', clicmd.APIStatus())
273 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
278 def nominatim(**kwargs: Any) -> int:
280 Command-line tools for importing, updating, administrating and
281 querying the Nominatim database.
283 parser = get_set_parser(**kwargs)
285 return parser.run(**kwargs)