2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
9 from pathlib import Path
11 from .config import Configuration
12 from .tools.exec_utils import run_legacy_script, run_php_server
13 from .errors import UsageError
15 from .clicmd.args import NominatimArgs
16 from .tools import tiger_data
18 LOG = logging.getLogger()
21 class CommandlineParser:
22 """ Wraps some of the common functions for parsing the command line
23 and setting up subcommands.
25 def __init__(self, prog, description):
26 self.parser = argparse.ArgumentParser(
28 description=description,
29 formatter_class=argparse.RawDescriptionHelpFormatter)
31 self.subs = self.parser.add_subparsers(title='available commands',
34 # Arguments added to every sub-command
35 self.default_args = argparse.ArgumentParser(add_help=False)
36 group = self.default_args.add_argument_group('Default arguments')
37 group.add_argument('-h', '--help', action='help',
38 help='Show this help message and exit')
39 group.add_argument('-q', '--quiet', action='store_const', const=0,
40 dest='verbose', default=1,
41 help='Print only error messages')
42 group.add_argument('-v', '--verbose', action='count', default=1,
43 help='Increase verboseness of output')
44 group.add_argument('--project-dir', metavar='DIR', default='.',
45 help='Base directory of the Nominatim installation (default:.)')
46 group.add_argument('-j', '--threads', metavar='NUM', type=int,
47 help='Number of parallel threads to use')
50 def add_subcommand(self, name, cmd):
51 """ Add a subcommand to the parser. The subcommand must be a class
52 with a function add_args() that adds the parameters for the
53 subcommand and a run() function that executes the command.
55 parser = self.subs.add_parser(name, parents=[self.default_args],
56 help=cmd.__doc__.split('\n', 1)[0],
57 description=cmd.__doc__,
58 formatter_class=argparse.RawDescriptionHelpFormatter,
60 parser.set_defaults(command=cmd)
63 def run(self, **kwargs):
64 """ Parse the command line arguments of the program and execute the
65 appropriate subcommand.
67 args = NominatimArgs()
68 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
70 if args.subcommand is None:
71 self.parser.print_help()
74 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
75 'data_dir', 'config_dir', 'phpcgi_path'):
76 setattr(args, arg, Path(kwargs[arg]))
77 args.project_dir = Path(args.project_dir).resolve()
79 if 'cli_args' not in kwargs:
80 logging.basicConfig(stream=sys.stderr,
81 format='%(asctime)s: %(message)s',
82 datefmt='%Y-%m-%d %H:%M:%S',
83 level=max(4 - args.verbose, 1) * 10)
85 args.config = Configuration(args.project_dir, args.config_dir,
86 environ=kwargs.get('environ', os.environ))
88 log = logging.getLogger()
89 log.warning('Using project directory: %s', str(args.project_dir))
92 return args.command.run(args)
93 except UsageError as exception:
94 if log.isEnabledFor(logging.DEBUG):
95 raise # use Python's exception printing
96 log.fatal('FATAL: %s', exception)
98 # If we get here, then execution has failed in some way.
102 ##### Subcommand classes
104 # Each class needs to implement two functions: add_args() adds the CLI parameters
105 # for the subfunction, run() executes the subcommand.
107 # The class documentation doubles as the help text for the command. The
108 # first line is also used in the summary when calling the program without
111 # No need to document the functions each time.
112 # pylint: disable=C0111
113 # Using non-top-level imports to make pyosmium optional for replication only.
114 # pylint: disable=E0012,C0415
117 class SetupSpecialPhrases:
119 Maintain special phrases.
123 def add_args(parser):
124 group = parser.add_argument_group('Input arguments')
125 group.add_argument('--from-wiki', action='store_true',
126 help='Pull special phrases from the OSM wiki.')
127 group = parser.add_argument_group('Output arguments')
128 group.add_argument('-o', '--output', default='-',
129 help="""File to write the preprocessed phrases to.
130 If omitted, it will be written to stdout.""")
134 if args.output != '-':
135 raise NotImplementedError('Only output to stdout is currently implemented.')
136 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
141 Add additional data from a file or an online source.
143 Data is only imported, not indexed. You need to call `nominatim-update index`
144 to complete the process.
148 def add_args(parser):
149 group_name = parser.add_argument_group('Source')
150 group = group_name.add_mutually_exclusive_group(required=True)
151 group.add_argument('--file', metavar='FILE',
152 help='Import data from an OSM file')
153 group.add_argument('--diff', metavar='FILE',
154 help='Import data from an OSM diff file')
155 group.add_argument('--node', metavar='ID', type=int,
156 help='Import a single node from the API')
157 group.add_argument('--way', metavar='ID', type=int,
158 help='Import a single way from the API')
159 group.add_argument('--relation', metavar='ID', type=int,
160 help='Import a single relation from the API')
161 group.add_argument('--tiger-data', metavar='DIR',
162 help='Add housenumbers from the US TIGER census database.')
163 group = parser.add_argument_group('Extra arguments')
164 group.add_argument('--use-main-api', action='store_true',
165 help='Use OSM API instead of Overpass to download objects')
170 return tiger_data.add_tiger_data(args.config.get_libpq_dsn(),
176 params = ['update.php']
178 params.extend(('--import-file', args.file))
180 params.extend(('--import-diff', args.diff))
182 params.extend(('--import-node', args.node))
184 params.extend(('--import-way', args.way))
186 params.extend(('--import-relation', args.relation))
187 if args.use_main_api:
188 params.append('--use-main-api')
189 return run_legacy_script(*params, nominatim_env=args)
194 Export addresses as CSV file from the database.
198 def add_args(parser):
199 group = parser.add_argument_group('Output arguments')
200 group.add_argument('--output-type', default='street',
201 choices=('continent', 'country', 'state', 'county',
202 'city', 'suburb', 'street', 'path'),
203 help='Type of places to output (default: street)')
204 group.add_argument('--output-format',
205 default='street;suburb;city;county;state;country',
206 help="""Semicolon-separated list of address types
207 (see --output-type). Multiple ranks can be
208 merged into one column by simply using a
209 comma-separated list.""")
210 group.add_argument('--output-all-postcodes', action='store_true',
211 help="""List all postcodes for address instead of
212 just the most likely one""")
213 group.add_argument('--language',
214 help="""Preferred language for output
215 (use local name, if omitted)""")
216 group = parser.add_argument_group('Filter arguments')
217 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
218 help='Export only objects within country')
219 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
220 help='Export only children of this OSM node')
221 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
222 help='Export only children of this OSM way')
223 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
224 help='Export only children of this OSM relation')
229 params = ['export.php',
230 '--output-type', args.output_type,
231 '--output-format', args.output_format]
232 if args.output_all_postcodes:
233 params.append('--output-all-postcodes')
235 params.extend(('--language', args.language))
236 if args.restrict_to_country:
237 params.extend(('--restrict-to-country', args.restrict_to_country))
238 if args.restrict_to_osm_node:
239 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
240 if args.restrict_to_osm_way:
241 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
242 if args.restrict_to_osm_relation:
243 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
245 return run_legacy_script(*params, nominatim_env=args)
250 Start a simple web server for serving the API.
252 This command starts the built-in PHP webserver to serve the website
253 from the current project directory. This webserver is only suitable
254 for testing and develop. Do not use it in production setups!
256 By the default, the webserver can be accessed at: http://127.0.0.1:8088
260 def add_args(parser):
261 group = parser.add_argument_group('Server arguments')
262 group.add_argument('--server', default='127.0.0.1:8088',
263 help='The address the server will listen to.')
267 run_php_server(args.server, args.project_dir / 'website')
270 def nominatim(**kwargs):
272 Command-line tools for importing, updating, administrating and
273 querying the Nominatim database.
275 parser = CommandlineParser('nominatim', nominatim.__doc__)
277 parser.add_subcommand('import', clicmd.SetupAll)
278 parser.add_subcommand('freeze', clicmd.SetupFreeze)
279 parser.add_subcommand('replication', clicmd.UpdateReplication)
281 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
283 parser.add_subcommand('add-data', UpdateAddData)
284 parser.add_subcommand('index', clicmd.UpdateIndex)
285 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
287 parser.add_subcommand('admin', clicmd.AdminFuncs)
289 parser.add_subcommand('export', QueryExport)
290 parser.add_subcommand('serve', AdminServe)
292 if kwargs.get('phpcgi_path'):
293 parser.add_subcommand('search', clicmd.APISearch)
294 parser.add_subcommand('reverse', clicmd.APIReverse)
295 parser.add_subcommand('lookup', clicmd.APILookup)
296 parser.add_subcommand('details', clicmd.APIDetails)
297 parser.add_subcommand('status', clicmd.APIStatus)
299 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
301 parser.add_subcommand('transition', clicmd.AdminTransition)
303 return parser.run(**kwargs)