def conformance(self, headers_, format_): """ Provide conformance definition :param headers_: copy of HEADERS object :param format_: format of requests, pre checked by pre_process decorator :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) conformance = {'conformsTo': CONFORMANCE} if format_ == 'html': # render headers_['Content-Type'] = 'text/html' content = render_j2_template(self.config, 'conformance.html', conformance) return headers_, 200, content return headers_, 200, json.dumps(conformance)
def openapi(self, headers_, format_, openapi): """ Provide OpenAPI document :param headers_: copy of HEADERS object :param format_: format of requests, pre checked by pre_process decorator :param openapi: dict of OpenAPI definition :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) path = '/'.join([self.config['server']['url'].rstrip('/'), 'openapi']) if format_ == 'html': data = {'openapi-document-path': path} headers_['Content-Type'] = 'text/html' content = render_j2_template(self.config, 'openapi.html', data) return headers_, 200, content headers_['Content-Type'] = \ 'application/vnd.oai.openapi+json;version=3.0' return headers_, 200, json.dumps(openapi)
def get_stac_root(self, headers_, format_): if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) id_ = 'pygeoapi-stac' stac_version = '0.6.2' stac_url = os.path.join(self.config['server']['url'], 'stac') content = { 'id': id_, 'stac_version': stac_version, 'title': self.config['metadata']['identification']['title'], 'description': self.config['metadata']['identification']['description'], # noqa 'license': self.config['metadata']['license']['name'], 'providers': [{ 'name': self.config['metadata']['provider']['name'], 'url': self.config['metadata']['provider']['url'], }], 'links': [] } for key, value in self.config['datasets'].items(): if value['provider']['name'] == 'FileSystem': content['links'].append({ 'rel': 'collection', 'href': '{}/{}?f=json'.format(stac_url, key), 'type': 'application/json' }) content['links'].append({ 'rel': 'collection', 'href': '{}/{}'.format(stac_url, key), 'type': 'text/html' }) if format_ == 'html': # render headers_['Content-Type'] = 'text/html' content = render_j2_template(self.config, 'stac/root.html', content) return headers_, 200, content return headers_, 200, json.dumps(content, default=json_serial)
def get_collection_item(self, headers_, format_, dataset, identifier): """ Get a single feature :param headers_: copy of HEADERS object :param format_: format of requests, pre checked by pre_process decorator :param dataset: dataset name :param identifier: feature identifier :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Processing query parameters') if dataset not in self.config['datasets'].keys(): exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid feature collection' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Loading provider') p = load_plugin('provider', self.config['datasets'][dataset]['provider']) try: LOGGER.debug('Fetching id {}'.format(identifier)) content = p.get(identifier) except ProviderQueryError as err: exception = { 'code': 'NoApplicableCode', 'description': 'query error (check logs)' } LOGGER.error(err) return headers_, 500, json.dumps(exception) except ProviderGenericError as err: exception = { 'code': 'NoApplicableCode', 'description': 'generic error (check logs)' } LOGGER.error(err) return headers_, 500, json.dumps(exception) if content is None: exception = { 'code': 'NotFound', 'description': 'identifier not found' } LOGGER.error(exception) return headers_, 404, json.dumps(exception) content['links'] = [{ 'rel': 'self' if not format_ or format_ == 'json' else 'alternate', 'type': 'application/geo+json', 'title': 'This document as GeoJSON', 'href': '{}/collections/{}/items/{}?f=json'.format( self.config['server']['url'], dataset, identifier) }, { 'rel': 'self' if format_ == 'jsonld' else 'alternate', 'type': 'application/ld+json', 'title': 'This document as RDF (JSON-LD)', 'href': '{}/collections/{}/items/{}?f=jsonld'.format( self.config['server']['url'], dataset, identifier) }, { 'rel': 'self' if format_ == 'html' else 'alternate', 'type': 'text/html', 'title': 'This document as HTML', 'href': '{}/collections/{}/items/{}?f=html'.format( self.config['server']['url'], dataset, identifier) }, { 'rel': 'collection', 'type': 'application/json', 'title': self.config['datasets'][dataset]['title'], 'href': '{}/collections/{}'.format(self.config['server']['url'], dataset) }, { 'rel': 'prev', 'type': 'application/geo+json', 'href': '{}/collections/{}/items/{}'.format(self.config['server']['url'], dataset, identifier) }, { 'rel': 'next', 'type': 'application/geo+json', 'href': '{}/collections/{}/items/{}'.format(self.config['server']['url'], dataset, identifier) }] if format_ == 'html': # render headers_['Content-Type'] = 'text/html' content['title'] = self.config['datasets'][dataset]['title'] content = render_j2_template(self.config, 'item.html', content) return headers_, 200, content elif format_ == 'jsonld': headers_['Content-Type'] = 'application/ld+json' content = geojson2geojsonld(self.config, content, dataset, identifier=identifier) return headers_, 200, content return headers_, 200, json.dumps(content, default=json_serial)
def get_collection_items(self, headers, args, dataset, pathinfo=None): """ Queries feature collection :param headers: dict of HTTP headers :param args: dict of HTTP request parameters :param dataset: dataset name :param pathinfo: path location :returns: tuple of headers, status code, content """ headers_ = HEADERS.copy() properties = [] reserved_fieldnames = [ 'bbox', 'f', 'limit', 'startindex', 'resulttype', 'datetime', 'sortby' ] formats = FORMATS formats.extend(f.lower() for f in PLUGINS['formatter'].keys()) if dataset not in self.config['datasets'].keys(): exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid feature collection' } LOGGER.error(exception) return headers_, 400, json.dumps(exception, default=json_serial) format_ = check_format(args, headers) if format_ is not None and format_ not in formats: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Processing query parameters') LOGGER.debug('Processing startindex parameter') try: startindex = int(args.get('startindex')) if startindex < 0: exception = { 'code': 'InvalidParameterValue', 'description': 'startindex value should be positive ' + 'or zero' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) except (TypeError) as err: LOGGER.warning(err) startindex = 0 except ValueError as err: LOGGER.warning(err) exception = { 'code': 'InvalidParameterValue', 'description': 'startindex value should be an integer' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Processing limit parameter') try: limit = int(args.get('limit')) # TODO: We should do more validation, against the min and max # allowed by the server configuration if limit <= 0: exception = { 'code': 'InvalidParameterValue', 'description': 'limit value should be strictly positive' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) except TypeError as err: LOGGER.warning(err) limit = int(self.config['server']['limit']) except ValueError as err: LOGGER.warning(err) exception = { 'code': 'InvalidParameterValue', 'description': 'limit value should be an integer' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) resulttype = args.get('resulttype') or 'results' LOGGER.debug('Processing bbox parameter') try: bbox = args.get('bbox').split(',') if len(bbox) != 4: exception = { 'code': 'InvalidParameterValue', 'description': 'bbox values should be minx,miny,maxx,maxy' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) except AttributeError: bbox = [] try: bbox = [float(c) for c in bbox] except ValueError: exception = { 'code': 'InvalidParameterValue', 'description': 'bbox values must be numbers' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Processing datetime parameter') # TODO: pass datetime to query as a `datetime` object # we would need to ensure partial dates work accordingly # as well as setting '..' values to `None` so that underlying # providers can just assume a `datetime.datetime` object # # NOTE: needs testing when passing partials from API to backend datetime_ = args.get('datetime') datetime_invalid = False if (datetime_ is not None and 'temporal' in self.config['datasets'][dataset]['extents']): te = self.config['datasets'][dataset]['extents']['temporal'] if te['begin'].tzinfo is None: te['begin'] = te['begin'].replace(tzinfo=pytz.UTC) if te['end'].tzinfo is None: te['end'] = te['end'].replace(tzinfo=pytz.UTC) if '/' in datetime_: # envelope LOGGER.debug('detected time range') LOGGER.debug('Validating time windows') datetime_begin, datetime_end = datetime_.split('/') if datetime_begin != '..': datetime_begin = dateparse(datetime_begin) if datetime_begin.tzinfo is None: datetime_begin = datetime_begin.replace( tzinfo=pytz.UTC) if datetime_end != '..': datetime_end = dateparse(datetime_end) if datetime_end.tzinfo is None: datetime_end = datetime_end.replace(tzinfo=pytz.UTC) if te['begin'] is not None and datetime_begin != '..': if datetime_begin < te['begin']: datetime_invalid = True if te['end'] is not None and datetime_end != '..': if datetime_end > te['end']: datetime_invalid = True else: # time instant datetime__ = dateparse(datetime_) if datetime__ != '..': if datetime__.tzinfo is None: datetime__ = datetime__.replace(tzinfo=pytz.UTC) LOGGER.debug('detected time instant') if te['begin'] is not None and datetime__ != '..': if datetime__ < te['begin']: datetime_invalid = True if te['end'] is not None and datetime__ != '..': if datetime__ > te['end']: datetime_invalid = True if datetime_invalid: exception = { 'code': 'InvalidParameterValue', 'description': 'datetime parameter out of range' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Loading provider') try: p = load_plugin('provider', self.config['datasets'][dataset]['provider']) except ProviderConnectionError: exception = { 'code': 'NoApplicableCode', 'description': 'connection error (check logs)' } LOGGER.error(exception) return headers_, 500, json.dumps(exception) except ProviderQueryError: exception = { 'code': 'NoApplicableCode', 'description': 'query error (check logs)' } LOGGER.error(exception) return headers_, 500, json.dumps(exception) LOGGER.debug('processing property parameters') for k, v in args.items(): if k not in reserved_fieldnames and k not in p.fields.keys(): exception = { 'code': 'InvalidParameterValue', 'description': 'unknown query parameter' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) elif k not in reserved_fieldnames and k in p.fields.keys(): LOGGER.debug('Add property filter {}={}'.format(k, v)) properties.append((k, v)) LOGGER.debug('processing sort parameter') val = args.get('sortby') if val is not None: sortby = [] sorts = val.split(',') for s in sorts: if ':' in s: prop, order = s.split(':') if order not in ['A', 'D']: exception = { 'code': 'InvalidParameterValue', 'description': 'sort order should be A or D' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) sortby.append({'property': prop, 'order': order}) else: sortby.append({'property': s, 'order': 'A'}) for s in sortby: if s['property'] not in p.fields.keys(): exception = { 'code': 'InvalidParameterValue', 'description': 'bad sort property' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) else: sortby = [] LOGGER.debug('Querying provider') LOGGER.debug('startindex: {}'.format(startindex)) LOGGER.debug('limit: {}'.format(limit)) LOGGER.debug('resulttype: {}'.format(resulttype)) LOGGER.debug('sortby: {}'.format(sortby)) try: content = p.query(startindex=startindex, limit=limit, resulttype=resulttype, bbox=bbox, datetime=datetime_, properties=properties, sortby=sortby) except ProviderConnectionError as err: exception = { 'code': 'NoApplicableCode', 'description': 'connection error (check logs)' } LOGGER.error(err) return headers_, 500, json.dumps(exception) except ProviderQueryError as err: exception = { 'code': 'NoApplicableCode', 'description': 'query error (check logs)' } LOGGER.error(err) return headers_, 500, json.dumps(exception) except ProviderGenericError as err: exception = { 'code': 'NoApplicableCode', 'description': 'generic error (check logs)' } LOGGER.error(err) return headers_, 500, json.dumps(exception) serialized_query_params = '' for k, v in args.items(): if k not in ('f', 'startindex'): serialized_query_params += '&' serialized_query_params += urllib.parse.quote(k, safe='') serialized_query_params += '=' serialized_query_params += urllib.parse.quote(str(v), safe=',') content['links'] = [{ 'type': 'application/geo+json', 'rel': 'self' if not format_ or format_ == 'json' else 'alternate', 'title': 'This document as GeoJSON', 'href': '{}/collections/{}/items?f=json{}'.format( self.config['server']['url'], dataset, serialized_query_params) }, { 'rel': 'self' if format_ == 'jsonld' else 'alternate', 'type': 'application/ld+json', 'title': 'This document as RDF (JSON-LD)', 'href': '{}/collections/{}/items?f=jsonld{}'.format( self.config['server']['url'], dataset, serialized_query_params) }, { 'type': 'text/html', 'rel': 'self' if format_ == 'html' else 'alternate', 'title': 'This document as HTML', 'href': '{}/collections/{}/items?f=html{}'.format( self.config['server']['url'], dataset, serialized_query_params) }] if startindex > 0: prev = max(0, startindex - limit) content['links'].append({ 'type': 'application/geo+json', 'rel': 'prev', 'title': 'items (prev)', 'href': '{}/collections/{}/items?startindex={}{}'.format( self.config['server']['url'], dataset, prev, serialized_query_params) }) if len(content['features']) == limit: next_ = startindex + limit content['links'].append({ 'type': 'application/geo+json', 'rel': 'next', 'title': 'items (next)', 'href': '{}/collections/{}/items?startindex={}{}'.format( self.config['server']['url'], dataset, next_, serialized_query_params) }) content['links'].append({ 'type': 'application/json', 'title': self.config['datasets'][dataset]['title'], 'rel': 'collection', 'href': '{}/collections/{}'.format(self.config['server']['url'], dataset) }) content['timeStamp'] = datetime.utcnow().strftime( '%Y-%m-%dT%H:%M:%S.%fZ') if format_ == 'html': # render headers_['Content-Type'] = 'text/html' # For constructing proper URIs to items if pathinfo: path_info = '/'.join([ self.config['server']['url'].rstrip('/'), pathinfo.strip('/') ]) else: path_info = '/'.join([ self.config['server']['url'].rstrip('/'), headers.environ['PATH_INFO'].strip('/') ]) content['items_path'] = path_info content['dataset_path'] = '/'.join(path_info.split('/')[:-1]) content['collections_path'] = '/'.join(path_info.split('/')[:-2]) content['startindex'] = startindex content = render_j2_template(self.config, 'items.html', content) return headers_, 200, content elif format_ == 'csv': # render formatter = load_plugin('formatter', {'name': 'CSV', 'geom': True}) content = formatter.write( data=content, options={ 'provider_def': self.config['datasets'][dataset]['provider'] }) headers_['Content-Type'] = '{}; charset={}'.format( formatter.mimetype, self.config['server']['encoding']) cd = 'attachment; filename="{}.csv"'.format(dataset) headers_['Content-Disposition'] = cd return headers_, 200, content elif format_ == 'jsonld': headers_['Content-Type'] = 'application/ld+json' content = geojson2geojsonld(self.config, content, dataset) return headers_, 200, content return headers_, 200, json.dumps(content, default=json_serial)
def describe_collections(self, headers_, format_, dataset=None): """ Provide feature collection metadata :param headers_: copy of HEADERS object :param format_: format of requests, pre checked by pre_process decorator :param dataset: name of collection :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) fcm = {'collections': [], 'links': []} if all([ dataset is not None, dataset not in self.config['datasets'].keys() ]): exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid feature collection' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Creating collections') for k, v in self.config['datasets'].items(): collection = {'links': []} collection['id'] = k collection['itemType'] = 'feature' collection['title'] = v['title'] collection['description'] = v['description'] collection['keywords'] = v['keywords'] bbox = v['extents']['spatial']['bbox'] # The output should be an array of bbox, so if the user only # provided a single bbox, wrap it in a array. if not isinstance(bbox[0], list): bbox = [bbox] collection['extent'] = {'spatial': {'bbox': bbox}} if 'crs' in v['extents']['spatial']: collection['extent']['spatial']['crs'] = \ v['extents']['spatial']['crs'] t_ext = v.get('extents', {}).get('temporal', {}) if t_ext: begins = dategetter('begin', t_ext) ends = dategetter('end', t_ext) collection['extent']['temporal'] = { 'interval': [[begins, ends]] } if 'trs' in t_ext: collection['extent']['temporal']['trs'] = t_ext['trs'] for link in v['links']: lnk = { 'type': link['type'], 'rel': link['rel'], 'title': link['title'], 'href': link['href'] } if 'hreflang' in link: lnk['hreflang'] = link['hreflang'] collection['links'].append(lnk) LOGGER.debug('Adding JSON and HTML link relations') collection['links'].append({ 'type': 'application/geo+json', 'rel': 'items', 'title': 'Features as GeoJSON', 'href': '{}/collections/{}/items?f=json'.format( self.config['server']['url'], k) }) collection['links'].append({ 'type': 'application/ld+json', 'rel': 'items', 'title': 'Features as RDF (GeoJSON-LD)', 'href': '{}/collections/{}/items?f=jsonld'.format( self.config['server']['url'], k) }) collection['links'].append({ 'type': 'text/html', 'rel': 'items', 'title': 'Features as HTML', 'href': '{}/collections/{}/items?f=html'.format( self.config['server']['url'], k) }) collection['links'].append({ 'type': 'application/json', 'rel': 'self' if not format_ or format_ == 'json' else 'alternate', 'title': 'This document as JSON', 'href': '{}/collections/{}?f=json'.format(self.config['server']['url'], k) }) collection['links'].append({ 'type': 'application/ld+json', 'rel': 'self' if format_ == 'jsonld' else 'alternate', 'title': 'This document as RDF (JSON-LD)', 'href': '{}/collections/{}?f=jsonld'.format( self.config['server']['url'], k) }) collection['links'].append({ 'type': 'text/html', 'rel': 'self' if format_ == 'html' else 'alternate', 'title': 'This document as HTML', 'href': '{}/collections/{}?f=html'.format(self.config['server']['url'], k) }) if dataset is not None and k == dataset: fcm = collection break fcm['collections'].append(collection) if dataset is None: fcm['links'].append({ 'type': 'application/json', 'rel': 'self' if not format or format_ == 'json' else 'alternate', 'title': 'This document as JSON', 'href': '{}/collections?f=json'.format(self.config['server']['url']) }) fcm['links'].append({ 'type': 'application/ld+json', 'rel': 'self' if format_ == 'jsonld' else 'alternate', 'title': 'This document as RDF (JSON-LD)', 'href': '{}/collections?f=jsonld'.format(self.config['server']['url']) }) fcm['links'].append({ 'type': 'text/html', 'rel': 'self' if format_ == 'html' else 'alternate', 'title': 'This document as HTML', 'href': '{}/collections?f=html'.format(self.config['server']['url']) }) if format_ == 'html': # render headers_['Content-Type'] = 'text/html' if dataset is not None: content = render_j2_template(self.config, 'collection.html', fcm) else: content = render_j2_template(self.config, 'collections.html', fcm) return headers_, 200, content if format_ == 'jsonld': jsonld = self.fcmld.copy() if dataset is not None: jsonld['dataset'] = jsonldify_collection(self, fcm) else: jsonld['dataset'] = list( map( lambda collection: jsonldify_collection( self, collection), fcm.get('collections', []))) headers_['Content-Type'] = 'application/ld+json' return headers_, 200, json.dumps(jsonld) return headers_, 200, json.dumps(fcm, default=json_serial)
def root(self, headers_, format_): """ Provide API :param headers_: copy of HEADERS object :param format_: format of requests, pre checked by pre_process decorator :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) fcm = { 'links': [], 'title': self.config['metadata']['identification']['title'], 'description': self.config['metadata']['identification']['description'] } LOGGER.debug('Creating links') fcm['links'] = [{ 'rel': 'self' if not format_ or format_ == 'json' else 'alternate', 'type': 'application/json', 'title': 'This document as JSON', 'href': '{}?f=json'.format(self.config['server']['url']) }, { 'rel': 'self' if format_ == 'jsonld' else 'alternate', 'type': 'application/ld+json', 'title': 'This document as RDF (JSON-LD)', 'href': '{}?f=jsonld'.format(self.config['server']['url']) }, { 'rel': 'self' if format_ == 'html' else 'alternate', 'type': 'text/html', 'title': 'This document as HTML', 'href': '{}?f=html'.format(self.config['server']['url']), 'hreflang': self.config['server']['language'] }, { 'rel': 'service-desc', 'type': 'application/vnd.oai.openapi+json;version=3.0', 'title': 'The OpenAPI definition as JSON', 'href': '{}/openapi'.format(self.config['server']['url']) }, { 'rel': 'service-doc', 'type': 'text/html', 'title': 'The OpenAPI definition as HTML', 'href': '{}/openapi?f=html'.format(self.config['server']['url']), 'hreflang': self.config['server']['language'] }, { 'rel': 'conformance', 'type': 'application/json', 'title': 'Conformance', 'href': '{}/conformance'.format(self.config['server']['url']) }, { 'rel': 'data', 'type': 'application/json', 'title': 'Collections', 'href': '{}/collections'.format(self.config['server']['url']) }] if format_ == 'html': # render headers_['Content-Type'] = 'text/html' content = render_j2_template(self.config, 'root.html', fcm) return headers_, 200, content if format_ == 'jsonld': headers_['Content-Type'] = 'application/ld+json' return headers_, 200, json.dumps(self.fcmld) return headers_, 200, json.dumps(fcm)
def describe_processes(self, headers_, format_, process=None): """ Provide processes metadata :param headers: dict of HTTP headers :param args: dict of HTTP request parameters :param process: name of process :returns: tuple of headers, status code, content """ if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) processes_config = self.config.get('processes', {}) if processes_config: if process is not None: if process not in processes_config.keys(): exception = { 'code': 'NotFound', 'description': 'identifier not found' } LOGGER.error(exception) return headers_, 404, json.dumps(exception) p = load_plugin('process', processes_config[process]['processor']) p.metadata['jobControlOptions'] = ['sync-execute'] p.metadata['outputTransmission'] = ['value'] response = p.metadata else: processes = [] for k, v in processes_config.items(): p = load_plugin('process', processes_config[k]['processor']) p.metadata['itemType'] = 'process' p.metadata['jobControlOptions'] = ['sync-execute'] p.metadata['outputTransmission'] = ['value'] processes.append(p.metadata) response = {'processes': processes} else: processes = [] response = {'processes': processes} if format_ == 'html': # render headers_['Content-Type'] = 'text/html' if process is not None: response = render_j2_template(self.config, 'process.html', p.metadata) else: response = render_j2_template(self.config, 'processes.html', {'processes': processes}) return headers_, 200, response return headers_, 200, json.dumps(response)
def get_stac_path(self, headers_, format_, path): if format_ is not None and format_ not in FORMATS: exception = { 'code': 'InvalidParameterValue', 'description': 'Invalid format' } LOGGER.error(exception) return headers_, 400, json.dumps(exception) LOGGER.debug('Path: {}'.format(path)) dir_tokens = path.split('/') if dir_tokens: dataset = dir_tokens[0] if dataset not in self.config['datasets']: exception = { 'code': 'NotFound', 'description': 'collection not found' } LOGGER.error(exception) return headers_, 404, json.dumps(exception) LOGGER.debug('Loading provider') try: p = load_plugin('provider', self.config['datasets'][dataset]['provider']) except ProviderConnectionError as err: LOGGER.error(err) exception = { 'code': 'NoApplicableCode', 'description': 'connection error (check logs)' } LOGGER.error(exception) return headers_, 500, json.dumps(exception) id_ = '{}-stac'.format(dataset) stac_version = '0.6.2' description = self.config['datasets'][dataset]['description'] content = { 'id': id_, 'stac_version': stac_version, 'description': description, 'links': [] } try: stac_data = p.get_data_path( os.path.join(self.config['server']['url'], 'stac'), path, path.replace(dataset, '', 1) ) except ProviderNotFoundError as err: LOGGER.error(err) exception = { 'code': 'NotFound', 'description': 'resource not found' } return headers_, 404, json.dumps(exception) except Exception as err: LOGGER.error(err) exception = { 'code': 'NoApplicableCode', 'description': 'data query error' } return headers_, 500, json.dumps(exception) if isinstance(stac_data, dict): content.update(stac_data) content['links'].extend(self.config['datasets'][dataset]['links']) if format_ == 'html': # render headers_['Content-Type'] = 'text/html' content['path'] = path if 'assets' in content: # item view content = render_j2_template(self.config, 'stac/item.html', content) else: content = render_j2_template(self.config, 'stac/catalog.html', content) return headers_, 200, content return headers_, 200, json.dumps(content, default=json_serial) else: # send back file headers_.pop('Content-Type', None) return headers_, 200, stac_data