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 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
104 'data_dir', 'config_dir', 'phpcgi_path'):
105 setattr(args, arg, Path(kwargs[arg]))
106 args.project_dir = Path(args.project_dir).resolve()
108 if 'cli_args' not in kwargs:
109 logging.basicConfig(stream=sys.stderr,
110 format='%(asctime)s: %(message)s',
111 datefmt='%Y-%m-%d %H:%M:%S',
112 level=max(4 - args.verbose, 1) * 10)
114 args.config = Configuration(args.project_dir, args.config_dir,
115 environ=kwargs.get('environ', os.environ))
116 args.config.set_libdirs(module=args.module_dir,
117 osm2pgsql=args.osm2pgsql_path,
122 log = logging.getLogger()
123 log.warning('Using project directory: %s', str(args.project_dir))
126 return args.command.run(args)
127 except UsageError as exception:
128 if log.isEnabledFor(logging.DEBUG):
129 raise # use Python's exception printing
130 log.fatal('FATAL: %s', exception)
132 # If we get here, then execution has failed in some way.
138 # Each class needs to implement two functions: add_args() adds the CLI parameters
139 # for the subfunction, run() executes the subcommand.
141 # The class documentation doubles as the help text for the command. The
142 # first line is also used in the summary when calling the program without
145 # No need to document the functions each time.
146 # pylint: disable=C0111
149 Export addresses as CSV file from the database.
152 def add_args(self, parser: argparse.ArgumentParser) -> None:
153 group = parser.add_argument_group('Output arguments')
154 group.add_argument('--output-type', default='street',
155 choices=('continent', 'country', 'state', 'county',
156 'city', 'suburb', 'street', 'path'),
157 help='Type of places to output (default: street)')
158 group.add_argument('--output-format',
159 default='street;suburb;city;county;state;country',
160 help=("Semicolon-separated list of address types "
161 "(see --output-type). Multiple ranks can be "
162 "merged into one column by simply using a "
163 "comma-separated list."))
164 group.add_argument('--output-all-postcodes', action='store_true',
165 help=("List all postcodes for address instead of "
166 "just the most likely one"))
167 group.add_argument('--language',
168 help=("Preferred language for output "
169 "(use local name, if omitted)"))
170 group = parser.add_argument_group('Filter arguments')
171 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
172 help='Export only objects within country')
173 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
174 help='Export only children of this OSM node')
175 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
176 help='Export only children of this OSM way')
177 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
178 help='Export only children of this OSM relation')
181 def run(self, args: NominatimArgs) -> int:
182 params: List[Union[int, str]] = [
183 '--output-type', args.output_type,
184 '--output-format', args.output_format]
185 if args.output_all_postcodes:
186 params.append('--output-all-postcodes')
188 params.extend(('--language', args.language))
189 if args.restrict_to_country:
190 params.extend(('--restrict-to-country', args.restrict_to_country))
191 if args.restrict_to_osm_node:
192 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
193 if args.restrict_to_osm_way:
194 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
195 if args.restrict_to_osm_relation:
196 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
198 return run_legacy_script('export.php', *params, nominatim_env=args)
203 Start a simple web server for serving the API.
205 This command starts the built-in PHP webserver to serve the website
206 from the current project directory. This webserver is only suitable
207 for testing and development. Do not use it in production setups!
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.')
218 def run(self, args: NominatimArgs) -> int:
219 run_php_server(args.server, args.project_dir / 'website')
223 def get_set_parser(**kwargs: Any) -> CommandlineParser:
225 Initializes the parser and adds various subcommands for
228 parser = CommandlineParser('nominatim', nominatim.__doc__)
230 parser.add_subcommand('import', clicmd.SetupAll())
231 parser.add_subcommand('freeze', clicmd.SetupFreeze())
232 parser.add_subcommand('replication', clicmd.UpdateReplication())
234 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases())
236 parser.add_subcommand('add-data', clicmd.UpdateAddData())
237 parser.add_subcommand('index', clicmd.UpdateIndex())
238 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
240 parser.add_subcommand('admin', clicmd.AdminFuncs())
242 parser.add_subcommand('export', QueryExport())
243 parser.add_subcommand('serve', AdminServe())
245 if kwargs.get('phpcgi_path'):
246 parser.add_subcommand('search', clicmd.APISearch())
247 parser.add_subcommand('reverse', clicmd.APIReverse())
248 parser.add_subcommand('lookup', clicmd.APILookup())
249 parser.add_subcommand('details', clicmd.APIDetails())
250 parser.add_subcommand('status', clicmd.APIStatus())
252 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
257 def nominatim(**kwargs: Any) -> int:
259 Command-line tools for importing, updating, administrating and
260 querying the Nominatim database.
262 parser = get_set_parser(**kwargs)
264 return parser.run(**kwargs)