@click.group()
@click.pass_context
@click.option('-w', '--workers', default=4,
              help=('The number of concurrent downloads when requesting '
                    'multiple scenes. - Default 4'))
@click.option('-v', '--verbose', count=True, help='Specify verbosity')
@click.option('-k', '--api-key',
              help='Valid API key - or via ENV variable %s' % api.auth.ENV_KEY)
@click.option('-u', '--base-url', envvar='PL_API_BASE_URL',
              help='Change the base Planet API URL or ENV PL_API_BASE_URL'
                   ' - Default https://api.planet.com/')
@click.version_option(version=__version__, message='%(version)s')
def cli(context, verbose, api_key, base_url, workers):
    '''Planet API Client'''

    configure_logging(verbose)

    client_params.clear()
    client_params['api_key'] = api_key
    client_params['workers'] = workers
    if base_url:
        client_params['base_url'] = base_url def clientv1():
    return api.ClientV1(**client_params) def limit_option(default):
def filter_opts(fun):
def search_request_opts(fun): class _LenientChoice(click.Choice):
class Bundle(_LenientChoice):
class ItemType(_LenientChoice):
class AssetType(_LenientChoice):
class AssetTypePerm(AssetType):
class _FilterFieldValues(CompositeParamType):
class StringIn(_FilterFieldValues):
class NumberIn(_FilterFieldValues):
class Range(CompositeParamType):
class DateRange(Range):
class GeomFilter(click.ParamType):
class FilterJSON(click.ParamType):
class SortSpec(CompositeParamType):
class BoundingBox(click.ParamType):
class DateInterval(click.ParamType):
class ClipAOI(click.ParamType):
class RequiredUnless(click.Option):
    def get_metavar(self, param):
    def _fail(self, msg, val, param, ctx):
    def convert(self, val, param, ctx):
    def _match(self, val, param, ctx, update_on_fail=True):
    def _update_choices(self):
    def get_remote_choices(self):
    def __init__(self):
    def get_remote_choices(self):
    def __init__(self):
    def get_remote_choices(self):
    def __init__(self):
    def get_remote_choices(self):
    def to_permissions(asset_types):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def _builder(self):
    def _builder(self):
    def _builder(self):
    def _parse(self, val, param, ctx):
    def convert(self, vals, param, ctx):
    def _builder(self):
    def _parse(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def convert(self, val, param, ctx):
    def __init__(self, *args, **kwargs):
    def handle_parse_result(self, ctx, opts, args): def call_and_wrap(func, *args, **kw):
    '''call the provided function and wrap any API exception with a click
    exception. this means no stack trace is visible to the user but instead
    a (hopefully) nice message is provided.
    note: could be a decorator but didn't play well with click
    '''
    try:
        return func(*args, **kw)
    except api.exceptions.APIException as ex:
        click_exception(ex) def check_writable(dirpath):
    try:
        tempfile.NamedTemporaryFile(dir=dirpath).close()
    except OSError:
        return False
    # in windows with a vagrant ro-mount, this was raised instead
    except IOError:
        return False
    return True def click_exception(ex):
    if type(ex) is api.exceptions.APIException:
        raise click.ClickException('Unexpected response: %s' % str(ex))
    msg = "%s: %s" % (type(ex).__name__, str(ex))
    raise click.ClickException(msg) def filter_from_opts(**kw):
    '''Build a AND filter from the provided kwargs defaulting to an
    empty 'and' filter (@todo: API workaround) if nothing is provided.

    If the 'filter_json' argument is provided, this will be assumed to contain
    a filter specification and will be anded with other filters. If the
    'filter_json' is a search, the search filter value will be used.

    All kw values should be tuple or list
    '''
    filter_in = kw.pop('filter_json', None)
    active = and_filter_from_opts(kw)
    if filter_in:
        filter_in = filter_in.get('filter', filter_in)
        if len(active['config']) > 0:
            active = filters.and_filter(active, filter_in)
        else:
            active = filter_in
    return active def downloader_output(dl, disable_ansi=False):
    thread = threading.current_thread()
    # do fancy output if we can or not explicitly disabled
    if sys.stdout.isatty() and not disable_ansi and not termui.WIN:
        return AnsiOutput(thread, dl)
    # work around for lack of nice output for downloader on windows:
    # unless told to be quiet, set logging higher to get some output
    # @todo fallback to simpler 'UI' when isatty on win
    if termui.WIN and not disable_ansi:
        logging.getLogger('').setLevel(logging.INFO)
    return Output(thread, dl) def echo_json_response(response, pretty, limit=None, ndjson=False):
    '''Wrapper to echo JSON with optional 'pretty' printing. If pretty is not
    provided explicity and stdout is a terminal (and not redirected or piped),
    the default will be to indent and sort keys'''
    indent = None
    sort_keys = False
    nl = False
    if not ndjson and (pretty or (pretty is None and sys.stdout.isatty())):
        indent = 2
        sort_keys = True
        nl = True
    try:
        if ndjson and hasattr(response, 'items_iter'):
            items = response.items_iter(limit)
            for item in items:
                click.echo(json.dumps(item))
        elif not ndjson and hasattr(response, 'json_encode'):
            response.json_encode(click.get_text_stream('stdout'), limit=limit,
                                 indent=indent, sort_keys=sort_keys)
        else:
            res = response.get_raw()
            if len(res) == 0:  # if the body is empty, just return the status
                click.echo("status: {}".format(response.response.status_code))
            else:
                res = json.dumps(json.loads(res), indent=indent,
                                 sort_keys=sort_keys)
                click.echo(res)
            if nl:
                click.echo()
    except IOError as ioe:
        # hide scary looking broken pipe stack traces
        raise click.ClickException(str(ioe)) def read(value, split=False):
    '''Get the value of an option interpreting as a file implicitly or
    explicitly and falling back to the value if not explicitly specified.
    If the value is '@name', then a file must exist with name and the returned
    value will be the contents of that file. If the value is '@-' or '-', then
    stdin will be read and returned as the value. Finally, if a file exists
    with the provided value, that file will be read. Otherwise, the value
    will be returned.
    '''
    v = str(value)
    retval = value
    if v[0] == '@' or v == '-':
        fname = '-' if v == '-' else v[1:]
        try:
            with click.open_file(fname) as fp:
                if not fp.isatty():
                    retval = fp.read()
                else:
                    retval = None
        # @todo better to leave as IOError and let caller handle it
        # to better report in context of call (e.g. the option/type)
        except IOError as ioe:
            # if explicit and problems, raise
            if v[0] == '@':
                raise click.ClickException(str(ioe))
    elif path.exists(v) and path.isfile(v):
        with click.open_file(v) as fp:
            retval = fp.read()
    if retval and split and type(retval) != tuple:
        retval = _split(retval.strip())
    return retval def search_req_from_opts(**kw):
    # item_type will be list of lists - flatten
    item_types = chain.from_iterable(kw.pop('item_type'))
    name = kw.pop('name', '')
    interval = kw.pop('interval', '')
    filt = filter_from_opts(**kw)
    return filters.build_search_request(
        filt, item_types, name=name, interval=interval) def create_order_request(**kwargs):
    for opt in ('item_type', 'bundle'):
        inputvalue = kwargs.get(opt)
        if len(inputvalue) > 1:
            raise click.ClickException(
                'only one value for {} is allowed.'.format(opt))

    item_type = kwargs.get('item_type')[0]
    bundle = kwargs.get('bundle')[0]
    ids = kwargs.get('id').split(',')
    email = kwargs.get('email')
    config = kwargs.get('cloudconfig')
    clip = kwargs.get('clip')
    tools = kwargs.get('tools')

    request = {'name': kwargs.get('name'),
               'products': [{'item_ids': ids,
                             'item_type': item_type,
                             'product_bundle': bundle}
                            ],
               'tools': [
    ],
        'delivery': {
    },
        'notifications': {
                   'email': email
    },
    }

    request["delivery"]["archive_filename"] = "{{name}}_{{order_id}}.zip"
    request["delivery"]["archive_type"] = "zip"
    request["delivery"]["single_archive"] = True

    if config:
        with open(config, 'r') as f:
            conf = json.load(f)
            request["delivery"].update(conf)

    # NOTE clip is the only tool that currently can be specified via CLI param.
    # A full tool chain can be specified via JSON file, so that will overwrite
    # clip if both are present. TODO add other common tools as params.
    if clip and not tools:
        toolchain = [{'clip': {'aoi': json.loads(clip)}}]
        request['tools'].extend(toolchain)

    if tools:
        with open(tools, 'r') as f:
            toolchain = json.load(f)
            request["tools"].extend(toolchain)

    return request def ids_from_search_response(resp):
    ret = []
    r = json.loads(resp)
    for feature in r['features']:
        ret.append(feature['id'])
    return ','.join(ret) def handle_interrupt(cancel, f, *a, **kw):
    '''Execute a function f(*a, **kw) listening for KeyboardInterrupt and if
    handled, invoke the cancel function. Blocks until f is complete or the
    interrupt is handled.
    '''
    res = []

    def run():
        res.append(f(*a, **kw))
    t = threading.Thread(target=run)
    t.start()
    # poll (or we miss the interrupt) and await completion
    try:
        while t.is_alive():
            t.join(.1)
    except KeyboardInterrupt:
        cancel()
        raise
    return res and res.pop() def _by_status(assets, types, status):
def _all_status(assets, types, statuses):
    def __init__(self, source, size=0, max_dps=0):
    def work(self):
    def start(self):
    def next(self):
    def _i(self, msg, *args):
    def _d(self, msg, *args):
    def _cancel(self, result):
    def cancel(self):
    def _task(self, t):
    def _alive(self):
    def _capacity(self):
    def _get_tasks(self):
    def _process_task(self):
    def _run(self):
    def __init__(self, source, client, asset_types):
    def _do(self, item):
    def __init__(self, source, client, asset_types):
    def _task(self, t):
    def _do(self, task):
    def __init__(self, source, client, asset_types, dest):
    def _task(self, t):
    def cancel(self):
    def _write_tracker(self, item, asset):
        def _tracker(**kw):
    def _get_writer(self, item, asset):
    def _do(self, task):
    def shutdown(self):
    def stats(self):
    def activate(self, items, asset_types):
    def download(self, items, asset_types, dest):
    def on_complete(self, item, asset, path=None):
    def __init__(self, client, **opts):
    def activate(self, items, asset_types):
    def download(self, items, asset_types, dest):
    def _init(self, items, asset_types, dest):
    def _run(self, items, asset_types, dest=None):
    def _apply_opts(self, to):
    def stats(self):
    def shutdown(self):
    def _task(self, t):
    def _do(self, task):
    def activate(self, items, asset_types):
    def _init(self, items, asset_types, dest):
    def stats(self):
    def _task(self, t):
    def _do(self, task):
    def activate(self, items, asset_types):
    def _init(self, items, asset_types, dest):
    def stats(self):
def create(client, mosaic=False, order=False, **kw):
class _Stage(object):
class _AStage(_Stage):
class _PStage(_Stage):
class _DStage(_Stage):
class Downloader(object):
class _Downloader(Downloader):
class _MosaicDownloadStage(_DStage):
class _MosaicDownloader(_Downloader):
class _OrderDownloadStage(_DStage):
class _OrderDownloader(_Downloader): def write_to_file(directory=None, callback=None, overwrite=True):
    '''Create a callback handler for asynchronous Body handling.

    If provided, the callback will be invoked as described in
    :py:meth:`planet.api.models.Body.write`. In addition, if the download
    is skipped because the destination exists, the callback will be invoked
    with ``callback(skip=body)``.

    The name of the file written to will be determined from the Body.name
    property.

    :param directory str: The optional directory to write to.
    :param callback func: An optional callback to receive notification of
                          write progress.
    :param overwrite bool: Overwrite any existing files. Defaults to True.
    '''

    def writer(body):
        file = os.path.join(directory or '.', body.name)
        if overwrite or not os.path.exists(file):
            body.write(file, callback)
        else:
            if callback:
                callback(skip=body)
            body.response.close()
    return writer
import click
import json
from click.testing import CliRunner
from itertools import chain
from .cli import (
    cli,
    clientv1,
)
from .opts import (
    asset_type_option,
    bundle_option,
    asset_type_perms,
    filter_opts,
    limit_option,
    pretty,
    search_request_opts,
    sort_order
)
from .types import (
    AssetTypePerm,
    BoundingBox,
    metavar_docs,
    DateInterval,
    ItemType,
    RequiredUnless,
    ClipAOI,
)
from .util import (
    call_and_wrap,
    check_writable,
    click_exception,
    filter_from_opts,
    downloader_output,
    echo_json_response,
    read,
    search_req_from_opts,
    create_order_request,
    ids_from_search_response,
)
from planet.api.utils import (
    handle_interrupt
)
from planet.api import downloader
from planet.api.utils import write_to_file
# Copyright 2017 Planet Labs, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.



filter_opts_epilog = '\nFilter Formats:\n\n' + \
                     '\n'.join(['%s\n\n%s' % (k, v.replace('    ', '')
                                              .replace('``', '\''))
                                for k, v in metavar_docs.items()])

DEFAULT_SEARCH_LIMIT = 100
MAX_PAGE_SIZE = 250


@cli.group('data')
def data():
    '''Commands for interacting with the Data API'''
    pass


@data.command('filter', epilog=filter_opts_epilog)
@filter_opts
def filter_dump(**kw):
    '''Output a AND filter as JSON to stdout.

    If provided using --filter-json, combine the filters.

    The output is suitable for use in other commands via the
    --filter-json option.
    '''
    click.echo(json.dumps(filter_from_opts(**kw), indent=2))


@data.command('search', epilog=filter_opts_epilog)
@limit_option(DEFAULT_SEARCH_LIMIT)
@pretty

@asset_type_perms