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.
15 from pathlib import Path
17 from nominatim.config import Configuration
18 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
19 from nominatim.errors import UsageError
20 from nominatim import clicmd
21 from nominatim import version
22 from nominatim.clicmd.args import NominatimArgs
24 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, description):
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():
61 """ Program name and version number as string
63 text = 'Nominatim version %s.%s.%s.%s' % version.NOMINATIM_VERSION
64 if version.GIT_COMMIT_HASH is not None:
65 text += ' (%s)' % version.GIT_COMMIT_HASH
68 def add_subcommand(self, name, cmd):
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 parser = self.subs.add_parser(name, parents=[self.default_args],
74 help=cmd.__doc__.split('\n', 1)[0],
75 description=cmd.__doc__,
76 formatter_class=argparse.RawDescriptionHelpFormatter,
78 parser.set_defaults(command=cmd)
81 def run(self, **kwargs):
82 """ Parse the command line arguments of the program and execute the
83 appropriate subcommand.
85 args = NominatimArgs()
87 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
92 print(CommandlineParser.nominatim_version_text())
95 if args.subcommand is None:
96 self.parser.print_help()
99 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
100 'data_dir', 'config_dir', 'phpcgi_path'):
101 setattr(args, arg, Path(kwargs[arg]))
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, args.config_dir,
111 environ=kwargs.get('environ', os.environ))
112 args.config.set_libdirs(module=args.module_dir,
113 osm2pgsql=args.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.
149 def add_args(parser):
150 group = parser.add_argument_group('Output arguments')
151 group.add_argument('--output-type', default='street',
152 choices=('continent', 'country', 'state', 'county',
153 'city', 'suburb', 'street', 'path'),
154 help='Type of places to output (default: street)')
155 group.add_argument('--output-format',
156 default='street;suburb;city;county;state;country',
157 help=("Semicolon-separated list of address types "
158 "(see --output-type). Multiple ranks can be "
159 "merged into one column by simply using a "
160 "comma-separated list."))
161 group.add_argument('--output-all-postcodes', action='store_true',
162 help=("List all postcodes for address instead of "
163 "just the most likely one"))
164 group.add_argument('--language',
165 help=("Preferred language for output "
166 "(use local name, if omitted)"))
167 group = parser.add_argument_group('Filter arguments')
168 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
169 help='Export only objects within country')
170 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
171 help='Export only children of this OSM node')
172 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
173 help='Export only children of this OSM way')
174 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
175 help='Export only children of this OSM relation')
180 params = ['export.php',
181 '--output-type', args.output_type,
182 '--output-format', args.output_format]
183 if args.output_all_postcodes:
184 params.append('--output-all-postcodes')
186 params.extend(('--language', args.language))
187 if args.restrict_to_country:
188 params.extend(('--restrict-to-country', args.restrict_to_country))
189 if args.restrict_to_osm_node:
190 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
191 if args.restrict_to_osm_way:
192 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
193 if args.restrict_to_osm_relation:
194 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
196 return run_legacy_script(*params, nominatim_env=args)
201 Start a simple web server for serving the API.
203 This command starts the built-in PHP webserver to serve the website
204 from the current project directory. This webserver is only suitable
205 for testing and development. Do not use it in production setups!
207 By the default, the webserver can be accessed at: http://127.0.0.1:8088
211 def add_args(parser):
212 group = parser.add_argument_group('Server arguments')
213 group.add_argument('--server', default='127.0.0.1:8088',
214 help='The address the server will listen to.')
218 run_php_server(args.server, args.project_dir / 'website')
220 def get_set_parser(**kwargs):
222 Initializes the parser and adds various subcommands for
225 parser = CommandlineParser('nominatim', nominatim.__doc__)
227 parser.add_subcommand('import', clicmd.SetupAll)
228 parser.add_subcommand('freeze', clicmd.SetupFreeze)
229 parser.add_subcommand('replication', clicmd.UpdateReplication)
231 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
233 parser.add_subcommand('add-data', clicmd.UpdateAddData)
234 parser.add_subcommand('index', clicmd.UpdateIndex)
235 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
237 parser.add_subcommand('admin', clicmd.AdminFuncs)
239 parser.add_subcommand('export', QueryExport)
240 parser.add_subcommand('serve', AdminServe)
242 if kwargs.get('phpcgi_path'):
243 parser.add_subcommand('search', clicmd.APISearch)
244 parser.add_subcommand('reverse', clicmd.APIReverse)
245 parser.add_subcommand('lookup', clicmd.APILookup)
246 parser.add_subcommand('details', clicmd.APIDetails)
247 parser.add_subcommand('status', clicmd.APIStatus)
249 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
254 def nominatim(**kwargs):
256 Command-line tools for importing, updating, administrating and
257 querying the Nominatim database.
259 parser = get_set_parser(**kwargs)
261 return parser.run(**kwargs)