2 Command-line interface to the Nominatim functions for import, update,
 
   3 database administration and querying.
 
   9 from pathlib import Path
 
  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
 
  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))
 
  87         args.config.set_libdirs(module=args.module_dir,
 
  88                                 osm2pgsql=args.osm2pgsql_path,
 
  93         log = logging.getLogger()
 
  94         log.warning('Using project directory: %s', str(args.project_dir))
 
  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)
 
 103         # If we get here, then execution has failed in some way.
 
 107 ##### Subcommand classes
 
 109 # Each class needs to implement two functions: add_args() adds the CLI parameters
 
 110 # for the subfunction, run() executes the subcommand.
 
 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
 
 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
 
 122     Add additional data from a file or an online source.
 
 124     Data is only imported, not indexed. You need to call `nominatim index`
 
 125     to complete the process.
 
 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')
 
 151             return tiger_data.add_tiger_data(args.tiger_data,
 
 152                                              args.config, args.threads or 1)
 
 154         params = ['update.php']
 
 156             params.extend(('--import-file', args.file))
 
 158             params.extend(('--import-diff', args.diff))
 
 160             params.extend(('--import-node', args.node))
 
 162             params.extend(('--import-way', args.way))
 
 164             params.extend(('--import-relation', args.relation))
 
 165         if args.use_main_api:
 
 166             params.append('--use-main-api')
 
 167         return run_legacy_script(*params, nominatim_env=args)
 
 172     Export addresses as CSV file from the database.
 
 176     def add_args(parser):
 
 177         group = parser.add_argument_group('Output arguments')
 
 178         group.add_argument('--output-type', default='street',
 
 179                            choices=('continent', 'country', 'state', 'county',
 
 180                                     'city', 'suburb', 'street', 'path'),
 
 181                            help='Type of places to output (default: street)')
 
 182         group.add_argument('--output-format',
 
 183                            default='street;suburb;city;county;state;country',
 
 184                            help=("Semicolon-separated list of address types "
 
 185                                  "(see --output-type). Multiple ranks can be "
 
 186                                  "merged into one column by simply using a "
 
 187                                  "comma-separated list."))
 
 188         group.add_argument('--output-all-postcodes', action='store_true',
 
 189                            help=("List all postcodes for address instead of "
 
 190                                  "just the most likely one"))
 
 191         group.add_argument('--language',
 
 192                            help=("Preferred language for output "
 
 193                                  "(use local name, if omitted)"))
 
 194         group = parser.add_argument_group('Filter arguments')
 
 195         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
 
 196                            help='Export only objects within country')
 
 197         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
 
 198                            help='Export only children of this OSM node')
 
 199         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
 
 200                            help='Export only children of this OSM way')
 
 201         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
 
 202                            help='Export only children of this OSM relation')
 
 207         params = ['export.php',
 
 208                   '--output-type', args.output_type,
 
 209                   '--output-format', args.output_format]
 
 210         if args.output_all_postcodes:
 
 211             params.append('--output-all-postcodes')
 
 213             params.extend(('--language', args.language))
 
 214         if args.restrict_to_country:
 
 215             params.extend(('--restrict-to-country', args.restrict_to_country))
 
 216         if args.restrict_to_osm_node:
 
 217             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
 
 218         if args.restrict_to_osm_way:
 
 219             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
 
 220         if args.restrict_to_osm_relation:
 
 221             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
 
 223         return run_legacy_script(*params, nominatim_env=args)
 
 228     Start a simple web server for serving the API.
 
 230     This command starts the built-in PHP webserver to serve the website
 
 231     from the current project directory. This webserver is only suitable
 
 232     for testing and develop. Do not use it in production setups!
 
 234     By the default, the webserver can be accessed at: http://127.0.0.1:8088
 
 238     def add_args(parser):
 
 239         group = parser.add_argument_group('Server arguments')
 
 240         group.add_argument('--server', default='127.0.0.1:8088',
 
 241                            help='The address the server will listen to.')
 
 245         run_php_server(args.server, args.project_dir / 'website')
 
 247 def get_set_parser(**kwargs):
 
 249     Initializes the parser and adds various subcommands for
 
 252     parser = CommandlineParser('nominatim', nominatim.__doc__)
 
 254     parser.add_subcommand('import', clicmd.SetupAll)
 
 255     parser.add_subcommand('freeze', clicmd.SetupFreeze)
 
 256     parser.add_subcommand('replication', clicmd.UpdateReplication)
 
 258     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
 
 260     parser.add_subcommand('add-data', UpdateAddData)
 
 261     parser.add_subcommand('index', clicmd.UpdateIndex)
 
 262     parser.add_subcommand('refresh', clicmd.UpdateRefresh)
 
 264     parser.add_subcommand('admin', clicmd.AdminFuncs)
 
 266     parser.add_subcommand('export', QueryExport)
 
 267     parser.add_subcommand('serve', AdminServe)
 
 269     if kwargs.get('phpcgi_path'):
 
 270         parser.add_subcommand('search', clicmd.APISearch)
 
 271         parser.add_subcommand('reverse', clicmd.APIReverse)
 
 272         parser.add_subcommand('lookup', clicmd.APILookup)
 
 273         parser.add_subcommand('details', clicmd.APIDetails)
 
 274         parser.add_subcommand('status', clicmd.APIStatus)
 
 276         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
 
 281 def nominatim(**kwargs):
 
 283     Command-line tools for importing, updating, administrating and
 
 284     querying the Nominatim database.
 
 286     parser = get_set_parser(**kwargs)
 
 288     return parser.run(**kwargs)