]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/cli.py
prepare 3.7.0 release
[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 .config import Configuration
12 from .tools.exec_utils import run_legacy_script, run_php_server
13 from .errors import UsageError
14 from . import clicmd
15 from .clicmd.args import NominatimArgs
16 from .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
88         log = logging.getLogger()
89         log.warning('Using project directory: %s', str(args.project_dir))
90
91         try:
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)
97
98         # If we get here, then execution has failed in some way.
99         return 1
100
101
102 ##### Subcommand classes
103 #
104 # Each class needs to implement two functions: add_args() adds the CLI parameters
105 # for the subfunction, run() executes the subcommand.
106 #
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
109 # a subcommand.
110 #
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
115 class UpdateAddData:
116     """\
117     Add additional data from a file or an online source.
118
119     Data is only imported, not indexed. You need to call `nominatim-update index`
120     to complete the process.
121     """
122
123     @staticmethod
124     def add_args(parser):
125         group_name = parser.add_argument_group('Source')
126         group = group_name.add_mutually_exclusive_group(required=True)
127         group.add_argument('--file', metavar='FILE',
128                            help='Import data from an OSM file')
129         group.add_argument('--diff', metavar='FILE',
130                            help='Import data from an OSM diff file')
131         group.add_argument('--node', metavar='ID', type=int,
132                            help='Import a single node from the API')
133         group.add_argument('--way', metavar='ID', type=int,
134                            help='Import a single way from the API')
135         group.add_argument('--relation', metavar='ID', type=int,
136                            help='Import a single relation from the API')
137         group.add_argument('--tiger-data', metavar='DIR',
138                            help='Add housenumbers from the US TIGER census database.')
139         group = parser.add_argument_group('Extra arguments')
140         group.add_argument('--use-main-api', action='store_true',
141                            help='Use OSM API instead of Overpass to download objects')
142
143     @staticmethod
144     def run(args):
145         if args.tiger_data:
146             return tiger_data.add_tiger_data(args.config.get_libpq_dsn(),
147                                              args.tiger_data,
148                                              args.threads or 1,
149                                              args.config,
150                                              args.sqllib_dir)
151
152         params = ['update.php']
153         if args.file:
154             params.extend(('--import-file', args.file))
155         elif args.diff:
156             params.extend(('--import-diff', args.diff))
157         elif args.node:
158             params.extend(('--import-node', args.node))
159         elif args.way:
160             params.extend(('--import-way', args.way))
161         elif args.relation:
162             params.extend(('--import-relation', args.relation))
163         if args.use_main_api:
164             params.append('--use-main-api')
165         return run_legacy_script(*params, nominatim_env=args)
166
167
168 class QueryExport:
169     """\
170     Export addresses as CSV file from the database.
171     """
172
173     @staticmethod
174     def add_args(parser):
175         group = parser.add_argument_group('Output arguments')
176         group.add_argument('--output-type', default='street',
177                            choices=('continent', 'country', 'state', 'county',
178                                     'city', 'suburb', 'street', 'path'),
179                            help='Type of places to output (default: street)')
180         group.add_argument('--output-format',
181                            default='street;suburb;city;county;state;country',
182                            help=("Semicolon-separated list of address types "
183                                  "(see --output-type). Multiple ranks can be "
184                                  "merged into one column by simply using a "
185                                  "comma-separated list."))
186         group.add_argument('--output-all-postcodes', action='store_true',
187                            help=("List all postcodes for address instead of "
188                                  "just the most likely one"))
189         group.add_argument('--language',
190                            help=("Preferred language for output "
191                                  "(use local name, if omitted)"))
192         group = parser.add_argument_group('Filter arguments')
193         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
194                            help='Export only objects within country')
195         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
196                            help='Export only children of this OSM node')
197         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
198                            help='Export only children of this OSM way')
199         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
200                            help='Export only children of this OSM relation')
201
202
203     @staticmethod
204     def run(args):
205         params = ['export.php',
206                   '--output-type', args.output_type,
207                   '--output-format', args.output_format]
208         if args.output_all_postcodes:
209             params.append('--output-all-postcodes')
210         if args.language:
211             params.extend(('--language', args.language))
212         if args.restrict_to_country:
213             params.extend(('--restrict-to-country', args.restrict_to_country))
214         if args.restrict_to_osm_node:
215             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
216         if args.restrict_to_osm_way:
217             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
218         if args.restrict_to_osm_relation:
219             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
220
221         return run_legacy_script(*params, nominatim_env=args)
222
223
224 class AdminServe:
225     """\
226     Start a simple web server for serving the API.
227
228     This command starts the built-in PHP webserver to serve the website
229     from the current project directory. This webserver is only suitable
230     for testing and develop. Do not use it in production setups!
231
232     By the default, the webserver can be accessed at: http://127.0.0.1:8088
233     """
234
235     @staticmethod
236     def add_args(parser):
237         group = parser.add_argument_group('Server arguments')
238         group.add_argument('--server', default='127.0.0.1:8088',
239                            help='The address the server will listen to.')
240
241     @staticmethod
242     def run(args):
243         run_php_server(args.server, args.project_dir / 'website')
244
245 def get_set_parser(**kwargs):
246     """\
247     Initializes the parser and adds various subcommands for
248     nominatim cli.
249     """
250     parser = CommandlineParser('nominatim', nominatim.__doc__)
251
252     parser.add_subcommand('import', clicmd.SetupAll)
253     parser.add_subcommand('freeze', clicmd.SetupFreeze)
254     parser.add_subcommand('replication', clicmd.UpdateReplication)
255
256     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
257
258     parser.add_subcommand('add-data', UpdateAddData)
259     parser.add_subcommand('index', clicmd.UpdateIndex)
260     parser.add_subcommand('refresh', clicmd.UpdateRefresh)
261
262     parser.add_subcommand('admin', clicmd.AdminFuncs)
263
264     parser.add_subcommand('export', QueryExport)
265     parser.add_subcommand('serve', AdminServe)
266
267     if kwargs.get('phpcgi_path'):
268         parser.add_subcommand('search', clicmd.APISearch)
269         parser.add_subcommand('reverse', clicmd.APIReverse)
270         parser.add_subcommand('lookup', clicmd.APILookup)
271         parser.add_subcommand('details', clicmd.APIDetails)
272         parser.add_subcommand('status', clicmd.APIStatus)
273     else:
274         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
275
276     parser.add_subcommand('transition', clicmd.AdminTransition)
277
278     return parser
279
280
281 def nominatim(**kwargs):
282     """\
283     Command-line tools for importing, updating, administrating and
284     querying the Nominatim database.
285     """
286     parser = get_set_parser(**kwargs)
287
288     return parser.run(**kwargs)