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
17 LOG = logging.getLogger()
20 class CommandlineParser:
21 """ Wraps some of the common functions for parsing the command line
22 and setting up subcommands.
24 def __init__(self, prog, description):
25 self.parser = argparse.ArgumentParser(
27 description=description,
28 formatter_class=argparse.RawDescriptionHelpFormatter)
30 self.subs = self.parser.add_subparsers(title='available commands',
33 # Arguments added to every sub-command
34 self.default_args = argparse.ArgumentParser(add_help=False)
35 group = self.default_args.add_argument_group('Default arguments')
36 group.add_argument('-h', '--help', action='help',
37 help='Show this help message and exit')
38 group.add_argument('-q', '--quiet', action='store_const', const=0,
39 dest='verbose', default=1,
40 help='Print only error messages')
41 group.add_argument('-v', '--verbose', action='count', default=1,
42 help='Increase verboseness of output')
43 group.add_argument('--project-dir', metavar='DIR', default='.',
44 help='Base directory of the Nominatim installation (default:.)')
45 group.add_argument('-j', '--threads', metavar='NUM', type=int,
46 help='Number of parallel threads to use')
49 def add_subcommand(self, name, cmd):
50 """ Add a subcommand to the parser. The subcommand must be a class
51 with a function add_args() that adds the parameters for the
52 subcommand and a run() function that executes the command.
54 parser = self.subs.add_parser(name, parents=[self.default_args],
55 help=cmd.__doc__.split('\n', 1)[0],
56 description=cmd.__doc__,
57 formatter_class=argparse.RawDescriptionHelpFormatter,
59 parser.set_defaults(command=cmd)
62 def run(self, **kwargs):
63 """ Parse the command line arguments of the program and execute the
64 appropriate subcommand.
66 args = NominatimArgs()
67 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
69 if args.subcommand is None:
70 self.parser.print_help()
73 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
74 'data_dir', 'config_dir', 'phpcgi_path'):
75 setattr(args, arg, Path(kwargs[arg]))
76 args.project_dir = Path(args.project_dir).resolve()
78 logging.basicConfig(stream=sys.stderr,
79 format='%(asctime)s: %(message)s',
80 datefmt='%Y-%m-%d %H:%M:%S',
81 level=max(4 - args.verbose, 1) * 10)
83 args.config = Configuration(args.project_dir, args.config_dir)
85 log = logging.getLogger()
86 log.warning('Using project directory: %s', str(args.project_dir))
89 return args.command.run(args)
90 except UsageError as exception:
91 if log.isEnabledFor(logging.DEBUG):
92 raise # use Python's exception printing
93 log.fatal('FATAL: %s', exception)
95 # If we get here, then execution has failed in some way.
99 ##### Subcommand classes
101 # Each class needs to implement two functions: add_args() adds the CLI parameters
102 # for the subfunction, run() executes the subcommand.
104 # The class documentation doubles as the help text for the command. The
105 # first line is also used in the summary when calling the program without
108 # No need to document the functions each time.
109 # pylint: disable=C0111
110 # Using non-top-level imports to make pyosmium optional for replication only.
111 # pylint: disable=E0012,C0415
114 class SetupSpecialPhrases:
116 Maintain special phrases.
120 def add_args(parser):
121 group = parser.add_argument_group('Input arguments')
122 group.add_argument('--from-wiki', action='store_true',
123 help='Pull special phrases from the OSM wiki.')
124 group = parser.add_argument_group('Output arguments')
125 group.add_argument('-o', '--output', default='-',
126 help="""File to write the preprocessed phrases to.
127 If omitted, it will be written to stdout.""")
131 if args.output != '-':
132 raise NotImplementedError('Only output to stdout is currently implemented.')
133 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
138 Add additional data from a file or an online source.
140 Data is only imported, not indexed. You need to call `nominatim-update index`
141 to complete the process.
145 def add_args(parser):
146 group_name = parser.add_argument_group('Source')
147 group = group_name.add_mutually_exclusive_group(required=True)
148 group.add_argument('--file', metavar='FILE',
149 help='Import data from an OSM file')
150 group.add_argument('--diff', metavar='FILE',
151 help='Import data from an OSM diff file')
152 group.add_argument('--node', metavar='ID', type=int,
153 help='Import a single node from the API')
154 group.add_argument('--way', metavar='ID', type=int,
155 help='Import a single way from the API')
156 group.add_argument('--relation', metavar='ID', type=int,
157 help='Import a single relation from the API')
158 group.add_argument('--tiger-data', metavar='DIR',
159 help='Add housenumbers from the US TIGER census database.')
160 group = parser.add_argument_group('Extra arguments')
161 group.add_argument('--use-main-api', action='store_true',
162 help='Use OSM API instead of Overpass to download objects')
167 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
168 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
170 params = ['update.php']
172 params.extend(('--import-file', args.file))
174 params.extend(('--import-diff', args.diff))
176 params.extend(('--import-node', args.node))
178 params.extend(('--import-way', args.way))
180 params.extend(('--import-relation', args.relation))
181 if args.use_main_api:
182 params.append('--use-main-api')
183 return run_legacy_script(*params, nominatim_env=args)
188 Export addresses as CSV file from the database.
192 def add_args(parser):
193 group = parser.add_argument_group('Output arguments')
194 group.add_argument('--output-type', default='street',
195 choices=('continent', 'country', 'state', 'county',
196 'city', 'suburb', 'street', 'path'),
197 help='Type of places to output (default: street)')
198 group.add_argument('--output-format',
199 default='street;suburb;city;county;state;country',
200 help="""Semicolon-separated list of address types
201 (see --output-type). Multiple ranks can be
202 merged into one column by simply using a
203 comma-separated list.""")
204 group.add_argument('--output-all-postcodes', action='store_true',
205 help="""List all postcodes for address instead of
206 just the most likely one""")
207 group.add_argument('--language',
208 help="""Preferred language for output
209 (use local name, if omitted)""")
210 group = parser.add_argument_group('Filter arguments')
211 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
212 help='Export only objects within country')
213 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
214 help='Export only children of this OSM node')
215 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
216 help='Export only children of this OSM way')
217 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
218 help='Export only children of this OSM relation')
223 params = ['export.php',
224 '--output-type', args.output_type,
225 '--output-format', args.output_format]
226 if args.output_all_postcodes:
227 params.append('--output-all-postcodes')
229 params.extend(('--language', args.language))
230 if args.restrict_to_country:
231 params.extend(('--restrict-to-country', args.restrict_to_country))
232 if args.restrict_to_osm_node:
233 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
234 if args.restrict_to_osm_way:
235 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
236 if args.restrict_to_osm_relation:
237 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
239 return run_legacy_script(*params, nominatim_env=args)
244 Start a simple web server for serving the API.
246 This command starts the built-in PHP webserver to serve the website
247 from the current project directory. This webserver is only suitable
248 for testing and develop. Do not use it in production setups!
250 By the default, the webserver can be accessed at: http://127.0.0.1:8088
254 def add_args(parser):
255 group = parser.add_argument_group('Server arguments')
256 group.add_argument('--server', default='127.0.0.1:8088',
257 help='The address the server will listen to.')
261 run_php_server(args.server, args.project_dir / 'website')
264 def nominatim(**kwargs):
266 Command-line tools for importing, updating, administrating and
267 querying the Nominatim database.
269 parser = CommandlineParser('nominatim', nominatim.__doc__)
271 parser.add_subcommand('import', clicmd.SetupAll)
272 parser.add_subcommand('freeze', clicmd.SetupFreeze)
273 parser.add_subcommand('replication', clicmd.UpdateReplication)
275 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
277 parser.add_subcommand('add-data', UpdateAddData)
278 parser.add_subcommand('index', clicmd.UpdateIndex)
279 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
281 parser.add_subcommand('admin', clicmd.AdminFuncs)
283 parser.add_subcommand('export', QueryExport)
284 parser.add_subcommand('serve', AdminServe)
286 if kwargs.get('phpcgi_path'):
287 parser.add_subcommand('search', clicmd.APISearch)
288 parser.add_subcommand('reverse', clicmd.APIReverse)
289 parser.add_subcommand('lookup', clicmd.APILookup)
290 parser.add_subcommand('details', clicmd.APIDetails)
291 parser.add_subcommand('status', clicmd.APIStatus)
293 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
295 parser.add_subcommand('transition', clicmd.AdminTransition)
297 return parser.run(**kwargs)