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.clicmd.args import NominatimArgs
23 LOG = logging.getLogger()
26 class CommandlineParser:
27 """ Wraps some of the common functions for parsing the command line
28 and setting up subcommands.
30 def __init__(self, prog, description):
31 self.parser = argparse.ArgumentParser(
33 description=description,
34 formatter_class=argparse.RawDescriptionHelpFormatter)
36 self.subs = self.parser.add_subparsers(title='available commands',
39 # Arguments added to every sub-command
40 self.default_args = argparse.ArgumentParser(add_help=False)
41 group = self.default_args.add_argument_group('Default arguments')
42 group.add_argument('-h', '--help', action='help',
43 help='Show this help message and exit')
44 group.add_argument('-q', '--quiet', action='store_const', const=0,
45 dest='verbose', default=1,
46 help='Print only error messages')
47 group.add_argument('-v', '--verbose', action='count', default=1,
48 help='Increase verboseness of output')
49 group.add_argument('--project-dir', metavar='DIR', default='.',
50 help='Base directory of the Nominatim installation (default:.)')
51 group.add_argument('-j', '--threads', metavar='NUM', type=int,
52 help='Number of parallel threads to use')
55 def add_subcommand(self, name, cmd):
56 """ Add a subcommand to the parser. The subcommand must be a class
57 with a function add_args() that adds the parameters for the
58 subcommand and a run() function that executes the command.
60 parser = self.subs.add_parser(name, parents=[self.default_args],
61 help=cmd.__doc__.split('\n', 1)[0],
62 description=cmd.__doc__,
63 formatter_class=argparse.RawDescriptionHelpFormatter,
65 parser.set_defaults(command=cmd)
68 def run(self, **kwargs):
69 """ Parse the command line arguments of the program and execute the
70 appropriate subcommand.
72 args = NominatimArgs()
73 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
75 if args.subcommand is None:
76 self.parser.print_help()
79 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
80 'data_dir', 'config_dir', 'phpcgi_path'):
81 setattr(args, arg, Path(kwargs[arg]))
82 args.project_dir = Path(args.project_dir).resolve()
84 if 'cli_args' not in kwargs:
85 logging.basicConfig(stream=sys.stderr,
86 format='%(asctime)s: %(message)s',
87 datefmt='%Y-%m-%d %H:%M:%S',
88 level=max(4 - args.verbose, 1) * 10)
90 args.config = Configuration(args.project_dir, args.config_dir,
91 environ=kwargs.get('environ', os.environ))
92 args.config.set_libdirs(module=args.module_dir,
93 osm2pgsql=args.osm2pgsql_path,
98 log = logging.getLogger()
99 log.warning('Using project directory: %s', str(args.project_dir))
102 return args.command.run(args)
103 except UsageError as exception:
104 if log.isEnabledFor(logging.DEBUG):
105 raise # use Python's exception printing
106 log.fatal('FATAL: %s', exception)
108 # If we get here, then execution has failed in some way.
114 # Each class needs to implement two functions: add_args() adds the CLI parameters
115 # for the subfunction, run() executes the subcommand.
117 # The class documentation doubles as the help text for the command. The
118 # first line is also used in the summary when calling the program without
121 # No need to document the functions each time.
122 # pylint: disable=C0111
125 Export addresses as CSV file from the database.
129 def add_args(parser):
130 group = parser.add_argument_group('Output arguments')
131 group.add_argument('--output-type', default='street',
132 choices=('continent', 'country', 'state', 'county',
133 'city', 'suburb', 'street', 'path'),
134 help='Type of places to output (default: street)')
135 group.add_argument('--output-format',
136 default='street;suburb;city;county;state;country',
137 help=("Semicolon-separated list of address types "
138 "(see --output-type). Multiple ranks can be "
139 "merged into one column by simply using a "
140 "comma-separated list."))
141 group.add_argument('--output-all-postcodes', action='store_true',
142 help=("List all postcodes for address instead of "
143 "just the most likely one"))
144 group.add_argument('--language',
145 help=("Preferred language for output "
146 "(use local name, if omitted)"))
147 group = parser.add_argument_group('Filter arguments')
148 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
149 help='Export only objects within country')
150 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
151 help='Export only children of this OSM node')
152 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
153 help='Export only children of this OSM way')
154 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
155 help='Export only children of this OSM relation')
160 params = ['export.php',
161 '--output-type', args.output_type,
162 '--output-format', args.output_format]
163 if args.output_all_postcodes:
164 params.append('--output-all-postcodes')
166 params.extend(('--language', args.language))
167 if args.restrict_to_country:
168 params.extend(('--restrict-to-country', args.restrict_to_country))
169 if args.restrict_to_osm_node:
170 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
171 if args.restrict_to_osm_way:
172 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
173 if args.restrict_to_osm_relation:
174 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
176 return run_legacy_script(*params, nominatim_env=args)
181 Start a simple web server for serving the API.
183 This command starts the built-in PHP webserver to serve the website
184 from the current project directory. This webserver is only suitable
185 for testing and development. Do not use it in production setups!
187 By the default, the webserver can be accessed at: http://127.0.0.1:8088
191 def add_args(parser):
192 group = parser.add_argument_group('Server arguments')
193 group.add_argument('--server', default='127.0.0.1:8088',
194 help='The address the server will listen to.')
198 run_php_server(args.server, args.project_dir / 'website')
200 def get_set_parser(**kwargs):
202 Initializes the parser and adds various subcommands for
205 parser = CommandlineParser('nominatim', nominatim.__doc__)
207 parser.add_subcommand('import', clicmd.SetupAll)
208 parser.add_subcommand('freeze', clicmd.SetupFreeze)
209 parser.add_subcommand('replication', clicmd.UpdateReplication)
211 parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
213 parser.add_subcommand('add-data', clicmd.UpdateAddData)
214 parser.add_subcommand('index', clicmd.UpdateIndex)
215 parser.add_subcommand('refresh', clicmd.UpdateRefresh())
217 parser.add_subcommand('admin', clicmd.AdminFuncs)
219 parser.add_subcommand('export', QueryExport)
220 parser.add_subcommand('serve', AdminServe)
222 if kwargs.get('phpcgi_path'):
223 parser.add_subcommand('search', clicmd.APISearch)
224 parser.add_subcommand('reverse', clicmd.APIReverse)
225 parser.add_subcommand('lookup', clicmd.APILookup)
226 parser.add_subcommand('details', clicmd.APIDetails)
227 parser.add_subcommand('status', clicmd.APIStatus)
229 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
234 def nominatim(**kwargs):
236 Command-line tools for importing, updating, administrating and
237 querying the Nominatim database.
239 parser = get_set_parser(**kwargs)
241 return parser.run(**kwargs)