@crossdomain(origin='*')
def get_ifttt_meta(field):
    if request.headers.get('IFTTT-Channel-Key') != environ.get('IFTTT_CHANNEL_KEY'):
        return make_ifttt_error('incorrect channel key', 401)

    data = []
    if field == 'node':
        args = {'network': 'plenario_development'}
        fields = ('network',)
        validated_args = sensor_network_validate(IFTTTValidator(only=fields), args)
        data = [{'label': node.id,
                 'value': node.id} for node in get_raw_metadata('nodes', validated_args)]
    elif field == 'curated_property':
        data = [{'label': curated_property,
                 'value': curated_property} for curated_property in list(curated_map.keys())]

    return make_ifttt_response(data) @crossdomain(origin='*')
def get_ifttt_observations():
    if request.headers.get('IFTTT-Channel-Key') != environ.get('IFTTT_CHANNEL_KEY'):
        return make_ifttt_error('incorrect channel key', 401)

    input_args = request.json
    args = dict()
    try:
        args['network'] = 'plenario_development'
        args['nodes'] = [input_args['triggerFields']['node']]
        args['feature'] = curated_map[input_args['triggerFields']['curated_property']].split('.')[0]
        args['limit'] = input_args['limit'] if 'limit' in list(input_args.keys()) else 50
        args['filter'] = json.dumps({'prop': curated_map[input_args['triggerFields']['curated_property']].split('.')[1],
                                     'op': input_args['triggerFields']['op'],
                                     'val': float(input_args['triggerFields']['val'])})
        # pass through the curated input property so we can return it to the user for display purposes
        curated_property = input_args['triggerFields']['curated_property']
    except (KeyError, ValueError) as err:
        return make_ifttt_error(str(err), 400)

    # override the normal limit 0 behaviour, which is to apply no limit
    if args['limit'] == 0:
        return make_ifttt_response([])

    fields = ('network', 'nodes', 'feature', 'sensors',
              'start_datetime', 'end_datetime', 'limit', 'filter')

    validated_args = sensor_network_validate(IFTTTValidator(only=fields), args)
    if validated_args.errors:
        return bad_request(validated_args.errors)
    validated_args.data.update({
        'features': [validated_args.data['feature']],
        'feature': None
    })
    validated_args = sanitize_validated_args(validated_args)

    observation_queries = get_observation_queries(validated_args)
    if type(observation_queries) != list:
        return observation_queries

    return run_ifttt_queries(observation_queries, curated_property) @crossdomain(origin='*')
def ifttt_status():
    if request.headers.get('IFTTT-Channel-Key') != environ.get('IFTTT_CHANNEL_KEY'):
        return make_ifttt_error('incorrect channel key', 401)

    resp = make_response('{}', 200)
    resp.headers['Content-Type'] = 'application/json'
    return resp @crossdomain(origin='*')
def ifttt_test_setup():
    if request.headers.get('IFTTT-Channel-Key') != environ.get('IFTTT_CHANNEL_KEY'):
        return make_ifttt_error('incorrect channel key', 401)

    resp = {
        'data': {
            'samples': {
                'triggers': {
                    'property_comparison': {
                        'node': 'node_dev_1',
                        'curated_property': 'temperature',
                        'op': 'gt',
                        'val': 0
                    }
                },
                'triggerFieldValidations': {
                    'property_comparison': {
                        'node': {
                            'valid': 'node_dev_1',
                            'invalid': 'invalid_node'
                        },
                        'curated_property': {
                            'valid': 'temperature',
                            'invalid': 'invalid_property'
                        }
                    }
                }
            }
        }
    }

    resp = make_response(json.dumps(resp, default=unknown_object_json_handler), 200)
    resp.headers['Content-Type'] = 'application/json; charset=utf-8'
    return resp @crossdomain(origin='*')
def check(network: str) -> Response:
    '''Validate query parameters.

    :endpoint: /sensor-networks/<network-name>/check'''

    nodes = request.args.get('node') or request.args.get('nodes')
    sensors = request.args.get('sensor') or request.args.get('sensors')
    features = request.args.get('feature') or request.args.get('features')

    args = {
        'network': network,
        'features': features.split(',') if features else [],
        'nodes': nodes.split(',') if nodes else [],
        'sensors': sensors.split(',') if sensors else [],
    }

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    return jsonify({'message': 'Your query params are good to go.'}) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def get_aggregations(network: str) -> Response:
    '''Aggregate individual node observations up to larger units of time.
    Do so by applying aggregate functions on all observations found within
    a specified window of time.

    :endpoint: /sensor-networks/<network-name>/aggregate'''

    node = request.args.get('node')
    feature = request.args.get('feature')
    sensors = request.args.get('sensor') or request.args.get('sensors')

    args = request.args.to_dict()
    args.update({
        'network': network,
        'feature': feature,
        'node': node,
        'sensors': sensors.split(',') if sensors else [],
    })

    validator = AggregateValidator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    if '.' in feature:
        validated.data.update({'property': feature.split('.', 1)[-1]})

    try:
        aggregate_fn = aggregate_fn_map[validated.data.get('function')]
        result = aggregate_fn(validated.data)
    except ValueError as err:
        return bad_request(str(err))

    # TODO(heyzoos)
    # Would rather not have to iterate over the results again just to make sure
    # the datetime format is isoformat as a string.
    for aggregate in result:
        aggregate['time_bucket'] = aggregate['time_bucket'].isoformat()

    return jsonify(json_response_base(validated, result, args)) @crossdomain(origin='*')
def get_feature_metadata(network: str, feature: str = None) -> Response:
    '''Return metadata about features for some network. If no feature is
    specified, return metadata about all features within the network.

    :endpoint: /sensor-networks/<network_name>/features_of_interest/<feature>'''

    args = request.args.to_dict()
    args.update({'network': network, 'features': [feature]})

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    network = validated.data['network']
    features = validated.data['features']

    if feature is None:
        features = []
        for f in network.features():
            features.append(FeatureMeta.query.get(f))
    elif all(feature.lower() not in f for f in network.features()):
        return bad_request('Invalid feature {} for {}'.format(feature, network))

    geojson = validated.data.get('geom')

    if geojson:

        nodes_within_geom = NodeMeta.within_geojson(network, geojson).all()
        if not nodes_within_geom:
            return bad_request('No features found within {}!'.format(geojson))

        feature_set = set()
        for node in nodes_within_geom:
            feature_set.update(node.features())

        if feature is None:
            features = set(FeatureMeta.query.get(f.split('.')[0]) for f in feature_set)
        elif feature not in feature_set:
            return bad_request('No features found within {}!'.format(geojson))

    result = [format_feature_metadata(f) for f in features]
    return jsonify(json_response_base(validated, result, args)) @crossdomain(origin='*')
def get_network_map(network: str) -> Response:
    '''Map of network and the relationships of the elements it contains.'''

    try:
        network_object = NetworkMeta.query.get(network)
    except NoResultFound:
        return bad_request('Invalid network name: %s' % network)
    if not network_object:
        return bad_request('Invalid network name: %s' % network)

    # TODO(heyzoos)
    # Format the returned tree so that beehive internals are not displayed.
    # Specifically: remove the property 'common names' or replace them with
    # more helpful information (property data type and unit of measurement).
    return jsonify(network_object.tree()) @crossdomain(origin='*')
def get_network_metadata(network: str = None) -> Response:
    '''Return metadata for some network. If no network_name is specified, the
    default is to return metadata for all sensor networks.

    :endpoint: /sensor-networks/<network-name>'''

    args = request.args.to_dict()
    args.update({'networks': [network]})

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    networks = validated.data['networks']
    if network is None:
        networks = NetworkMeta.query.all()

    result = [format_network_metadata(n) for n in networks]
    return jsonify(json_response_base(validated, result, args)) @crossdomain(origin='*')
def get_node_download(network: str, node: str):
    '''Return an aws presigned-url to download a month of tar'd node data.'''

    args = request.args.to_dict()
    args.update({
        'network': network,
        'node': node,
        'datetime': args.get('datetime')
    })

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    dt = validated.data['datetime']
    year_and_month = '{}-{}'.format(dt.year, dt.month)
    key = year_and_month + '/' + node + '.tar.gz'
    return redirect(presigned_url(S3_BUCKET, key, node)) @crossdomain(origin='*')
def get_node_metadata(network: str, node: str = None) -> Response:
    '''Return metadata about nodes for some network. If no node_id or
    location_geom__within is specified, the default is to return metadata
    for all nodes within the network.

    :endpoint: /sensor-networks/<network-name>/nodes/<node>'''

    args = request.args.to_dict()
    args.update({'network': network, 'nodes': [node]})

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    network = validated.data['network']
    nodes = validated.data['nodes']

    if node is None:
        nodes = NodeMeta.all(network.name)
    elif node.lower() not in network.tree():
        return bad_request('Invalid node {} for {}'.format(node, network))

    geojson = validated.data.get('geom')
    if geojson:
        nodes_within_geom = NodeMeta.within_geojson(network, geojson).all()
        if not nodes_within_geom:
            return bad_request('No features found within {}!'.format(geojson))
        nodes = nodes_within_geom

    result = [format_node_metadata(n) for n in nodes]
    return jsonify(json_response_base(validated, result, args)) @crossdomain(origin='*')
def get_observation_nearest(network: str) -> Response:
    '''Return a single observation from the node nearest to the specified
    long, lat coordinate.

    :endpoint: /sensor-networks/<network-name>/nearest
               ?lng=<lng>&lat=<lat>&feature=<feature>'''

    args = request.args.to_dict()
    args.update({'network': network})

    validator = NearestValidator(only=('lat', 'lng', 'feature', 'network', 'datetime', 'filter'))
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    result = get_observation_nearest_query(validated)
    return jsonify(json_response_base(validated, result, args)) @crossdomain(origin='*')
def get_observations(network: str) -> Response:
    '''Return raw sensor network observations for a single feature within
    the specified network.

    :endpoint: /sensor-networks/<network-name>/query?feature=<feature>'''

    nodes = request.args.get('nodes')
    sensors = request.args.get('sensors')
    feature = request.args.get('feature')

    args = request.args.to_dict()
    args.update({
        'network': network,
        'feature': feature,
        'nodes': nodes.split(',') if nodes else [],
        'sensors': sensors.split(',') if sensors else [],
    })

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    if '.' in feature:
        feature, property_ = feature.split('.', 1)
        validated.data.update({'property': property_})

    redshift_base.metadata.reflect()
    table = redshift_base.metadata.tables[network + '__' + feature]

    try:
        query = observation_query(table, **validated.data)
    except KeyError as err:
        return bad_request(str(err))

    data = list()
    for obs in query:
        data.append(format_observation(obs, table))

    return jsonify(json_response_base(validated, data, args)) @crossdomain(origin='*')
def get_observations_download(network: str) -> Response:
    '''Stream a sensor network's bulk records to a csv file.

    :endpoint: /sensor-networks/<network>/download'''

    nodes = request.args.get('node') or request.args.get('nodes')
    sensors = request.args.get('sensor') or request.args.get('sensors')
    features = request.args.get('feature') or request.args.get('features')

    kwargs = request.args.to_dict()
    kwargs.update({
        'network': network,
        'features': features.split(',') if features else [],
        'nodes': nodes.split(',') if nodes else [],
        'sensors': sensors.split(',') if sensors else [],
    })

    validator = NoLimitValidator()
    deserialized = validator.load(kwargs)
    if deserialized.errors:
        return bad_request(deserialized.errors)

    if not kwargs['features']:
        kwargs['features'] = deserialized.data['network'].features()

    deserialized = validator.load(kwargs)

    if not deserialized.data.get('start_datetime'):
        deserialized.data.update({'start_datetime': datetime.now() - timedelta(days=7)})

    if deserialized.data.get('data_type') == 'json':
        stream = get_observation_datadump_json(**deserialized.data)
        filename = datetime.now().isoformat() + '-' + deserialized.data['network'].name + '.json'
        attachment = Response(stream_with_context(stream), mimetype='text/json')
    else:
        stream = get_observation_datadump_csv(**deserialized.data)
        filename = datetime.now().isoformat() + '-' + deserialized.data['network'].name + '.csv'
        attachment = Response(stream_with_context(stream), mimetype='text/csv')

    content_disposition = 'attachment; filename={}'.format(filename)
    attachment.headers['Content-Disposition'] = content_disposition
    return attachment @crossdomain(origin='*')
def get_sensor_metadata(network: str, sensor: str = None) -> Response:
    '''Return metadata for all sensors within a network. Sensors can also be
    be filtered by various other properties. If no single sensor is specified,
    the default is to return metadata for all sensors within the network.

    :endpoint: /sensor-networks/<network_name>/sensors/<sensor>'''

    args = request.args.to_dict()
    args.update({'network': network, 'sensors': [sensor]})

    validator = Validator()
    validated = validator.load(args)
    if validated.errors:
        return bad_request(validated.errors)

    network = validated.data['network']
    sensors = validated.data['sensors']

    if sensor is None:
        sensors = []
        for node in NodeMeta.all(network.name):
            sensors += node.sensors
    elif sensor.lower() not in network.sensors():
        return bad_request('Invalid sensor {} for {}'.format(sensor, network))

    geojson = validated.data.get('geom')

    if geojson:

        nodes_within_geom = NodeMeta.within_geojson(network, geojson).all()
        if not nodes_within_geom:
            return bad_request('No sensors found within {}!'.format(geojson))

        sensors_within_geom = NodeMeta.sensors_from_nodes(nodes_within_geom)
        if sensor is None:
            sensors = sensors_within_geom
        elif SensorMeta.query.get(sensor) not in sensors_within_geom:
            return bad_request('No sensors found within {}!'.format(geojson))

    result = [format_sensor_metadata(s) for s in set(sensors)]
    return jsonify(json_response_base(validated, result, args)) RESPONSE_LIMIT = 1000
CACHE_TIMEOUT = 60 * 60 * 6
def unknown_object_json_handler(obj):
def date_json_handler(obj):
def crossdomain(origin=None, methods=None, headers=None,
                max_age=21600, attach_to_all=True,
                automatic_options=True):  # pragma: no cover
    def get_methods():
    def decorator(f):
        def wrapped_function(*args, **kwargs):
def make_cache_key(*args, **kwargs):
def make_csv(data):
def extract_first_geometry_fragment(geojson):
def make_fragment_str(geojson_fragment, buffer=100): @crossdomain(origin='*')
def datadump_view():
    fields = ('location_geom__within', 'dataset_name', 'shape', 'obs_date__ge',
              'obs_date__le', 'offset', 'date__time_of_day_ge',
              'date__time_of_day_le', 'limit', 'job', 'data_type')

    validator = DatasetRequiredValidator(only=fields)
    validator_result = validate(validator, request.args.to_dict())

    if validator_result.errors:
        return api_response.error(validator_result.errors, 400)

    stream = datadump(**validator_result.data)

    dataset = validator_result.data['dataset'].name
    fmt = validator_result.data['data_type']
    content_disposition = 'attachment; filename={}.{}'.format(dataset, fmt)

    attachment = Response(stream_with_context(stream), mimetype='text/%s' % fmt)
    attachment.headers['Content-Disposition'] = content_disposition
    return attachment @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def dataset_fields(dataset_name):
    request_args = request.args.to_dict()
    request_args['dataset_name'] = dataset_name
    fields = ('obs_date__le', 'obs_date__ge', 'dataset_name', 'job')
    validator = DatasetRequiredValidator(only=fields)
    validator_result = validate(validator, request_args)

    if validator_result.errors:
        return api_response.bad_request(validator_result.errors)

    if validator_result.data.get('job'):
        return make_job_response('fields', validator_result)
    else:
        result_data = _meta(validator_result)
        return api_response.fields_response(result_data, validator_result) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def detail():
    fields = ('location_geom__within', 'dataset_name', 'shape', 'obs_date__ge',
              'obs_date__le', 'data_type', 'offset', 'date__time_of_day_ge',
              'date__time_of_day_le', 'limit', 'job')
    validator = DatasetRequiredValidator(only=fields)
    validator_result = validate(validator, request.args.to_dict())

    if validator_result.errors:
        return api_response.bad_request(validator_result.errors)

    if validator_result.data.get('job'):
        return make_job_response('detail', validator_result)
    else:
        result_rows = _detail(validator_result)
        return api_response.detail_response(result_rows, validator_result) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def detail_aggregate():
    fields = ('location_geom__within', 'dataset_name', 'agg', 'obs_date__ge',
              'obs_date__le', 'data_type', 'job')
    validator = NoGeoJSONDatasetRequiredValidator(only=fields)
    validator_result = validate(validator, request.args.to_dict())

    if validator_result.errors:
        return api_response.bad_request(validator_result.errors)

    if validator_result.data.get('job'):
        return make_job_response('detail-aggregate', validator_result)
    else:
        time_counts = _detail_aggregate(validator_result)
        return api_response.detail_aggregate_response(time_counts, validator_result) @crossdomain(origin='*')
def get_job_view(ticket):
    return jsonify(get_job(ticket)) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def grid():

    fields = (
        'dataset',
        'dataset_name',
        'resolution',
        'buffer',
        'obs_date__le',
        'obs_date__ge',
        'location_geom__within',
    )

    validator = PointsetRequiredValidator(only=fields)
    validator_result = validate(validator, request.args)
    if validator_result.errors:
        return api_response.bad_request(validator_result.errors)

    results = _grid(validator_result)

    query = validator.dumps(validator_result.data)
    query = json.loads(query.data)
    results['properties'] = query
    return jsonify(results) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def meta():
    fields = ('obs_date__le', 'obs_date__ge', 'dataset_name', 'location_geom__within', 'job')
    validator_result = validate(NoDefaultDatesValidator(only=fields), request.args.to_dict())

    if validator_result.errors:
        return api_response.bad_request(validator_result.errors)

    if validator_result.data.get('job'):
        return make_job_response('meta', validator_result)
    else:
        result_data = _meta(validator_result)
        return api_response.meta_response(result_data, validator_result) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def weather(table):
    raw_query_params = request.args.copy()

    weather_table = Table(
        'dat_weather_observations_{}'.format(table),
        postgres_base.metadata,
        autoload=True,
        autoload_with=engine,
        extend_existing=True
    )

    stations_table = Table(
        'weather_stations',
        postgres_base.metadata,
        autoload=True,
        autoload_with=engine,
        extend_existing=True
    )

    valid_query, query_clauses, resp, status_code = make_query(weather_table,
                                                               raw_query_params)

    if valid_query:
        resp['meta']['status'] = 'ok'
        base_query = postgres_session.query(weather_table, stations_table)
        base_query = base_query.join(
            stations_table,
            weather_table.c.wban_code == stations_table.c.wban_code
        )

        for clause in query_clauses:
            base_query = base_query.filter(clause)

        try:
            base_query = base_query.order_by(
                getattr(weather_table.c, 'date').desc()
            )
        except AttributeError:
            base_query = base_query.order_by(
                getattr(weather_table.c, 'datetime').desc()
            )
        base_query = base_query.limit(RESPONSE_LIMIT)

        if raw_query_params.get('offset'):
            offset = raw_query_params['offset']
            base_query = base_query.offset(int(offset))

        values = [r for r in base_query.all()]
        weather_fields = list(weather_table.columns.keys())
        station_fields = list(stations_table.columns.keys())
        weather_data = {}
        station_data = {}

        for value in values:
            wd = {f: getattr(value, f) for f in weather_fields}
            sd = {f: getattr(value, f) for f in station_fields}
            if weather_data.get(value.wban_code):
                weather_data[value.wban_code].append(wd)
            else:
                weather_data[value.wban_code] = [wd]
            loc = bytes(value.location.data)
            sd['location'] = shapely.wkb.loads(loc).__geo_interface__
            station_data[value.wban_code] = sd

        for station_id in list(weather_data.keys()):
            d = {
                'station_info': station_data[station_id],
                'observations': weather_data[station_id],
            }
            resp['objects'].append(d)
        resp['meta']['total'] = sum([len(r['observations']) for r in resp['objects']])

    resp['meta']['query'] = raw_query_params
    resp = make_response(
        json.dumps(resp, default=date_json_handler),
        status_code
    )
    resp.headers['Content-Type'] = 'application/json'
    return resp @crossdomain(origin='*')
def weather_fill():
    from plenario.tasks import update_weather

    args = request.args.copy()
    year = year_if_valid(args.get('year'))
    if not year:
        return make_error('Must supply a year between 2000 and 2019', 400)

    month = month_if_valid(args.get('month'))
    if not month:
        return make_error('Must supply month as number between 1 and 12', 400)

    wbans = wban_list_if_valid(args.get('wbans'))
    if not wbans:
        return make_error('WBAN list misformatted or no WBANS provided are '
                          'available. Check /weather-stations', 400)

    task_id = update_weather.delay(month=month, year=year, wbans=wbans).id
    return jsonify({
        'message': 'Successfully queued weather update.',
        'task_id': task_id
    }) @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def weather_stations():
    raw_query_params = request.args.copy()

    stations_table = Table(
        'weather_stations',
        postgres_base.metadata,
        autoload=True,
        autoload_with=engine,
        extend_existing=True
    )

    valid_query, query_clauses, resp, status_code = make_query(stations_table, raw_query_params)
    if valid_query:

        resp['meta']['status'] = 'ok'
        base_query = postgres_session.query(stations_table)

        for clause in query_clauses:
            print(('weather_stations(): filtering on clause', clause))
            base_query = base_query.filter(clause)

        values = [r for r in base_query.all()]
        fieldnames = [f for f in list(stations_table.columns.keys())]

        for value in values:
            d = {f: getattr(value, f) for f in fieldnames}
            loc = bytes(value.location.data)
            d['location'] = shapely.wkb.loads(loc).__geo_interface__
            resp['objects'].append(d)

    resp['meta']['query'] = raw_query_params
    resp = make_response(
        json.dumps(resp, default=date_json_handler),
        status_code
    )
    resp.headers['Content-Type'] = 'application/json'
    return resp @crossdomain(origin='*')
def aggregate_point_data(point_dataset_name, polygon_dataset_name):
    consider = ('dataset_name', 'shape', 'obs_date__ge', 'obs_date__le',
                'data_type', 'location_geom__within', 'job')

    request_args = request.args.to_dict()
    request_args['dataset_name'] = point_dataset_name
    request_args['shape'] = polygon_dataset_name

    validated_args = validate(Validator(only=consider), request_args)

    if validated_args.errors:
        return bad_request(validated_args.errors)
    elif validated_args.data.get('job'):
        return make_job_response('aggregate-point-data', validated_args)
    else:
        result = _aggregate_point_data(validated_args)
        data_type = validated_args.data.get('data_type')
        return aggregate_point_data_response(
            data_type,
            result,
            [polygon_dataset_name, point_dataset_name]
        ) @crossdomain(origin='*')
def export_shape(dataset_name):
    """Route for /shapes/<shapeset>/ endpoint. Requires a dataset argument
    and can apply column specific filters to it.

    :param dataset_name: user provided name of target shapeset
    :returns: response object result of _export_shape
    """
    # Find a way to work these into the validator, they shouldn't be out here.
    if dataset_name not in ShapeMetadata.tablenames():
        return make_error(dataset_name + ' not found.', 404)
    try:
        ShapeMetadata.get_by_dataset_name(dataset_name).shape_table
    except NoSuchTableError:
        return make_error(dataset_name + ' has yet to be ingested.', 404)

    meta_params = ('shape', 'data_type', 'location_geom__within', 'job')
    request_args = request.args.to_dict()

    # Using the 'shape' key triggers the correct validator.
    request_args['shape'] = dataset_name
    validated_args = validate(
        ExportFormatsValidator(only=meta_params),
        request_args
    )

    if validated_args.errors:
        return bad_request(validated_args.errors)
    elif validated_args.data.get('job'):
        return make_job_response('export-shape', validated_args)
    else:
        query = _export_shape(validated_args)
        shapeset = validated_args.data.get('shapeset')
        data_type = validated_args.data.get('data_type')
        return export_dataset_to_response(shapeset, data_type, query) @crossdomain(origin='*')
def get_all_shape_datasets():
    """Fetches metadata for every shape dataset in meta_shape.
    """
    try:
        response_skeleton = {
            'meta': {
                'status': 'ok',
                'message': '',
            },
            'objects': []
        }

        geom = request.args.get('location_geom__within')
        simple_bbox = request.args.get('simple_bbox')

        if geom:
            geom = make_fragment_str(
                extract_first_geometry_fragment(geom)
            )

        if simple_bbox:
            public_listing = ShapeMetadata.simple_index(geom)
        else:
            public_listing = ShapeMetadata.index(geom)
        response_skeleton['objects'] = public_listing
        status_code = 200

    except Exception as e:
        response_skeleton = {
            'meta': {
                'status': 'error',
                'message': str(e),
            }
        }
        status_code = 500

    resp = make_response(json.dumps(response_skeleton, default=str), status_code)
    resp.headers['Content-Type'] = 'application/json'
    return resp @cache.cached(timeout=CACHE_TIMEOUT, key_prefix=make_cache_key)
@crossdomain(origin='*')
def timeseries():
    validator = TimeseriesValidator()

    deserialized_arguments = validator.load(request.args)
    serialized_arguments = json.loads(validator.dumps(deserialized_arguments.data).data)

    if deserialized_arguments.errors:
        return make_error(deserialized_arguments.errors, 400, serialized_arguments)

    qargs = deserialized_arguments.data

    agg = qargs['agg']
    data_type = qargs['data_type']
    geom = qargs['location_geom__within']
    pointset = qargs['dataset_name']
    pointsets = qargs['dataset_name__in']
    start_date = qargs['obs_date__ge']
    end_date = qargs['obs_date__le']

    ctrees = {}
    raw_ctrees = {}

    if has_tree_filters(request.args):
        # Timeseries is a little tricky. If there aren't filters,
        # it would be ridiculous to build a condition tree for every one.
        for field, value in list(request.args.items()):
            if 'filter' in field:
                # This pattern matches the last occurrence of the '__' pattern.
                # Prevents an error that is caused by dataset names with trailing
                # underscores.
                tablename = re.split(r'__(?!_)', field)[0]
                metarecord = MetaTable.get_by_dataset_name(tablename)
                pt = metarecord.point_table
                ctrees[pt.name] = parse_tree(pt, json.loads(value))
                raw_ctrees[pt.name] = json.loads(value)

    point_set_names = [p.name for p in pointsets + [pointset] if p is not None]
    if not point_set_names:
        point_set_names = MetaTable.index()

    results = MetaTable.timeseries_all(point_set_names, agg, start_date, end_date, geom, ctrees)

    payload = {
        'meta': {
            'message': [],
            'query': serialized_arguments,
            'status': 'ok',
            'total': len(results)
        },
        'objects': results
    }

    if ctrees:
        payload['meta']['query']['filters'] = raw_ctrees

    if data_type == 'json':
        return jsonify(payload)

    elif data_type == 'csv':

        # response format
        # temporal_group,dataset_name_1,dataset_name_2
        # 2014-02-24 00:00:00,235,653
        # 2014-03-03 00:00:00,156,624

        fields = ['temporal_group']
        for o in payload['objects']:
            fields.append(o['dataset_name'])

        csv_resp = []
        i = 0
        for k, g in groupby(payload['objects'], key=itemgetter('dataset_name')):
            l_g = list(g)[0]

            j = 0
            for row in l_g['items']:
                # first iteration, populate the first column with temporal_groups
                if i == 0:
                    csv_resp.append([row['datetime']])
                csv_resp[j].append(row['count'])
                j += 1
            i += 1

        csv_resp.insert(0, fields)
        csv_resp = make_csv(csv_resp)
        resp = make_response(csv_resp, 200)
        resp.headers['Content-Type'] = 'text/csv'
        filedate = datetime.now().strftime('%Y-%m-%d')
        resp.headers['Content-Disposition'] = 'attachment; filename=%s.csv' % filedate

        return resp
import json
from time import sleep
from flask import Blueprint, make_response
from plenario.sensor_network.api.ifttt import get_ifttt_meta, get_ifttt_observations, ifttt_status, ifttt_test_setup
from plenario.sensor_network.api.sensor_networks import check, get_aggregations, get_feature_metadata, \
    get_network_map, get_network_metadata, get_node_download, get_node_metadata, get_observation_nearest, \
    get_observations, get_observations_download, get_sensor_metadata
from .common import cache, make_cache_key
from .point import datadump_view, dataset_fields, detail, detail_aggregate, get_job_view, grid, meta
from .sensor import weather, weather_fill, weather_stations
from .shape import aggregate_point_data, export_shape, get_all_shape_datasets
from .timeseries import timeseries




api = Blueprint('api', __name__)

API_VERSION = '/v1'

prefix = API_VERSION + '/api'


api.add_url_rule('{}{}'.format(prefix, '/timeseries'), 'timeseries', timeseries)
api.add_url_rule('{}{}'.format(prefix, '/detail'), 'detail', detail)
api.add_url_rule('{}{}'.format(prefix, '/detail-aggregate'), 'detail-aggregate', detail_aggregate)
api.add_url_rule('{}{}'.format(prefix, '/datasets'), 'meta', meta)
api.add_url_rule('{}{}'.format(prefix, '/fields/<dataset_name>'), 'point_fields', dataset_fields)
api.add_url_rule('{}{}'.format(prefix, '/grid'), 'grid', grid)


api.add_url_rule('{}{}'.format(prefix, '/weather/<table>/'), 'weather', weather)