]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/cli.py
add library directories to config
[nominatim.git] / nominatim / cli.py
1 """
2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
4 """
5 import logging
6 import os
7 import sys
8 import argparse
9 from pathlib import Path
10
11 from nominatim.config import Configuration
12 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
13 from nominatim.errors import UsageError
14 from nominatim import clicmd
15 from nominatim.clicmd.args import NominatimArgs
16 from nominatim.tools import tiger_data
17
18 LOG = logging.getLogger()
19
20
21 class CommandlineParser:
22     """ Wraps some of the common functions for parsing the command line
23         and setting up subcommands.
24     """
25     def __init__(self, prog, description):
26         self.parser = argparse.ArgumentParser(
27             prog=prog,
28             description=description,
29             formatter_class=argparse.RawDescriptionHelpFormatter)
30
31         self.subs = self.parser.add_subparsers(title='available commands',
32                                                dest='subcommand')
33
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')
48
49
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.
54         """
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,
59                                       add_help=False)
60         parser.set_defaults(command=cmd)
61         cmd.add_args(parser)
62
63     def run(self, **kwargs):
64         """ Parse the command line arguments of the program and execute the
65             appropriate subcommand.
66         """
67         args = NominatimArgs()
68         self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
69
70         if args.subcommand is None:
71             self.parser.print_help()
72             return 1
73
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()
78
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)
84
85         args.config = Configuration(args.project_dir, args.config_dir,
86                                     environ=kwargs.get('environ', os.environ))
87         args.config.set_libdirs(module=args.module_dir,
88                                 osm2pgsql=args.osm2pgsql_path,
89                                 php=args.phplib_dir,
90                                 sql=args.sqllib_dir,
91                                 data=args.data_dir)
92
93         log = logging.getLogger()
94         log.warning('Using project directory: %s', str(args.project_dir))
95
96         try:
97             return args.command.run(args)
98         except UsageError as exception:
99             if log.isEnabledFor(logging.DEBUG):
100                 raise # use Python's exception printing
101             log.fatal('FATAL: %s', exception)
102
103         # If we get here, then execution has failed in some way.
104         return 1
105
106
107 ##### Subcommand classes
108 #
109 # Each class needs to implement two functions: add_args() adds the CLI parameters
110 # for the subfunction, run() executes the subcommand.
111 #
112 # The class documentation doubles as the help text for the command. The
113 # first line is also used in the summary when calling the program without
114 # a subcommand.
115 #
116 # No need to document the functions each time.
117 # pylint: disable=C0111
118 # Using non-top-level imports to make pyosmium optional for replication only.
119 # pylint: disable=E0012,C0415
120 class UpdateAddData:
121     """\
122     Add additional data from a file or an online source.
123
124     Data is only imported, not indexed. You need to call `nominatim-update index`
125     to complete the process.
126     """
127
128     @staticmethod
129     def add_args(parser):
130         group_name = parser.add_argument_group('Source')
131         group = group_name.add_mutually_exclusive_group(required=True)
132         group.add_argument('--file', metavar='FILE',
133                            help='Import data from an OSM file')
134         group.add_argument('--diff', metavar='FILE',
135                            help='Import data from an OSM diff file')
136         group.add_argument('--node', metavar='ID', type=int,
137                            help='Import a single node from the API')
138         group.add_argument('--way', metavar='ID', type=int,
139                            help='Import a single way from the API')
140         group.add_argument('--relation', metavar='ID', type=int,
141                            help='Import a single relation from the API')
142         group.add_argument('--tiger-data', metavar='DIR',
143                            help='Add housenumbers from the US TIGER census database.')
144         group = parser.add_argument_group('Extra arguments')
145         group.add_argument('--use-main-api', action='store_true',
146                            help='Use OSM API instead of Overpass to download objects')
147
148     @staticmethod
149     def run(args):
150         if args.tiger_data:
151             return tiger_data.add_tiger_data(args.config.get_libpq_dsn(),
152                                              args.tiger_data,
153                                              args.threads or 1,
154                                              args.config,
155                                              args.sqllib_dir)
156
157         params = ['update.php']
158         if args.file:
159             params.extend(('--import-file', args.file))
160         elif args.diff:
161             params.extend(('--import-diff', args.diff))
162         elif args.node:
163             params.extend(('--import-node', args.node))
164         elif args.way:
165             params.extend(('--import-way', args.way))
166         elif args.relation:
167             params.extend(('--import-relation', args.relation))
168         if args.use_main_api:
169             params.append('--use-main-api')
170         return run_legacy_script(*params, nominatim_env=args)
171
172
173 class QueryExport:
174     """\
175     Export addresses as CSV file from the database.
176     """
177
178     @staticmethod
179     def add_args(parser):
180         group = parser.add_argument_group('Output arguments')
181         group.add_argument('--output-type', default='street',
182                            choices=('continent', 'country', 'state', 'county',
183                                     'city', 'suburb', 'street', 'path'),
184                            help='Type of places to output (default: street)')
185         group.add_argument('--output-format',
186                            default='street;suburb;city;county;state;country',
187                            help=("Semicolon-separated list of address types "
188                                  "(see --output-type). Multiple ranks can be "
189                                  "merged into one column by simply using a "
190                                  "comma-separated list."))
191         group.add_argument('--output-all-postcodes', action='store_true',
192                            help=("List all postcodes for address instead of "
193                                  "just the most likely one"))
194         group.add_argument('--language',
195                            help=("Preferred language for output "
196                                  "(use local name, if omitted)"))
197         group = parser.add_argument_group('Filter arguments')
198         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
199                            help='Export only objects within country')
200         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
201                            help='Export only children of this OSM node')
202         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
203                            help='Export only children of this OSM way')
204         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
205                            help='Export only children of this OSM relation')
206
207
208     @staticmethod
209     def run(args):
210         params = ['export.php',
211                   '--output-type', args.output_type,
212                   '--output-format', args.output_format]
213         if args.output_all_postcodes:
214             params.append('--output-all-postcodes')
215         if args.language:
216             params.extend(('--language', args.language))
217         if args.restrict_to_country:
218             params.extend(('--restrict-to-country', args.restrict_to_country))
219         if args.restrict_to_osm_node:
220             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
221         if args.restrict_to_osm_way:
222             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
223         if args.restrict_to_osm_relation:
224             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
225
226         return run_legacy_script(*params, nominatim_env=args)
227
228
229 class AdminServe:
230     """\
231     Start a simple web server for serving the API.
232
233     This command starts the built-in PHP webserver to serve the website
234     from the current project directory. This webserver is only suitable
235     for testing and develop. Do not use it in production setups!
236
237     By the default, the webserver can be accessed at: http://127.0.0.1:8088
238     """
239
240     @staticmethod
241     def add_args(parser):
242         group = parser.add_argument_group('Server arguments')
243         group.add_argument('--server', default='127.0.0.1:8088',
244                            help='The address the server will listen to.')
245
246     @staticmethod
247     def run(args):
248         run_php_server(args.server, args.project_dir / 'website')
249
250 def get_set_parser(**kwargs):
251     """\
252     Initializes the parser and adds various subcommands for
253     nominatim cli.
254     """
255     parser = CommandlineParser('nominatim', nominatim.__doc__)
256
257     parser.add_subcommand('import', clicmd.SetupAll)
258     parser.add_subcommand('freeze', clicmd.SetupFreeze)
259     parser.add_subcommand('replication', clicmd.UpdateReplication)
260
261     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
262
263     parser.add_subcommand('add-data', UpdateAddData)
264     parser.add_subcommand('index', clicmd.UpdateIndex)
265     parser.add_subcommand('refresh', clicmd.UpdateRefresh)
266
267     parser.add_subcommand('admin', clicmd.AdminFuncs)
268
269     parser.add_subcommand('export', QueryExport)
270     parser.add_subcommand('serve', AdminServe)
271
272     if kwargs.get('phpcgi_path'):
273         parser.add_subcommand('search', clicmd.APISearch)
274         parser.add_subcommand('reverse', clicmd.APIReverse)
275         parser.add_subcommand('lookup', clicmd.APILookup)
276         parser.add_subcommand('details', clicmd.APIDetails)
277         parser.add_subcommand('status', clicmd.APIStatus)
278     else:
279         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
280
281     return parser
282
283
284 def nominatim(**kwargs):
285     """\
286     Command-line tools for importing, updating, administrating and
287     querying the Nominatim database.
288     """
289     parser = get_set_parser(**kwargs)
290
291     return parser.run(**kwargs)