Source code for isofit.__main__

"""ISOFIT command line interface."""

import importlib
import logging
import os
import sys

# Explicitly set the number of threads to be 1, so we more effectively run in parallel
# Must be executed before importing numpy, otherwise doesn't work
# Setting in the CLI ensures this works for any script
if not os.environ.get("ISOFIT_NO_SET_THREADS"):
    os.environ["MKL_NUM_THREADS"] = "1"
    os.environ["OMP_NUM_THREADS"] = "1"

import click

import isofit
from isofit.data import env


[docs] class CLI(click.Group): """ Custom click class to load commands at runtime. This enables optional, external subcommands (such as isoplots) to be inserted into the sys path. Reference: https://click.palletsprojects.com/en/stable/complex/#defining-the-lazy-group """
[docs] laziest = False
def __init__(self, *args, lazy_subcommands=None, **kwargs): super().__init__(*args, **kwargs)
[docs] self.lazy = lazy_subcommands or {}
[docs] def main(self, *args, **kwargs): """ Loads the default ini and checks for the cli_laziest before shell tab completion, which may speed it up """ env.load() self.laziest = env["cli_laziest"] == "1" return super().main(*args, **kwargs)
[docs] def invoke(self, ctx): """ Loads the ISOFIT ini into env and applies any overrides before invoking any subcommands Parameters ---------- ctx : click.Context The Click context """ # Pop these to not pass them along to subcommands ini = ctx.params.pop("ini") base = ctx.params.pop("base") section = ctx.params.pop("section") paths = ctx.params.pop("path") keys = ctx.params.pop("keys") save = ctx.params.pop("save") preview = ctx.params.pop("preview") self.debug = ctx.params.pop("debug") env.load(ini, section) if base: env.changeBase(base) for key, value in paths: if value: env.changePath(key, value) for key, value in keys: env.changeKey(key, value) # Can permanently enable the laziest flag using the ini via `isofit --keys cli_laziest 1` # Disable via `isofit --keys cli_laziest 0` self.laziest = ctx.params.pop("laziest") or env["cli_laziest"] == "1" # --help always disables lazier loading # protected_args is populated when a subcommand is invoked # do NOT laziest, else click.groups break if ctx.params["help"] or ctx.protected_args: self.laziest = False if preview: print(env) else: env.save(diff_only=save) # If an override path is provided, insert it into the sys.paths if env.validate("isoplots", path=env.plots, quiet=True): sys.path.append(env.plots) super().invoke(ctx)
[docs] def list_commands(self, ctx): """ List the names of available commands Parameters ---------- ctx : click.Context The Click context Returns ------- list of str The names of available lazy-loaded commands """ base = super().list_commands(ctx) lazy = list(self.lazy) return base + lazy
[docs] def resolve(self, cmd_name): """ Resolves the import of a click function from a module string The import path to a Click command is to be in the format 'module:function' If 'function' is not provided, defaults seeking for a function named 'cli' Parameters ---------- cmd_name : str Command to retrieve Returns ------- function Resulting Click function imported from a module """ path = self.lazy[cmd_name] func = "cli" if ":" in path: path, func = path.split(":") module = importlib.import_module(path) return getattr(module, func)
[docs] def get_command(self, ctx, cmd_name): """ Get a lazily-loaded command by name Parameters ---------- ctx : click.Context The Click context cmd_name : str Name of the command to retrieve Returns ------- click.Command or None The command object or None if not found """ command = self.lazy.get(cmd_name) if command: # ctx.protected_args needs to be checked here for tab completion cases if self.laziest and not ctx.protected_args: # Just create a fake command for Click return click.Command(cmd_name) try: return self.resolve(cmd_name) except ModuleNotFoundError: if self.debug: if cmd_name == "plot": logging.exception( "Isoplots does not appear to be installed, install it via `isofit download plots`" ) else: logging.exception( f"Failed to import {cmd_name} from {command}:" ) except: if self.debug: logging.exception(f"Failed to import {cmd_name} from {command}:") return super().get_command(ctx, cmd_name)
@click.group( cls=CLI, add_help_option=False, invoke_without_command=True, lazy_subcommands={ "run": "isofit.core.isofit", "build": "isofit.data.build_examples", "download": "isofit.data.download", "validate": "isofit.data.validate", "path": "isofit.data", "dev": "isofit.data:dev", "HRRR_to_modtran": "isofit.utils.add_HRRR_profiles_to_modtran_config", "apply_oe": "isofit.utils.apply_oe", "skyview": "isofit.utils.skyview", "wavelength_cal": "isofit.utils.wavelength_cal:cli_wavelength_cal", "get_wavelength_adjustment": "isofit.utils.wavelength_cal:cli_get_wavelength_adjustment", "6s_to_srtmnet": "isofit.utils.convert_6s_to_srtmnet", "analytical_line": "isofit.utils.analytical_line", "empirical_line": "isofit.utils.empirical_line", "algebraic_line": "isofit.utils.algebraic_line", "ewt": "isofit.utils.ewt_from_reflectance", "reconstruct_subs": "isofit.utils.reconstruct", "interpolate_spectra": "isofit.utils.interpolate_spectra", "classify_multicomponent": "isofit.utils.multicomponent_classification", "sun": "isofit.utils.solar_position", "surface_model": "isofit.utils.surface_model", "plot": "isoplots", }, ) @click.pass_context @click.option("-i", "--ini", help="Override path to an isofit.ini file") @click.option("-b", "--base", help="Override the base directory for all products") @click.option("-s", "--section", help="Switches which section of the ini to use") @click.option( "-p", "--path", nargs=2, multiple=True, help="Override paths with the format `-p [key] [value]`", ) @click.option( "-k", "--keys", nargs=2, multiple=True, help="Override keys with the format `-k [key] [value]`", ) @click.option( "--save/--no-save", " /-S", is_flag=True, default=True, help="Save the ini file" ) @click.option( "--preview", is_flag=True, help="Prints the environment that will be used. This disables saving", ) @click.option("--version", is_flag=True, help="Print the installed ISOFIT version") @click.option("--debug", is_flag=True, help="Enables debug logging for the CLI") @click.option( "--laziest", is_flag=True, help="Changes the CLI to be completely lazy, greatly speeding up the responsiveness but at the cost of not validating subcommands", ) @click.option("--help", is_flag=True, help="Show this message and exit")
[docs] def cli(ctx, version, help, **kwargs): """\ ISOFIT contains a set of routines and utilities for fitting surface, atmosphere and instrument models to imaging spectrometer data. \b Repository: https://github.com/isofit/isofit Documentation: https://isofit.readthedocs.io/en/latest Report an issue: https://github.com/isofit/isofit/issues """ # invoke_without_command so that the invoke() command always gets called if version: print(isofit.__version__) elif help or ctx.invoked_subcommand is None: print(ctx.get_help())
if __name__ == "__main__": cli()