diff --git a/aws-lambda/zappa_settings.json b/aws-lambda/zappa_settings.json index cddd206..09e4549 100644 --- a/aws-lambda/zappa_settings.json +++ b/aws-lambda/zappa_settings.json @@ -3,7 +3,7 @@ "app_function": "pygeoapi.flask_app.APP", "profile_name": null, "project_name": "pygeoapi", - "runtime": "python3.6", + "runtime": "python3.8", "s3_bucket": "zappa-pwpqh2twb", "aws_region": "us-east-2", "environment_variables": { diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 52040d5..855e377 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -86,7 +86,7 @@ LOGGER = logging.getLogger(__name__) #: Return headers for requests (e.g:X-Powered-By) HEADERS = { 'Content-Type': 'application/json', - 'X-Powered-By': 'pygeoapi {}'.format(__version__) + 'X-Powered-By': f'pygeoapi {__version__}' } CHARSET = ['utf-8'] @@ -198,7 +198,7 @@ def gzip(func): content = compress(content.encode(charset)) except TypeError as err: headers.pop('Content-Encoding') - LOGGER.error('Error in compression: {}'.format(err)) + LOGGER.error(f'Error in compression: {err}') return headers, status, content @@ -648,7 +648,7 @@ class API: 'output_dir': None } - LOGGER.debug('Loading process manager {}'.format(manager_def['name'])) + LOGGER.debug(f"Loading process manager {manager_def['name']}") self.manager = load_plugin('process_manager', manager_def) LOGGER.info('Process manager plugin loaded') @@ -685,35 +685,34 @@ class API: 'rel': request.get_linkrel(F_JSON), 'type': FORMAT_TYPES[F_JSON], 'title': 'This document as JSON', - 'href': '{}?f={}'.format(self.config['server']['url'], F_JSON) + 'href': f"{self.config['server']['url']}?f={F_JSON}" }, { 'rel': request.get_linkrel(F_JSONLD), 'type': FORMAT_TYPES[F_JSONLD], 'title': 'This document as RDF (JSON-LD)', - 'href': '{}?f={}'.format(self.config['server']['url'], F_JSONLD) + 'href': f"{self.config['server']['url']}?f={F_JSONLD}" }, { 'rel': request.get_linkrel(F_HTML), 'type': FORMAT_TYPES[F_HTML], 'title': 'This document as HTML', - 'href': '{}?f={}'.format(self.config['server']['url'], F_HTML), + 'href': f"{self.config['server']['url']}?f={F_HTML}", 'hreflang': self.default_locale }, { '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']) + 'href': f"{self.config['server']['url']}/openapi" }, { 'rel': 'service-doc', 'type': FORMAT_TYPES[F_HTML], 'title': 'The OpenAPI definition as HTML', - 'href': '{}/openapi?f={}'.format(self.config['server']['url'], - F_HTML), + 'href': f"{self.config['server']['url']}/openapi?f={F_HTML}", 'hreflang': self.default_locale }, { 'rel': 'conformance', 'type': FORMAT_TYPES[F_JSON], 'title': 'Conformance', - 'href': '{}/conformance'.format(self.config['server']['url']) + 'href': f"{self.config['server']['url']}/conformance" }, { 'rel': 'data', 'type': FORMAT_TYPES[F_JSON], @@ -723,12 +722,12 @@ class API: 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/processes', 'type': FORMAT_TYPES[F_JSON], 'title': 'Processes', - 'href': '{}/processes'.format(self.config['server']['url']) + 'href': f"{self.config['server']['url']}/processes" }, { 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/job-list', 'type': FORMAT_TYPES[F_JSON], 'title': 'Jobs', - 'href': '{}/jobs'.format(self.config['server']['url']) + 'href': f"{self.config['server']['url']}/jobs" }] headers = request.get_response_headers() @@ -871,7 +870,7 @@ class API: LOGGER.debug('Creating collections') for k, v in collections_dict.items(): if v.get('visibility', 'default') == 'hidden': - LOGGER.debug('Skipping hidden layer: {}'.format(k)) + LOGGER.debug(f'Skipping hidden layer: {k}') continue collection_data = get_provider_default(v['providers']) collection_data_type = collection_data['type'] @@ -932,34 +931,31 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': 'root', 'title': 'The landing page of this server as JSON', - 'href': '{}?f={}'.format(self.config['server']['url'], F_JSON) + 'href': f"{self.config['server']['url']}?f={F_JSON}" }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'root', 'title': 'The landing page of this server as HTML', - 'href': '{}?f={}'.format(self.config['server']['url'], F_HTML) + 'href': f"{self.config['server']['url']}?f={F_HTML}" }) collection['links'].append({ 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), 'title': 'This document as JSON', - 'href': '{}/{}?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}?f={F_JSON}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_JSONLD], 'rel': request.get_linkrel(F_JSONLD), 'title': 'This document as RDF (JSON-LD)', - 'href': '{}/{}?f={}'.format( - self.get_collections_url(), k, F_JSONLD) + 'href': f'{self.get_collections_url()}/{k}?f={F_JSONLD}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), 'title': 'This document as HTML', - 'href': '{}/{}?f={}'.format( - self.get_collections_url(), k, F_HTML) + 'href': f'{self.get_collections_url()}/{k}?f={F_HTML}' }) if collection_data_type in ['feature', 'record', 'tile']: @@ -970,36 +966,31 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': 'queryables', 'title': 'Queryables for this collection as JSON', - 'href': '{}/{}/queryables?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}/queryables?f={F_JSON}' # noqa }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'queryables', 'title': 'Queryables for this collection as HTML', - 'href': '{}/{}queryables?f={}'.format( - self.get_collections_url(), k, F_HTML) + 'href': f'{self.get_collections_url()}/{k}/queryables?f={F_HTML}' # noqa }) collection['links'].append({ 'type': 'application/geo+json', 'rel': 'items', 'title': 'items as GeoJSON', - 'href': '{}/{}/items?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}/items?f={F_JSON}' # noqa }) collection['links'].append({ 'type': FORMAT_TYPES[F_JSONLD], 'rel': 'items', 'title': 'items as RDF (GeoJSON-LD)', - 'href': '{}/{}/items?f={}'.format( - self.get_collections_url(), k, F_JSONLD) + 'href': f'{self.get_collections_url()}/{k}/items?f={F_JSONLD}' # noqa }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'items', 'title': 'Items as HTML', - 'href': '{}/{}/items?f={}'.format( - self.get_collections_url(), k, F_HTML) + 'href': f'{self.get_collections_url()}/{k}/items?f={F_HTML}' # noqa }) elif collection_data_type == 'coverage': @@ -1009,59 +1000,52 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': 'collection', 'title': 'Detailed Coverage metadata in JSON', - 'href': '{}/{}?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}?f={F_JSON}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'collection', 'title': 'Detailed Coverage metadata in HTML', - 'href': '{}/{}?f={}'.format( - self.get_collections_url(), k, F_HTML) + 'href': f'{self.get_collections_url()}/{k}?f={F_HTML}' }) - coverage_url = '{}/{}/coverage'.format( - self.get_collections_url(), k) + coverage_url = f'{self.get_collections_url()}/{k}/coverage' collection['links'].append({ 'type': FORMAT_TYPES[F_JSON], - 'rel': '{}/coverage-domainset'.format(OGC_RELTYPES_BASE), + 'rel': f'{OGC_RELTYPES_BASE}/coverage-domainset', 'title': 'Coverage domain set of collection in JSON', - 'href': '{}/domainset?f={}'.format(coverage_url, F_JSON) + 'href': f'{coverage_url}/domainset?f={F_JSON}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], - 'rel': '{}/coverage-domainset'.format(OGC_RELTYPES_BASE), + 'rel': f'{OGC_RELTYPES_BASE}/coverage-domainset', 'title': 'Coverage domain set of collection in HTML', - 'href': '{}/domainset?f={}'.format(coverage_url, F_HTML) + 'href': f'{coverage_url}/domainset?f={F_HTML}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_JSON], - 'rel': '{}/coverage-rangetype'.format(OGC_RELTYPES_BASE), + 'rel': f'{OGC_RELTYPES_BASE}/coverage-rangetype', 'title': 'Coverage range type of collection in JSON', - 'href': '{}/rangetype?f={}'.format(coverage_url, F_JSON) + 'href': f'{coverage_url}/rangetype?f={F_JSON}' }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], - 'rel': '{}/coverage-rangetype'.format(OGC_RELTYPES_BASE), + 'rel': f'{OGC_RELTYPES_BASE}/coverage-rangetype', 'title': 'Coverage range type of collection in HTML', - 'href': '{}/rangetype?f={}'.format(coverage_url, F_HTML) + 'href': f'{coverage_url}/rangetype?f={F_HTML}' }) collection['links'].append({ 'type': 'application/prs.coverage+json', - 'rel': '{}/coverage'.format(OGC_RELTYPES_BASE), + 'rel': f'{OGC_RELTYPES_BASE}/coverage', 'title': 'Coverage data', - 'href': '{}/{}/coverage?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}/coverage?f={F_JSON}' # noqa }) if collection_data_format is not None: collection['links'].append({ 'type': collection_data_format['mimetype'], - 'rel': '{}/coverage'.format(OGC_RELTYPES_BASE), - 'title': 'Coverage data as {}'.format( - collection_data_format['name']), - 'href': '{}/{}/coverage?f={}'.format( - self.get_collections_url(), k, - collection_data_format['name']) + 'rel': f'{OGC_RELTYPES_BASE}/coverage', + 'title': f"Coverage data as {collection_data_format['name']}", # noqa + 'href': f"{self.get_collections_url()}/{k}/coverage?f={collection_data_format['name']}" # noqa }) if dataset is not None: LOGGER.debug('Creating extended coverage metadata') @@ -1093,15 +1077,13 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': 'tiles', 'title': 'Tiles as JSON', - 'href': '{}/{}/tiles?f={}'.format( - self.get_collections_url(), k, F_JSON) + 'href': f'{self.get_collections_url()}/{k}/tiles?f={F_JSON}' # noqa }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'tiles', 'title': 'Tiles as HTML', - 'href': '{}/{}/tiles?f={}'.format( - self.get_collections_url(), k, F_HTML) + 'href': f'{self.get_collections_url()}/{k}/tiles?f={F_HTML}' # noqa }) try: @@ -1118,9 +1100,8 @@ class API: collection['links'].append({ 'type': map_mimetype, 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/map', - 'title': 'Map as {}'.format(map_format), - 'href': '{}/collections/{}/map?f={}'.format( - self.config['server']['url'], k, map_format) + 'title': f'Map as {map_format}', + 'href': f"{self.config['server']['url']}/collections/{k}/map?f={map_format}" # noqa }) try: @@ -1144,16 +1125,14 @@ class API: collection['links'].append({ 'type': 'application/json', 'rel': 'data', - 'title': '{} query for this collection as JSON'.format(qt), # noqa - 'href': '{}/{}/{}?f={}'.format( - self.get_collections_url(), k, qt, F_JSON) + 'title': f'{qt} query for this collection as JSON', + 'href': f'{self.get_collections_url()}/{k}/{qt}?f={F_JSON}' # noqa }) collection['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': 'data', - 'title': '{} query for this collection as HTML'.format(qt), # noqa - 'href': '{}/{}/{}?f={}'.format( - self.get_collections_url(), k, qt, F_HTML) + 'title': f'{qt} query for this collection as HTML', + 'href': f'{self.get_collections_url()}/{k}/{qt}?f={F_HTML}' # noqa }) except ProviderConnectionError: msg = 'connection error (check logs)' @@ -1174,19 +1153,19 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), 'title': 'This document as JSON', - 'href': '{}?f={}'.format(self.get_collections_url(), F_JSON) + 'href': f'{self.get_collections_url()}?f={F_JSON}' }) fcm['links'].append({ 'type': FORMAT_TYPES[F_JSONLD], 'rel': request.get_linkrel(F_JSONLD), 'title': 'This document as RDF (JSON-LD)', - 'href': '{}?f={}'.format(self.get_collections_url(), F_JSONLD) + 'href': f'{self.get_collections_url()}?f={F_JSONLD}' }) fcm['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), 'title': 'This document as HTML', - 'href': '{}?f={}'.format(self.get_collections_url(), F_HTML) + 'href': f'{self.get_collections_url()}?f={F_HTML}' }) if request.format == F_HTML: # render @@ -1265,8 +1244,7 @@ class API: self.config['resources'][dataset]['title'], request.locale), 'properties': {}, '$schema': 'http://json-schema.org/draft/2019-09/schema', - '$id': '{}/{}/queryables'.format( - self.get_collections_url(), dataset) + '$id': f'{self.get_collections_url()}/{dataset}/queryables' } if p.fields: @@ -1431,7 +1409,7 @@ class API: LOGGER.debug('processing property parameters') for k, v in request.params.items(): if k not in reserved_fieldnames and k in list(p.fields.keys()): - LOGGER.debug('Adding property filter {}={}'.format(k, v)) + LOGGER.debug(f'Adding property filter {k}={v}') properties.append((k, v)) LOGGER.debug('processing sort parameter') @@ -1504,19 +1482,19 @@ class API: prv_locale = l10n.get_plugin_locale(provider_def, request.raw_locale) LOGGER.debug('Querying provider') - LOGGER.debug('offset: {}'.format(offset)) - LOGGER.debug('limit: {}'.format(limit)) - LOGGER.debug('resulttype: {}'.format(resulttype)) - LOGGER.debug('sortby: {}'.format(sortby)) - LOGGER.debug('bbox: {}'.format(bbox)) - LOGGER.debug('datetime: {}'.format(datetime_)) - LOGGER.debug('properties: {}'.format(properties)) - LOGGER.debug('select properties: {}'.format(select_properties)) - LOGGER.debug('skipGeometry: {}'.format(skip_geometry)) - LOGGER.debug('language: {}'.format(prv_locale)) - LOGGER.debug('q: {}'.format(q)) - LOGGER.debug('cql_text: {}'.format(cql_text)) - LOGGER.debug('filter-lang: {}'.format(filter_lang)) + LOGGER.debug(f'offset: {offset}') + LOGGER.debug(f'limit: {limit}') + LOGGER.debug(f'resulttype: {resulttype}') + LOGGER.debug(f'sortby: {sortby}') + LOGGER.debug(f'bbox: {bbox}') + LOGGER.debug(f'datetime: {datetime_}') + LOGGER.debug(f'properties: {properties}') + LOGGER.debug(f'select properties: {select_properties}') + LOGGER.debug(f'skipGeometry: {skip_geometry}') + LOGGER.debug(f'language: {prv_locale}') + LOGGER.debug(f'q: {q}') + LOGGER.debug(f'cql_text: {cql_text}') + LOGGER.debug(f'filter-lang: {filter_lang}') try: content = p.query(offset=offset, limit=limit, @@ -1551,25 +1529,22 @@ class API: serialized_query_params += urllib.parse.quote(str(v), safe=',') # TODO: translate titles - uri = '{}/{}/items'.format(self.get_collections_url(), dataset) + uri = f'{self.get_collections_url()}/{dataset}/items' content['links'] = [{ 'type': 'application/geo+json', 'rel': request.get_linkrel(F_JSON), 'title': 'This document as GeoJSON', - 'href': '{}?f={}{}'.format( - uri, F_JSON, serialized_query_params) + 'href': f'{uri}?f={F_JSON}{serialized_query_params}' }, { 'rel': request.get_linkrel(F_JSONLD), 'type': FORMAT_TYPES[F_JSONLD], 'title': 'This document as RDF (JSON-LD)', - 'href': '{}?f={}{}'.format( - uri, F_JSONLD, serialized_query_params) + 'href': f'{uri}?f={F_JSONLD}{serialized_query_params}' }, { 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), 'title': 'This document as HTML', - 'href': '{}?f={}{}'.format( - uri, F_HTML, serialized_query_params) + 'href': f'{uri}?f={F_HTML}{serialized_query_params}' }] if offset > 0: @@ -1579,9 +1554,7 @@ class API: 'type': 'application/geo+json', 'rel': 'prev', 'title': 'items (prev)', - 'href': '{}?offset={}{}' - .format( - uri, prev, serialized_query_params) + 'href': f'{uri}?offset={prev}{serialized_query_params}' }) if len(content['features']) == limit: @@ -1591,9 +1564,7 @@ class API: 'type': 'application/geo+json', 'rel': 'next', 'title': 'items (next)', - 'href': '{}?offset={}{}' - .format( - uri, next_, serialized_query_params) + 'href': f'{uri}?offset={next_}{serialized_query_params}' }) content['links'].append( @@ -1653,15 +1624,14 @@ class API: return self.get_exception( 500, headers, request.format, 'NoApplicableCode', msg) - headers['Content-Type'] = '{}; charset={}'.format( - formatter.mimetype, self.config['server']['encoding']) + headers['Content-Type'] = f"{formatter.mimetype}; charset={self.config['server']['encoding']}" # noqa if p.filename is None: - filename = '{}.csv'.format(dataset) + filename = f'{dataset}.csv' else: - filename = '{}'.format(p.filename) + filename = f'{p.filename}' - cd = 'attachment; filename="{}"'.format(filename) + cd = f'attachment; filename="{filename}"' headers['Content-Disposition'] = cd return headers, 200, content @@ -1802,11 +1772,11 @@ class API: LOGGER.debug('processing property parameters') for k, v in request.params.items(): if k not in reserved_fieldnames and k not in p.fields.keys(): - msg = 'unknown query parameter: {}'.format(k) + msg = f'unknown query parameter: {k}' return self.get_exception( 400, headers, request.format, 'InvalidParameterValue', msg) elif k not in reserved_fieldnames and k in p.fields.keys(): - LOGGER.debug('Add property filter {}={}'.format(k, v)) + LOGGER.debug(f'Add property filter {k}={v}') properties.append((k, v)) LOGGER.debug('processing sort parameter') @@ -1862,16 +1832,16 @@ class API: 400, headers, request.format, 'InvalidParameterValue', msg) LOGGER.debug('Querying provider') - LOGGER.debug('offset: {}'.format(offset)) - LOGGER.debug('limit: {}'.format(limit)) - LOGGER.debug('resulttype: {}'.format(resulttype)) - LOGGER.debug('sortby: {}'.format(sortby)) - LOGGER.debug('bbox: {}'.format(bbox)) - LOGGER.debug('datetime: {}'.format(datetime_)) - LOGGER.debug('properties: {}'.format(select_properties)) - LOGGER.debug('skipGeometry: {}'.format(skip_geometry)) - LOGGER.debug('q: {}'.format(q)) - LOGGER.debug('filter-lang: {}'.format(filter_lang)) + LOGGER.debug(f'offset: {offset}') + LOGGER.debug(f'limit: {limit}') + LOGGER.debug(f'resulttype: {resulttype}') + LOGGER.debug(f'sortby: {sortby}') + LOGGER.debug(f'bbox: {bbox}') + LOGGER.debug(f'datetime: {datetime_}') + LOGGER.debug(f'properties: {select_properties}') + LOGGER.debug(f'skipGeometry: {skip_geometry}') + LOGGER.debug(f'q: {q}') + LOGGER.debug(f'filter-lang: {filter_lang}') LOGGER.debug('Processing headers') @@ -2017,8 +1987,7 @@ class API: return self.get_exception( 400, headers, request.format, 'InvalidParameterValue', msg) - headers['Location'] = '{}/{}/items/{}'.format( - self.get_collections_url(), dataset, identifier) + headers['Location'] = f'{self.get_collections_url()}/{dataset}/items/{identifier}' # noqa return headers, 201, '' @@ -2095,7 +2064,7 @@ class API: prv_locale = l10n.get_plugin_locale(provider_def, request.raw_locale) try: - LOGGER.debug('Fetching id {}'.format(identifier)) + LOGGER.debug(f'Fetching id {identifier}') content = p.get(identifier, language=prv_locale) except ProviderConnectionError as err: LOGGER.error(err) @@ -2123,8 +2092,7 @@ class API: 'NotFound', msg) uri = content['properties'].get(p.uri_field) if p.uri_field else \ - '{}/{}/items/{}'.format( - self.get_collections_url(), dataset, identifier) + f'{self.get_collections_url()}/{dataset}/items/{identifier}' if 'links' not in content: content['links'] = [] @@ -2133,51 +2101,46 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': 'root', 'title': 'The landing page of this server as JSON', - 'href': '{}?f={}'.format(self.config['server']['url'], F_JSON) + 'href': f"{self.config['server']['url']}?f={F_JSON}" }, { 'type': FORMAT_TYPES[F_HTML], 'rel': 'root', 'title': 'The landing page of this server as HTML', - 'href': '{}?f={}'.format(self.config['server']['url'], F_HTML) + 'href': f"{self.config['server']['url']}?f={F_HTML}" }, { 'rel': request.get_linkrel(F_JSON), 'type': 'application/geo+json', 'title': 'This document as GeoJSON', - 'href': '{}?f={}'.format(uri, F_JSON) + 'href': f'{uri}?f={F_JSON}' }, { 'rel': request.get_linkrel(F_JSONLD), 'type': FORMAT_TYPES[F_JSONLD], 'title': 'This document as RDF (JSON-LD)', - 'href': '{}?f={}'.format(uri, F_JSONLD) + 'href': f'{uri}?f={F_JSONLD}' }, { 'rel': request.get_linkrel(F_HTML), 'type': FORMAT_TYPES[F_HTML], 'title': 'This document as HTML', - 'href': '{}?f={}'.format(uri, F_HTML) + 'href': f'{uri}?f={F_HTML}' }, { 'rel': 'collection', 'type': FORMAT_TYPES[F_JSON], 'title': l10n.translate(collections[dataset]['title'], request.locale), - 'href': '{}/{}'.format( - self.get_collections_url(), dataset) + 'href': f'{self.get_collections_url()}/{dataset}' }]) if 'prev' in content: content['links'].append({ 'rel': 'prev', 'type': FORMAT_TYPES[request.format], - 'href': '{}/{}/items/{}?f={}'.format( - self.get_collections_url(), dataset, - content['prev'], request.format) + 'href': f"{self.get_collections_url()}/{dataset}/items/{content['prev']}?f={request.format}" # noqa }) if 'next' in content: content['links'].append({ 'rel': 'next', 'type': FORMAT_TYPES[request.format], - 'href': '{}/{}/items/{}?f={}'.format( - self.get_collections_url(), dataset, - content['next'], request.format) + 'href': f"{self.get_collections_url()}/{dataset}/items/{content['next']}?f={request.format}" # noqa }) # Set response language to requested provider locale @@ -2271,7 +2234,7 @@ class API: LOGGER.debug('Processing datetime parameter') - datetime_ = request.params.get('datetime', None) + datetime_ = request.params.get('datetime') try: datetime_ = validate_datetime( @@ -2292,7 +2255,7 @@ class API: LOGGER.debug('Processing properties parameter') query_args['properties'] = [rs for rs in properties.split(',') if rs] - LOGGER.debug('Fields: {}'.format(query_args['properties'])) + LOGGER.debug(f"Fields: {query_args['properties']}") for a in query_args['properties']: if a not in p.fields: @@ -2305,7 +2268,7 @@ class API: try: subsets = validate_subset(request.params['subset'] or '') except (AttributeError, ValueError) as err: - msg = 'Invalid subset: {}'.format(err) + msg = f'Invalid subset: {err}' LOGGER.error(msg) return self.get_exception( 400, headers, format_, 'InvalidParameterValue', msg) @@ -2317,13 +2280,13 @@ class API: 400, headers, format_, 'InvalidParameterValue', msg) query_args['subsets'] = subsets - LOGGER.debug('Subsets: {}'.format(query_args['subsets'])) + LOGGER.debug(f"Subsets: {query_args['subsets']}") LOGGER.debug('Querying coverage') try: data = p.query(**query_args) except ProviderInvalidQueryError as err: - msg = 'query error: {}'.format(err) + msg = f'query error: {err}' return self.get_exception( 400, headers, format_, 'InvalidParameterValue', msg) except ProviderNoDataError: @@ -2338,7 +2301,7 @@ class API: mt = collection_def['format']['name'] if format_ == mt: # native format if p.filename is not None: - cd = 'attachment; filename="{}"'.format(p.filename) + cd = f'attachment; filename="{p.filename}"' headers['Content-Disposition'] = cd headers['Content-Type'] = collection_def['format']['mimetype'] @@ -2511,29 +2474,25 @@ class API: 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), 'title': 'This document as JSON', - 'href': '{}/{}/tiles?f={}'.format( - self.get_collections_url(), dataset, F_JSON) + 'href': f'{self.get_collections_url()}/{dataset}/tiles?f={F_JSON}' }) tiles['links'].append({ 'type': FORMAT_TYPES[F_JSONLD], 'rel': request.get_linkrel(F_JSONLD), 'title': 'This document as RDF (JSON-LD)', - 'href': '{}/{}/tiles?f={}'.format( - self.get_collections_url(), dataset, F_JSONLD) + 'href': f'{self.get_collections_url()}/{dataset}/tiles?f={F_JSONLD}' # noqa }) tiles['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), 'title': 'This document as HTML', - 'href': '{}/{}/tiles?f={}'.format( - self.get_collections_url(), dataset, F_HTML) + 'href': f'{self.get_collections_url()}/{dataset}/tiles?f={F_HTML}' }) tile_services = p.get_tiles_service( baseurl=self.config['server']['url'], - servicepath='{}/{}/tiles/{{{}}}/{{{}}}/{{{}}}/{{{}}}?f=mvt' - .format(self.get_collections_url(), dataset, 'tileMatrixSetId', - 'tileMatrix', 'tileRow', 'tileCol')) + servicepath='{self.get_collections_url()}/{dataset}/tiles/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}?f=mvt' # noqa + ) for service in tile_services['links']: tiles['links'].append(service) @@ -2551,20 +2510,14 @@ class API: tile_matrix['links'].append({ 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), - 'title': '{} - {} - {}'.format( - dataset, matrix.tileMatrixSet, F_JSON), - 'href': '{}/{}/tiles/{}?f={}'.format( - self.get_collections_url(), dataset, - matrix.tileMatrixSet, F_JSON) + 'title': f'{dataset} - {matrix.tileMatrixSet} - {F_JSON}', + 'href': f'{self.get_collections_url()}/{dataset}/tiles/{matrix.tileMatrixSet}?f={F_JSON}' # noqa }) tile_matrix['links'].append({ 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), - 'title': '{} - {} - {}'.format( - dataset, matrix.tileMatrixSet, F_HTML), - 'href': '{}/{}/tiles/{}?f={}'.format( - self.get_collections_url(), dataset, - matrix.tileMatrixSet, F_HTML) + 'title': f'{dataset} - {matrix.tileMatrixSet} - {F_HTML}', + 'href': f'{self.get_collections_url()}/{dataset}/tiles/{matrix.tileMatrixSet}?f={F_HTML}' # noqa }) tiles['tilesets'].append(tile_matrix) @@ -2635,8 +2588,7 @@ class API: format_ = p.format_type headers['Content-Type'] = format_ - LOGGER.debug('Fetching tileset id {} and tile {}/{}/{}'.format( - matrix_id, z_idx, y_idx, x_idx)) + LOGGER.debug(f'Fetching tileset id {matrix_id} and tile {z_idx}/{y_idx}/{x_idx}') # noqa content = p.get_tiles(layer=p.get_layer(), tileset=matrix_id, z=z_idx, y=y_idx, x=x_idx, format_=format_) if content is None: @@ -2886,7 +2838,7 @@ class API: except ProviderInvalidQueryError as err: exception = { 'code': 'NoApplicableCode', - 'description': 'query error: {}'.format(err), + 'description': f'query error: {err}' } LOGGER.error(exception) headers['Content-type'] = 'application/json' @@ -2977,7 +2929,7 @@ class API: except ProviderInvalidQueryError as err: exception = { 'code': 'NoApplicableCode', - 'description': 'query error: {}'.format(err), + 'description': f'query error: {err}' } LOGGER.error(exception) return headers, 400, to_json(exception, self.pretty_print) @@ -3064,15 +3016,14 @@ class API: p2['outputTransmission'] = ['value'] p2['links'] = p2.get('links', []) - jobs_url = '{}/jobs'.format(self.config['server']['url']) - process_url = '{}/processes/{}'.format( - self.config['server']['url'], key) + jobs_url = f"{self.config['server']['url']}/jobs" + process_url = f"{self.config['server']['url']}/processes/{key}" # TODO translation support link = { 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), - 'href': '{}?f={}'.format(process_url, F_JSON), + 'href': f'{process_url}?f={F_JSON}', 'title': 'Process description as JSON', 'hreflang': self.default_locale } @@ -3081,7 +3032,7 @@ class API: link = { 'type': FORMAT_TYPES[F_HTML], 'rel': request.get_linkrel(F_HTML), - 'href': '{}?f={}'.format(process_url, F_HTML), + 'href': f'{process_url}?f={F_HTML}', 'title': 'Process description as HTML', 'hreflang': self.default_locale } @@ -3090,7 +3041,7 @@ class API: link = { 'type': FORMAT_TYPES[F_HTML], 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/job-list', - 'href': '{}?f={}'.format(jobs_url, F_HTML), + 'href': f'{jobs_url}?f={F_HTML}', 'title': 'jobs for this process as HTML', 'hreflang': self.default_locale } @@ -3099,7 +3050,7 @@ class API: link = { 'type': FORMAT_TYPES[F_JSON], 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/job-list', - 'href': '{}?f={}'.format(jobs_url, F_JSON), + 'href': f'{jobs_url}?f={F_JSON}', 'title': 'jobs for this process as JSON', 'hreflang': self.default_locale } @@ -3108,7 +3059,7 @@ class API: link = { 'type': FORMAT_TYPES[F_JSON], 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/execute', - 'href': '{}/execution?f={}'.format(process_url, F_JSON), + 'href': f'{process_url}/execution?f={F_JSON}', 'title': 'Execution for this process as JSON', 'hreflang': self.default_locale } @@ -3168,12 +3119,12 @@ class API: serialized_jobs = { 'jobs': [], 'links': [{ - 'href': '{}/jobs?f={}'.format(self.config['server']['url'], F_HTML), # noqa + 'href': f"{self.config['server']['url']}/jobs?f={F_HTML}", 'rel': request.get_linkrel(F_HTML), 'type': FORMAT_TYPES[F_HTML], 'title': 'Jobs list as HTML' }, { - 'href': '{}/jobs?f={}'.format(self.config['server']['url'], F_JSON), # noqa + 'href': f"{self.config['server']['url']}/jobs?f={F_JSON}", 'rel': request.get_linkrel(F_JSON), 'type': FORMAT_TYPES[F_JSON], 'title': 'Jobs list as JSON' @@ -3195,19 +3146,18 @@ class API: if JobStatus[job_['status']] in ( JobStatus.successful, JobStatus.running, JobStatus.accepted): - job_result_url = '{}/jobs/{}/results'.format( - self.config['server']['url'], job_['identifier']) + job_result_url = f"{ self.config['server']['url']}/jobs/{job_['identifier']}/results" # noqa job2['links'] = [{ - 'href': '{}?f={}'.format(job_result_url, F_HTML), + 'href': f'{job_result_url}?f={F_HTML}', 'rel': 'about', 'type': FORMAT_TYPES[F_HTML], - 'title': 'results of job {} as HTML'.format(job_id) + 'title': f'results of job {job_id} as HTML' }, { - 'href': '{}?f={}'.format(job_result_url, F_JSON), + 'href': f'{job_result_url}?f={F_JSON}', 'rel': 'about', 'type': FORMAT_TYPES[F_JSON], - 'title': 'results of job {} as JSON'.format(job_id) + 'title': f'results of job {job_id} as JSON' }] if job_['mimetype'] not in (FORMAT_TYPES[F_JSON], @@ -3216,8 +3166,7 @@ class API: 'href': job_result_url, 'rel': 'about', 'type': job_['mimetype'], - 'title': 'results of job {} as {}'.format( - job_id, job_['mimetype']) + 'title': f"results of job {job_id} as {job_['mimetype']}" # noqa }) serialized_jobs['jobs'].append(job2) @@ -3302,8 +3251,7 @@ class API: LOGGER.debug(data_dict) job_id = data.get("job_id", str(uuid.uuid1())) - url = '{}/jobs/{}'.format( - self.config['server']['url'], job_id) + url = f"{self.config['server']['url']}/jobs/{job_id}" headers['Location'] = url @@ -3430,7 +3378,7 @@ class API: } else: http_status = 200 - jobs_url = '{}/jobs'.format(self.config['server']['url']) + jobs_url = f"{self.config['server']['url']}/jobs" response = { 'jobID': job_id, @@ -3495,7 +3443,7 @@ class API: parameternames = parameternames.split(',') LOGGER.debug('Processing coords parameter') - wkt = request.params.get('coords', None) + wkt = request.params.get('coords') if not wkt: msg = 'missing coords parameter' @@ -3615,12 +3563,12 @@ class API: for key, value in stac_collections.items(): content['links'].append({ 'rel': 'child', - 'href': '{}/{}?f={}'.format(stac_url, key, F_JSON), + 'href': f'{stac_url}/{key}?f={F_JSON}', 'type': FORMAT_TYPES[F_JSON] }) content['links'].append({ 'rel': 'child', - 'href': '{}/{}'.format(stac_url, key), + 'href': f'{stac_url}/{key}', 'type': FORMAT_TYPES[F_HTML] }) @@ -3649,7 +3597,7 @@ class API: headers = request.get_response_headers() dataset = None - LOGGER.debug('Path: {}'.format(path)) + LOGGER.debug(f'Path: {path}') dir_tokens = path.split('/') if dir_tokens: dataset = dir_tokens[0] @@ -3672,7 +3620,7 @@ class API: return self.get_exception( 500, headers, request.format, 'NoApplicableCode', msg) - id_ = '{}-stac'.format(dataset) + id_ = f'{dataset}-stac' stac_version = '1.0.0-rc.2' content = { @@ -3768,7 +3716,7 @@ class API: 400, headers, request.format, 'InvalidParameterValue', msg) def get_collections_url(self): - return '{}/collections'.format(self.config['server']['url']) + return f"{self.config['server']['url']}/collections" def validate_bbox(value=None) -> list: @@ -3909,7 +3857,7 @@ def validate_subset(value: str) -> dict: subsets = {} for s in value.split(','): - LOGGER.debug('Processing subset {}'.format(s)) + LOGGER.debug(f'Processing subset {s}') m = re.search(r'(.*)\((.*)\)', s) subset_name, values = m.group(1, 2) diff --git a/pygeoapi/config.py b/pygeoapi/config.py index f9fe0d3..d6eb757 100644 --- a/pygeoapi/config.py +++ b/pygeoapi/config.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -31,15 +31,16 @@ import click import json from jsonschema import validate as jsonschema_validate import logging -import os +from pathlib import Path from pygeoapi.util import to_json, yaml_load LOGGER = logging.getLogger(__name__) -THISDIR = os.path.dirname(os.path.realpath(__file__)) + +THISDIR = Path(__file__).parent.resolve() -def validate_config(instance_dict): +def validate_config(instance_dict: dict) -> bool: """ Validate pygeoapi configuration against pygeoapi schema @@ -48,10 +49,9 @@ def validate_config(instance_dict): :returns: `bool` of validation """ - schema_file = os.path.join(THISDIR, 'schemas', 'config', - 'pygeoapi-config-0.x.yml') + schema_file = THISDIR / 'schemas' / 'config' / 'pygeoapi-config-0.x.yml' - with open(schema_file) as fh2: + with schema_file.open() as fh2: schema_dict = yaml_load(fh2) jsonschema_validate(json.loads(to_json(instance_dict)), schema_dict) @@ -74,7 +74,7 @@ def validate(ctx, config_file): raise click.ClickException('--config/-c required') with open(config_file) as ff: - click.echo('Validating {}'.format(config_file)) + click.echo(f'Validating {config_file}') instance = yaml_load(ff) validate_config(instance) click.echo('Valid configuration') diff --git a/pygeoapi/flask_app.py b/pygeoapi/flask_app.py index 75f10f5..cae0152 100644 --- a/pygeoapi/flask_app.py +++ b/pygeoapi/flask_app.py @@ -67,7 +67,7 @@ APP.config['JSONIFY_PRETTYPRINT_REGULAR'] = CONFIG['server'].get( api_ = API(CONFIG) -OGC_SCHEMAS_LOCATION = CONFIG['server'].get('ogc_schemas_location', None) +OGC_SCHEMAS_LOCATION = CONFIG['server'].get('ogc_schemas_location') if (OGC_SCHEMAS_LOCATION is not None and not OGC_SCHEMAS_LOCATION.startswith('http')): diff --git a/pygeoapi/formatter/base.py b/pygeoapi/formatter/base.py index eb27464..808d0f5 100644 --- a/pygeoapi/formatter/base.py +++ b/pygeoapi/formatter/base.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -35,7 +35,7 @@ LOGGER = logging.getLogger(__name__) class BaseFormatter: """generic Formatter ABC""" - def __init__(self, formatter_def): + def __init__(self, formatter_def: dict): """ Initialize object @@ -51,7 +51,7 @@ class BaseFormatter: if 'geom' in formatter_def: self.geom = formatter_def['geom'] - def write(self, options={}, data=None): + def write(self, options: dict = {}, data: dict = None) -> str: """ Generate data in specified format @@ -64,7 +64,7 @@ class BaseFormatter: raise NotImplementedError() def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' class FormatterGenericError(Exception): diff --git a/pygeoapi/formatter/csv_.py b/pygeoapi/formatter/csv_.py index 18176b8..1e29cc3 100644 --- a/pygeoapi/formatter/csv_.py +++ b/pygeoapi/formatter/csv_.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2018 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -40,7 +40,7 @@ LOGGER = logging.getLogger(__name__) class CSVFormatter(BaseFormatter): """CSV formatter""" - def __init__(self, formatter_def): + def __init__(self, formatter_def: dict): """ Initialize object @@ -56,7 +56,7 @@ class CSVFormatter(BaseFormatter): super().__init__({'name': 'csv', 'geom': geom}) self.mimetype = 'text/csv' - def write(self, options={}, data=None): + def write(self, options: dict = {}, data: dict = None) -> str: """ Generate data in CSV format @@ -83,7 +83,7 @@ class CSVFormatter(BaseFormatter): # TODO: implement wkt geometry serialization LOGGER.debug('not a point geometry, skipping') - LOGGER.debug('CSV fields: {}'.format(fields)) + LOGGER.debug(f'CSV fields: {fields}') try: output = io.BytesIO() @@ -104,4 +104,4 @@ class CSVFormatter(BaseFormatter): return output.getvalue() def __repr__(self): - return ' {}'.format(self.mimetype) + return f' {self.name}' diff --git a/pygeoapi/linked_data.py b/pygeoapi/linked_data.py index 45370d1..93c3b94 100644 --- a/pygeoapi/linked_data.py +++ b/pygeoapi/linked_data.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -26,11 +26,13 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= + """ Linked data capabilities Returns content as linked data representations """ import logging +from typing import Callable from pygeoapi.util import is_url from pygeoapi import l10n @@ -40,23 +42,23 @@ from shapely.ops import unary_union LOGGER = logging.getLogger(__name__) -def jsonldify(func): +def jsonldify(func: Callable) -> Callable: """ - Decorator that transforms app configuration\ - to include a JSON-LD representation + Decorator that transforms app configuration\ + to include a JSON-LD representation - :param func: decorated function + :param func: decorated function - :returns: `func` + :returns: `func` """ def inner(*args, **kwargs): apireq = args[1] - format_ = getattr(apireq, 'format', None) + format_ = getattr(apireq, 'format') if not format_ == 'jsonld': return func(*args, **kwargs) # Function args have been pre-processed, so get locale from APIRequest - locale_ = getattr(apireq, 'locale', None) + locale_ = getattr(apireq, 'locale') LOGGER.debug('Creating JSON-LD representation') cls = args[0] cfg = cls.config @@ -67,43 +69,43 @@ def jsonldify(func): fcmld = { "@context": "https://schema.org/docs/jsonldcontext.jsonld", "@type": "DataCatalog", - "@id": cfg.get('server', {}).get('url', None), - "url": cfg.get('server', {}).get('url', None), - "name": l10n.translate(ident.get('title', None), locale_), + "@id": cfg.get('server', {}).get('url'), + "url": cfg.get('server', {}).get('url'), + "name": l10n.translate(ident.get('title'), locale_), "description": l10n.translate( - ident.get('description', None), locale_), + ident.get('description'), locale_), "keywords": l10n.translate( - ident.get('keywords', None), locale_), + ident.get('keywords'), locale_), "termsOfService": l10n.translate( - ident.get('terms_of_service', None), locale_), - "license": meta.get('license', {}).get('url', None), + ident.get('terms_of_service'), locale_), + "license": meta.get('license', {}).get('url'), "provider": { "@type": "Organization", - "name": l10n.translate(provider.get('name', None), locale_), - "url": provider.get('url', None), + "name": l10n.translate(provider.get('name'), locale_), + "url": provider.get('url'), "address": { "@type": "PostalAddress", - "streetAddress": contact.get('address', None), - "postalCode": contact.get('postalcode', None), - "addressLocality": contact.get('city', None), - "addressRegion": contact.get('stateorprovince', None), - "addressCountry": contact.get('country', None) + "streetAddress": contact.get('address'), + "postalCode": contact.get('postalcode'), + "addressLocality": contact.get('city'), + "addressRegion": contact.get('stateorprovince'), + "addressCountry": contact.get('country') }, "contactPoint": { "@type": "Contactpoint", - "email": contact.get('email', None), - "telephone": contact.get('phone', None), - "faxNumber": contact.get('fax', None), - "url": contact.get('url', None), + "email": contact.get('email'), + "telephone": contact.get('phone'), + "faxNumber": contact.get('fax'), + "url": contact.get('url'), "hoursAvailable": { - "opens": contact.get('hours', None), + "opens": contact.get('hours'), "description": l10n.translate( - contact.get('instructions', None), locale_) + contact.get('instructions'), locale_) }, "contactType": l10n.translate( - contact.get('role', None), locale_), + contact.get('role'), locale_), "description": l10n.translate( - contact.get('position', None), locale_) + contact.get('position'), locale_) } } } @@ -112,46 +114,43 @@ def jsonldify(func): return inner -def jsonldify_collection(cls, collection, locale_): +def jsonldify_collection(cls, collection: dict, locale_: str) -> dict: """ - Transforms collection into a JSON-LD representation + Transforms collection into a JSON-LD representation - :param cls: API object - :param collection: `collection` as prepared for non-LD JSON - representation - :param locale_: The locale to use for translations (if supported) + :param cls: API object + :param collection: `collection` as prepared for non-LD JSON + representation + :param locale_: The locale to use for translations (if supported) - :returns: `collection` a dictionary, mapped into JSON-LD, of - type schema:Dataset + :returns: `collection` a dictionary, mapped into JSON-LD, of + type schema:Dataset """ temporal_extent = collection.get('extent', {}).get('temporal', {}) - interval = temporal_extent.get('interval', [[None, None]]) + interval = temporal_extent.get('interval') + if interval is not None: + interval = f'{interval[0][0]}/{interval[0][1]}' spatial_extent = collection.get('extent', {}).get('spatial', {}) - bbox = spatial_extent.get('bbox', None) - crs = spatial_extent.get('crs', None) + bbox = spatial_extent.get('bbox') + crs = spatial_extent.get('crs') hascrs84 = crs.endswith('CRS84') dataset = { "@type": "Dataset", - "@id": "{}/collections/{}".format( - cls.config['server']['url'], - collection['id'] - ), + "@id": f"{cls.config['server']['url']}/collections/{collection['id']}", "name": l10n.translate(collection['title'], locale_), "description": l10n.translate(collection['description'], locale_), "license": cls.fcmld['license'], - "keywords": l10n.translate(collection.get('keywords', None), locale_), + "keywords": l10n.translate(collection.get('keywords'), locale_), "spatial": None if (not hascrs84 or not bbox) else [{ "@type": "Place", "geo": { "@type": "GeoShape", - "box": '{},{} {},{}'.format(*_bbox[0:2], *_bbox[2:4]) + "box": f'{_bbox[0]},{_bbox[1]} {_bbox[2]},{_bbox[3]}' } } for _bbox in bbox], - "temporalCoverage": None if not interval else "{}/{}".format( - *interval[0] - ) + "temporalCoverage": interval } dataset['url'] = dataset['@id'] @@ -173,20 +172,22 @@ def jsonldify_collection(cls, collection, locale_): return dataset -def geojson2jsonld(config, data, dataset, identifier=None, id_field='id'): +def geojson2jsonld(config: dict, data: dict, dataset: str, + identifier: str = None, id_field: str = 'id') -> str: """ - Render GeoJSON-LD from a GeoJSON base. Inserts a @context that can be - read from, and extended by, the pygeoapi configuration for a particular - dataset. + Render GeoJSON-LD from a GeoJSON base. Inserts a @context that can be + read from, and extended by, the pygeoapi configuration for a particular + dataset. - :param config: dict of configuration - :param data: dict of data: - :param dataset: dataset identifier - :param identifier: item identifier (optional) - :param id_field: item identifier_field (optional) + :param config: dict of configuration + :param data: dict of data: + :param dataset: dataset identifier + :param identifier: item identifier (optional) + :param id_field: item identifier_field (optional) - :returns: string of rendered JSON (GeoJSON-LD) + :returns: string of rendered JSON (GeoJSON-LD) """ + context = config['resources'][dataset].get('context', []).copy() defaultVocabulary = { 'schema': 'https://schema.org/', @@ -215,17 +216,14 @@ def geojson2jsonld(config, data, dataset, identifier=None, id_field='id'): 'FeatureCollection': 'schema:itemList' }) - data['@id'] = '{}/collections/{}/items/'.format( - config['server']['url'], dataset - ) + data['@id'] = f"{config['server']['url']}/collections/{dataset}" for i, feature in enumerate(data['features']): # Get URI for each feature identifier = feature.get(id_field, feature['properties'].get(id_field, '')) if not is_url(str(identifier)): - identifier = '{}/collections/{}/items/{}'.format( - config['server']['url'], dataset, feature['id']) + identifier = f"config['server']['url']/collections/{dataset}/items/{feature['id']}" # noqa data['features'][i] = { id_field: identifier, @@ -245,14 +243,14 @@ def geojson2jsonld(config, data, dataset, identifier=None, id_field='id'): return ldjsonData -def jsonldify_geometry(feature): +def jsonldify_geometry(feature: dict) -> None: """ - Render JSON-LD for feature with GeoJSON, Geosparql/WKT, and - schema geometry encodings. + Render JSON-LD for feature with GeoJSON, Geosparql/WKT, and + schema geometry encodings. - :param feature: feature body to with GeoJSON geometry + :param feature: feature body to with GeoJSON geometry - :returns: None + :returns: None """ geo = feature.get('geometry') @@ -274,13 +272,13 @@ def jsonldify_geometry(feature): feature['schema:geo'] = geom2schemageo(geom) -def geom2schemageo(geom): +def geom2schemageo(geom: shape) -> dict: """ - Render Schema Geometry from a GeoJSON base. + Render Schema Geometry from a GeoJSON base. - :param geom: shapely geom of feature + :param geom: shapely geom of feature - :returns: dict of rendered schema:geo geometry + :returns: dict of rendered schema:geo geometry """ f = {'@type': 'schema:GeoShape'} if geom.geom_type == 'Point': @@ -313,9 +311,9 @@ def geom2schemageo(geom): # MultiPolygon to Polygon (buffer of 0 helps ensure manifold polygon) poly = unary_union(geom.buffer(0)) if poly.geom_type.startswith('Multi') or not poly.is_valid: - LOGGER.debug('Invalid Poly: {}'.format(poly.geom_type)) + LOGGER.debug(f'Invalid MultiPolygon: {poly.geom_type}') poly = poly.convex_hull - LOGGER.debug('New Poly: {}'.format(poly.geom_type)) + LOGGER.debug(f'New MultiPolygon: {poly.geom_type}') poly_geom = poly.exterior.coords[:] else: diff --git a/pygeoapi/openapi.py b/pygeoapi/openapi.py index dc03873..dd03207 100644 --- a/pygeoapi/openapi.py +++ b/pygeoapi/openapi.py @@ -61,7 +61,7 @@ THISDIR = os.path.dirname(os.path.realpath(__file__)) def get_ogc_schemas_location(server_config): - osl = server_config.get('ogc_schemas_location', None) + osl = server_config.get('ogc_schemas_location') value = 'https://schemas.opengis.net' @@ -86,7 +86,7 @@ def gen_media_type_object(media_type, api_type, path): :returns: `dict` of media type object """ - ref = '{}/{}'.format(OPENAPI_YAML[api_type], path) + ref = f'{OPENAPI_YAML[api_type]}/{path}' content = { media_type: { @@ -177,9 +177,9 @@ def get_oas_30(cfg): {'$ref': '#/components/parameters/lang'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/LandingPage'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/LandingPage"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -209,7 +209,7 @@ def get_oas_30(cfg): ], 'responses': { '200': {'$ref': '#/components/responses/200'}, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa 'default': {'$ref': '#/components/responses/default'} } } @@ -226,9 +226,9 @@ def get_oas_30(cfg): {'$ref': '#/components/parameters/lang'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/ConformanceDeclaration'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/LandingPage"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -244,9 +244,9 @@ def get_oas_30(cfg): {'$ref': '#/components/parameters/lang'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/Collections'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/LandingPage"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -434,12 +434,12 @@ def get_oas_30(cfg): for k, v in collections.items(): if v.get('visibility', 'default') == 'hidden': - LOGGER.debug('Skipping hidden layer: {}'.format(k)) + LOGGER.debug(f'Skipping hidden layer: {k}') continue name = l10n.translate(k, locale_) title = l10n.translate(v['title'], locale_) desc = l10n.translate(v['description'], locale_) - collection_name_path = '/collections/{}'.format(k) + collection_name_path = f'/collections/{k}' tag = { 'name': name, 'description': desc, @@ -457,19 +457,19 @@ def get_oas_30(cfg): paths[collection_name_path] = { 'get': { - 'summary': 'Get {} metadata'.format(title), + 'summary': f'Get {title} metadata', 'description': desc, 'tags': [name], - 'operationId': 'describe{}Collection'.format(name.capitalize()), # noqa + 'operationId': f'describe{name.capitalize()}Collection', 'parameters': [ {'$ref': '#/components/parameters/f'}, {'$ref': '#/components/parameters/lang'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/Collection'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/Collection"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -489,7 +489,7 @@ def get_oas_30(cfg): p = load_plugin('provider', get_provider_by_type( collections[k]['providers'], ptype)) - items_path = '{}/items'.format(collection_name_path) + items_path = f'{collection_name_path}/items' coll_properties = deepcopy(oas['components']['parameters']['properties']) # noqa @@ -497,26 +497,26 @@ def get_oas_30(cfg): paths[items_path] = { 'get': { - 'summary': 'Get {} items'.format(title), # noqa + 'summary': f'Get {title} items', 'description': desc, 'tags': [name], - 'operationId': 'get{}Features'.format(name.capitalize()), + 'operationId': f'get{name.capitalize()}Features', 'parameters': [ items_f, items_l, - {'$ref': '{}#/components/parameters/bbox'.format(OPENAPI_YAML['oapif'])}, # noqa - {'$ref': '{}#/components/parameters/limit'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/bbox"}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/limit"}, # noqa coll_properties, {'$ref': '#/components/parameters/vendorSpecificParameters'}, # noqa {'$ref': '#/components/parameters/skipGeometry'}, - {'$ref': '{}/parameters/sortby.yaml'.format(OPENAPI_YAML['oapir'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapir']}/parameters/sortby.yaml"}, # noqa {'$ref': '#/components/parameters/offset'}, ], 'responses': { - '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/Features"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -525,10 +525,10 @@ def get_oas_30(cfg): LOGGER.debug('Provider is editable; adding post') paths[items_path]['post'] = { - 'summary': 'Add {} items'.format(title), # noqa + 'summary': f'Add {title} items', 'description': desc, 'tags': [name], - 'operationId': 'add{}Features'.format(name.capitalize()), + 'operationId': f'add{name.capitalize()}Features', 'requestBody': { 'description': 'Adds item to collection', 'content': { @@ -540,8 +540,8 @@ def get_oas_30(cfg): }, 'responses': { '201': {'description': 'Successful creation'}, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } @@ -555,33 +555,32 @@ def get_oas_30(cfg): if ptype == 'record': paths[items_path]['get']['parameters'].append( - {'$ref': '{}/parameters/q.yaml'.format(OPENAPI_YAML['oapir'])}) # noqa + {'$ref': f"{OPENAPI_YAML['oapir']}/parameters/q.yaml"}) if p.fields: - queryables_path = '{}/queryables'.format(collection_name_path) + queryables_path = f'{collection_name_path}/queryables' paths[queryables_path] = { 'get': { - 'summary': 'Get {} queryables'.format(title), + 'summary': f'Get {title} queryables', 'description': desc, 'tags': [name], - 'operationId': 'get{}Queryables'.format( - name.capitalize()), + 'operationId': f'get{name.capitalize()}Queryables', 'parameters': [ items_f, items_l ], 'responses': { '200': {'$ref': '#/components/responses/Queryables'}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"}, # noqa } } } if p.time_field is not None: paths[items_path]['get']['parameters'].append( - {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])}) # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/datetime"}) # noqa for field, type_ in p.fields.items(): @@ -611,8 +610,8 @@ def get_oas_30(cfg): else: schema = type_ - path_ = '{}/items'.format(collection_name_path) - paths['{}'.format(path_)]['get']['parameters'].append({ + path_ = f'{collection_name_path}/items' + paths[path_]['get']['parameters'].append({ 'name': field, 'in': 'query', 'required': False, @@ -621,29 +620,29 @@ def get_oas_30(cfg): 'explode': False }) - paths['{}/items/{{featureId}}'.format(collection_name_path)] = { + paths[f'{collection_name_path}/items/{{featureId}}'] = { 'get': { - 'summary': 'Get {} item by id'.format(title), + 'summary': f'Get {title} item by id', 'description': desc, 'tags': [name], - 'operationId': 'get{}Feature'.format(name.capitalize()), + 'operationId': f'get{name.capitalize()}Feature', 'parameters': [ - {'$ref': '{}#/components/parameters/featureId'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/featureId"}, # noqa {'$ref': '#/components/parameters/f'}, {'$ref': '#/components/parameters/lang'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/Feature'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/Feature"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } try: schema_ref = p.get_schema() - paths['{}/items/{{featureId}}'.format(collection_name_path)]['get']['responses']['200'] = { # noqa + paths[f'{collection_name_path}/items/{{featureId}}']['get']['responses']['200'] = { # noqa 'content': { schema_ref[0]: { 'schema': schema_ref[1] @@ -655,14 +654,14 @@ def get_oas_30(cfg): if p.editable: LOGGER.debug('Provider is editable; adding put/delete') - put_path = '{}/items/{{featureId}}'.format(collection_name_path) # noqa + put_path = f'{collection_name_path}/items/{{featureId}}' # noqa paths[put_path]['put'] = { # noqa - 'summary': 'Update {} items'.format(title), + 'summary': f'Update {title} items', 'description': desc, 'tags': [name], - 'operationId': 'update{}Features'.format(name.capitalize()), # noqa + 'operationId': f'update{name.capitalize()}Features', 'parameters': [ - {'$ref': '{}#/components/parameters/featureId'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/featureId"} # noqa ], 'requestBody': { 'description': 'Updates item in collection', @@ -675,8 +674,8 @@ def get_oas_30(cfg): }, 'responses': { '204': {'description': 'Successful update'}, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } @@ -688,18 +687,18 @@ def get_oas_30(cfg): except Exception as err: LOGGER.debug(err) - paths['{}/items/{{featureId}}'.format(collection_name_path)]['delete'] = { # noqa - 'summary': 'Delete {} items'.format(title), + paths[f'{collection_name_path}/items/{{featureId}}']['delete'] = { # noqa + 'summary': f'Delete {title} items', 'description': desc, 'tags': [name], - 'operationId': 'delete{}Features'.format(name.capitalize()), # noqa + 'operationId': f'delete{name.capitalize()}Features', 'parameters': [ - {'$ref': '{}#/components/parameters/featureId'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/featureId"}, # noqa ], 'responses': { '200': {'description': 'Successful delete'}, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } @@ -711,71 +710,67 @@ def get_oas_30(cfg): load_plugin('provider', get_provider_by_type( collections[k]['providers'], 'coverage')) - coverage_path = '{}/coverage'.format(collection_name_path) + coverage_path = f'{collection_name_path}/coverage' paths[coverage_path] = { 'get': { - 'summary': 'Get {} coverage'.format(title), + 'summary': f'Get {title} coverage', 'description': desc, 'tags': [name], - 'operationId': 'get{}Coverage'.format(name.capitalize()), + 'operationId': f'get{name.capitalize()}Coverage', 'parameters': [ items_f, items_l, - {'$ref': '{}#/components/parameters/bbox'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/bbox"}, # noqa {'$ref': '#/components/parameters/bbox-crs'} ], 'responses': { - '200': {'$ref': '{}#/components/responses/Features'.format(OPENAPI_YAML['oapif'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/Features"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } - coverage_domainset_path = '{}/coverage/domainset'.format( - collection_name_path) + coverage_domainset_path = f'{collection_name_path}/coverage/domainset' # noqa paths[coverage_domainset_path] = { 'get': { - 'summary': 'Get {} coverage domain set'.format(title), + 'summary': f'Get {title} coverage domain set', 'description': desc, 'tags': [name], - 'operationId': 'get{}CoverageDomainSet'.format( - name.capitalize()), + 'operationId': f'get{name.capitalize()}CoverageDomainSet', 'parameters': [ items_f, items_l ], 'responses': { - '200': {'$ref': '{}/schemas/cis_1.1/domainSet.yaml'.format(OPENAPI_YAML['oacov'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oacov']}/schemas/cis_1.1/domainSet.yaml"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } - coverage_rangetype_path = '{}/coverage/rangetype'.format( - collection_name_path) + coverage_rangetype_path = f'{collection_name_path}/coverage/rangetype' # noqa paths[coverage_rangetype_path] = { 'get': { - 'summary': 'Get {} coverage range type'.format(title), + 'summary': f'Get {title} coverage range type', 'description': desc, 'tags': [name], - 'operationId': 'get{}CoverageRangeType'.format( - name.capitalize()), + 'operationId': f'get{name.capitalize()}CoverageRangeType', 'parameters': [ items_f, items_l ], 'responses': { - '200': {'$ref': '{}/schemas/cis_1.1/rangeType.yaml'.format(OPENAPI_YAML['oacov'])}, # noqa - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '200': {'$ref': f"{OPENAPI_YAML['oacov']}/schemas/cis_1.1/rangeType.yaml"}, # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -830,47 +825,47 @@ def get_oas_30(cfg): }, 'links': { 'type': 'array', - 'items': {'$ref': '{}#/components/schemas/link'.format(OPENAPI_YAML['oapit'])}, # noqa + 'items': {'$ref': f"{OPENAPI_YAML['oapit']}#/components/schemas/link"} # noqa } } } } ) - tiles_path = '{}/tiles'.format(collection_name_path) + tiles_path = f'{collection_name_path}/tiles' paths[tiles_path] = { 'get': { - 'summary': 'Fetch a {} tiles description'.format(title), # noqa + 'summary': f'Fetch a {title} tiles description', 'description': desc, 'tags': [name], - 'operationId': 'describe{}Tiles'.format(name.capitalize()), + 'operationId': f'describe{name.capitalize()}Tiles', 'parameters': [ items_f, # items_l TODO: is this useful? ], 'responses': { '200': {'$ref': '#/components/responses/Tiles'}, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } - tiles_data_path = '{}/tiles/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}'.format(collection_name_path) # noqa + tiles_data_path = f'{collection_name_path}/tiles/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}' # noqa paths[tiles_data_path] = { 'get': { - 'summary': 'Get a {} tile'.format(title), + 'summary': f'Get a {title} tile', 'description': desc, 'tags': [name], - 'operationId': 'get{}Tiles'.format(name.capitalize()), + 'operationId': f'get{name.capitalize()}Tiles', 'parameters': [ - {'$ref': '{}#/components/parameters/tileMatrixSetId'.format(OPENAPI_YAML['oat'])}, # noqa - {'$ref': '{}#/components/parameters/tileMatrix'.format(OPENAPI_YAML['oat'])}, # noqa - {'$ref': '{}#/components/parameters/tileRow'.format(OPENAPI_YAML['oat'])}, # noqa - {'$ref': '{}#/components/parameters/tileCol'.format(OPENAPI_YAML['oat'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oat']}#/components/parameters/tileMatrixSetId"}, # noqa + {'$ref': f"{OPENAPI_YAML['oat']}#/components/parameters/tileMatrix"}, # noqa + {'$ref': f"{OPENAPI_YAML['oat']}#/components/parameters/tileRow"}, # noqa + {'$ref': f"{OPENAPI_YAML['oat']}#/components/parameters/tileCol"}, # noqa { 'name': 'f', 'in': 'query', @@ -886,9 +881,9 @@ def get_oas_30(cfg): } ], 'responses': { - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '404': {'$ref': '{}#/components/responses/NotFound'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/NotFound"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"} # noqa } } } @@ -916,29 +911,29 @@ def get_oas_30(cfg): for qt in ep.get_query_types(): edr_query_endpoints.append({ - 'path': '{}/{}'.format(collection_name_path, qt), + 'path': f'{collection_name_path}/{qt}', 'qt': qt, - 'op_id': 'query{}{}'.format(qt.capitalize(), k.capitalize()) # noqa + 'op_id': f'query{qt.capitalize()}{k.capitalize()}' }) if ep.instances: edr_query_endpoints.append({ - 'path': '{}/instances/{{instanceId}}/{}'.format(collection_name_path, qt), # noqa + 'path': f'{collection_name_path}/instances/{{instanceId}}/{qt}', # noqa 'qt': qt, - 'op_id': 'query{}Instance{}'.format(qt.capitalize(), k.capitalize()) # noqa + 'op_id': f'query{qt.capitalize()}Instance{k.capitalize()}' # noqa }) for eqe in edr_query_endpoints: paths[eqe['path']] = { 'get': { - 'summary': 'query {} by {}'.format(v['description'], eqe['qt']), # noqa + 'summary': f"query {v['description']} by {eqe['qt']}", # noqa 'description': v['description'], 'tags': [k], 'operationId': eqe['op_id'], 'parameters': [ - {'$ref': '{}/parameters/{}Coords.yaml'.format(OPENAPI_YAML['oaedr'], eqe['qt'])}, # noqa - {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])}, # noqa - {'$ref': '{}/parameters/parameter-name.yaml'.format(OPENAPI_YAML['oaedr'])}, # noqa - {'$ref': '{}/parameters/z.yaml'.format(OPENAPI_YAML['oaedr'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/{eqe['qt']}Coords.yaml"}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/datetime"}, # noqa + {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/parameter-name.yaml"}, # noqa + {'$ref': f"{OPENAPI_YAML['oaedr']}/parameters/z.yaml"}, # noqa {'$ref': '#/components/parameters/f'} ], 'responses': { @@ -947,13 +942,14 @@ def get_oas_30(cfg): 'content': { 'application/prs.coverage+json': { 'schema': { - '$ref': '{}/schemas/coverageJSON.yaml'.format(OPENAPI_YAML['oaedr'])} # noqa + '$ref': f"{OPENAPI_YAML['oaedr']}/schemas/coverageJSON.yaml" # noqa } } } } } } + } LOGGER.debug('setting up maps endpoints') map_extension = filter_providers_by_type( @@ -966,15 +962,15 @@ def get_oas_30(cfg): map_f['schema']['enum'] = [map_extension['format']['name']] map_f['schema']['default'] = map_extension['format']['name'] - pth = '/collections/{}/map'.format(k) + pth = f'/collections/{k}/map' paths[pth] = { 'get': { 'summary': 'Get map', - 'description': '{} map'.format(v['description']), + 'description': f"{v['description']} map", 'tags': [k], 'operationId': 'getMap', 'parameters': [ - {'$ref': '{}#/components/parameters/bbox'.format(OPENAPI_YAML['oapif'])}, # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/bbox"}, # noqa { 'name': 'width', 'in': 'query', @@ -1019,14 +1015,14 @@ def get_oas_30(cfg): 'application/json': {} } }, - '400': {'$ref': '{}#/components/responses/InvalidParameter'.format(OPENAPI_YAML['oapif'])}, # noqa - '500': {'$ref': '{}#/components/responses/ServerError'.format(OPENAPI_YAML['oapif'])} # noqa + '400': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/InvalidParameter"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapif']}#/components/responses/ServerError"}, # noqa } } } if mp.time_field is not None: paths[pth]['get']['parameters'].append( - {'$ref': '{}#/components/parameters/datetime'.format(OPENAPI_YAML['oapif'])}) # noqa + {'$ref': f"{OPENAPI_YAML['oapif']}#/components/parameters/datetime"}) # noqa LOGGER.debug('setting up STAC') stac_collections = filter_dict_by_key_value(cfg['resources'], @@ -1061,7 +1057,7 @@ def get_oas_30(cfg): {'$ref': '#/components/parameters/f'} ], 'responses': { - '200': {'$ref': '{}/responses/ProcessList.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '200': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/ProcessList.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} } } @@ -1070,13 +1066,13 @@ def get_oas_30(cfg): for k, v in processes.items(): if k.startswith('_'): - LOGGER.debug('Skipping hidden layer: {}'.format(k)) + LOGGER.debug(f'Skipping hidden layer: {k}') continue name = l10n.translate(k, locale_) p = load_plugin('process', v['processor']) md_desc = l10n.translate(p.metadata['description'], locale_) - process_name_path = '/processes/{}'.format(name) + process_name_path = f'/processes/{name}' tag = { 'name': name, 'description': md_desc, # noqa @@ -1097,7 +1093,7 @@ def get_oas_30(cfg): 'summary': 'Get process metadata', 'description': md_desc, 'tags': [name], - 'operationId': 'describe{}Process'.format(name.capitalize()), # noqa + 'operationId': f'describe{name.capitalize()}Process', 'parameters': [ {'$ref': '#/components/parameters/f'} ], @@ -1108,18 +1104,17 @@ def get_oas_30(cfg): } } - paths['{}/execution'.format(process_name_path)] = { + paths[f'{process_name_path}/execution'] = { 'post': { - 'summary': 'Process {} execution'.format( - l10n.translate(p.metadata['title'], locale_)), + 'summary': f"Process {l10n.translate(p.metadata['title'], locale_)} execution", # noqa 'description': md_desc, 'tags': [name], - 'operationId': 'execute{}Job'.format(name.capitalize()), + 'operationId': f'execute{name.capitalize()}Job', 'responses': { '200': {'$ref': '#/components/responses/200'}, - '201': {'$ref': '{}/responses/ExecuteAsync.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa - '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa - '500': {'$ref': '{}/responses/ServerError.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '201': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/ExecuteAsync.yaml"}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/NotFound.yaml"}, # noqa + '500': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/ServerError.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} }, 'requestBody': { @@ -1128,7 +1123,7 @@ def get_oas_30(cfg): 'content': { 'application/json': { 'schema': { - '$ref': '{}/schemas/execute.yaml'.format(OPENAPI_YAML['oapip']) # noqa + '$ref': f"{OPENAPI_YAML['oapip']}/schemas/execute.yaml" # noqa } } } @@ -1136,7 +1131,7 @@ def get_oas_30(cfg): } } if 'example' in p.metadata: - paths['{}/execution'.format(process_name_path)]['post']['requestBody']['content']['application/json']['example'] = p.metadata['example'] # noqa + paths[f'{process_name_path}/execution']['post']['requestBody']['content']['application/json']['example'] = p.metadata['example'] # noqa name_in_path = { 'name': 'jobId', @@ -1157,7 +1152,7 @@ def get_oas_30(cfg): 'operationId': 'getJobs', 'responses': { '200': {'$ref': '#/components/responses/200'}, - '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/NotFound.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} } } @@ -1175,7 +1170,7 @@ def get_oas_30(cfg): 'operationId': 'getJob', 'responses': { '200': {'$ref': '#/components/responses/200'}, - '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/NotFound.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} # noqa } }, @@ -1189,7 +1184,7 @@ def get_oas_30(cfg): 'operationId': 'deleteJob', 'responses': { '204': {'$ref': '#/components/responses/204'}, - '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/NotFound.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} # noqa } }, @@ -1207,7 +1202,7 @@ def get_oas_30(cfg): 'operationId': 'getJobResults', 'responses': { '200': {'$ref': '#/components/responses/200'}, - '404': {'$ref': '{}/responses/NotFound.yaml'.format(OPENAPI_YAML['oapip'])}, # noqa + '404': {'$ref': f"{OPENAPI_YAML['oapip']}/responses/NotFound.yaml"}, # noqa 'default': {'$ref': '#/components/responses/default'} # noqa } } @@ -1297,7 +1292,7 @@ def validate(ctx, openapi_file): if openapi_file is None: raise click.ClickException('--openapi/-o required') - click.echo('Validating {}'.format(openapi_file)) + click.echo(f'Validating {openapi_file}') instance = yaml_load(openapi_file) validate_openapi_document(instance) click.echo('Valid OpenAPI document') diff --git a/pygeoapi/plugin.py b/pygeoapi/plugin.py index 3ce7bd9..4c610d2 100644 --- a/pygeoapi/plugin.py +++ b/pygeoapi/plugin.py @@ -30,6 +30,7 @@ import importlib import logging +from typing import Any LOGGER = logging.getLogger(__name__) @@ -71,7 +72,7 @@ PLUGINS = { } -def load_plugin(plugin_type, plugin_def): +def load_plugin(plugin_type: str, plugin_def: dict) -> Any: """ loads plugin by name @@ -84,16 +85,16 @@ def load_plugin(plugin_type, plugin_def): name = plugin_def['name'] if plugin_type not in PLUGINS.keys(): - msg = 'Plugin type {} not found'.format(plugin_type) + msg = f'Plugin type {plugin_type} not found' LOGGER.exception(msg) raise InvalidPluginError(msg) plugin_list = PLUGINS[plugin_type] - LOGGER.debug('Plugins: {}'.format(plugin_list)) + LOGGER.debug(f'Plugins: {plugin_list}') if '.' not in name and name not in plugin_list.keys(): - msg = 'Plugin {} not found'.format(name) + msg = f'Plugin {name} not found' LOGGER.exception(msg) raise InvalidPluginError(msg) @@ -102,8 +103,8 @@ def load_plugin(plugin_type, plugin_def): else: # core formatter packagename, classname = plugin_list[name].rsplit('.', 1) - LOGGER.debug('package name: {}'.format(packagename)) - LOGGER.debug('class name: {}'.format(classname)) + LOGGER.debug(f'package name: {packagename}') + LOGGER.debug(f'class name: {classname}') module = importlib.import_module(packagename) class_ = getattr(module, classname) diff --git a/pygeoapi/process/base.py b/pygeoapi/process/base.py index 62596f0..e9aa683 100644 --- a/pygeoapi/process/base.py +++ b/pygeoapi/process/base.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -28,6 +28,7 @@ # ================================================================= import logging +from typing import Any, Tuple LOGGER = logging.getLogger(__name__) @@ -35,7 +36,7 @@ LOGGER = logging.getLogger(__name__) class BaseProcessor: """generic Processor ABC. Processes are inherited from this class""" - def __init__(self, processor_def, process_metadata): + def __init__(self, processor_def: dict, process_metadata: dict): """ Initialize object @@ -47,7 +48,7 @@ class BaseProcessor: self.name = processor_def['name'] self.metadata = process_metadata - def execute(self): + def execute(self) -> Tuple[str, Any]: """ execute the process @@ -57,7 +58,7 @@ class BaseProcessor: raise NotImplementedError() def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' class ProcessorGenericError(Exception): diff --git a/pygeoapi/process/hello_world.py b/pygeoapi/process/hello_world.py index 0e7ce78..ffaee34 100644 --- a/pygeoapi/process/hello_world.py +++ b/pygeoapi/process/hello_world.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -120,12 +120,13 @@ class HelloWorldProcessor(BaseProcessor): def execute(self, data): mimetype = 'application/json' - name = data.get('name', None) + name = data.get('name') if name is None: raise ProcessorExecuteError('Cannot process without a name') - value = 'Hello {}! {}'.format(name, data.get('message', '')).strip() + message = data.get('message', '') + value = f'Hello {name}! {message}'.strip() outputs = { 'id': 'echo', @@ -135,4 +136,4 @@ class HelloWorldProcessor(BaseProcessor): return mimetype, outputs def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' diff --git a/pygeoapi/process/manager/base.py b/pygeoapi/process/manager/base.py index 79e441f..f4a58d2 100644 --- a/pygeoapi/process/manager/base.py +++ b/pygeoapi/process/manager/base.py @@ -28,13 +28,14 @@ # ================================================================= from datetime import datetime -import io import json import logging from multiprocessing import dummy -import os +from pathlib import Path +from typing import Any, Tuple from pygeoapi.util import DATETIME_FORMAT, JobStatus +from pygeoapi.process.base import BaseProcessor LOGGER = logging.getLogger(__name__) @@ -42,7 +43,7 @@ LOGGER = logging.getLogger(__name__) class BaseManager: """generic Manager ABC""" - def __init__(self, manager_def): + def __init__(self, manager_def: dict): """ Initialize object @@ -53,10 +54,13 @@ class BaseManager: self.name = manager_def['name'] self.is_async = False - self.connection = manager_def.get('connection', None) - self.output_dir = manager_def.get('output_dir', None) + self.connection = manager_def.get('connection') + self.output_dir = manager_def.get('output_dir') - def get_jobs(self, status=None): + if self.output_dir is not None: + self.output_dir = Path(self.output_dir) + + def get_jobs(self, status: JobStatus = None) -> list: """ Get process jobs, optionally filtered by status @@ -68,7 +72,7 @@ class BaseManager: raise NotImplementedError() - def add_job(self, job_metadata): + def add_job(self, job_metadata: dict) -> str: """ Add a job @@ -79,7 +83,7 @@ class BaseManager: raise NotImplementedError() - def update_job(self, job_id, update_dict): + def update_job(self, job_id: str, update_dict: dict) -> bool: """ Updates a job @@ -91,7 +95,7 @@ class BaseManager: raise NotImplementedError() - def get_job(self, job_id): + def get_job(self, job_id: str) -> dict: """ Get a job (!) @@ -102,7 +106,7 @@ class BaseManager: raise NotImplementedError() - def get_job_result(self, job_id): + def get_job_result(self, job_id: str) -> Tuple[str, Any]: """ Returns the actual output from a completed process @@ -113,7 +117,7 @@ class BaseManager: raise NotImplementedError() - def delete_job(self, job_id): + def delete_job(self, job_id: str) -> bool: """ Deletes a job and associated results/outputs @@ -124,7 +128,8 @@ class BaseManager: raise NotImplementedError() - def _execute_handler_async(self, p, job_id, data_dict): + def _execute_handler_async(self, p: BaseProcessor, job_id: str, + data_dict: dict) -> Tuple[None, JobStatus]: """ This private execution handler executes a process in a background thread using `multiprocessing.dummy` @@ -145,7 +150,8 @@ class BaseManager: _process.start() return 'application/json', None, JobStatus.accepted - def _execute_handler_sync(self, p, job_id, data_dict): + def _execute_handler_sync(self, p: BaseProcessor, job_id: str, + data_dict: dict) -> Tuple[str, Any, JobStatus]: """ Synchronous execution handler @@ -180,8 +186,8 @@ class BaseManager: try: if self.output_dir is not None: - filename = '{}-{}'.format(p.metadata['id'], job_id) - job_filename = os.path.join(self.output_dir, filename) + filename = f"{p.metadata['id']}-{job_id}" + job_filename = self.output_dir / filename else: job_filename = None @@ -195,7 +201,7 @@ class BaseManager: }) if self.output_dir is not None: - LOGGER.debug('writing output to {}'.format(job_filename)) + LOGGER.debug(f'writing output to {job_filename}') if isinstance(outputs, dict): mode = 'w' data = json.dumps(outputs, sort_keys=True, indent=4) @@ -204,7 +210,7 @@ class BaseManager: mode = 'wb' data = outputs encoding = None - with io.open(job_filename, mode, encoding=encoding) as fh: + with job_filename.open(mode=mode, encoding=encoding) as fh: fh.write(data) current_status = JobStatus.successful @@ -213,7 +219,7 @@ class BaseManager: 'job_end_datetime': datetime.utcnow().strftime( DATETIME_FORMAT), 'status': current_status.value, - 'location': job_filename, + 'location': str(job_filename), 'mimetype': jfmt, 'message': 'Job complete', 'progress': 100 @@ -230,6 +236,7 @@ class BaseManager: # endpoint, even if the /result endpoint correctly returns the # failure information (i.e. what one might assume is a 200 # response). + current_status = JobStatus.failed code = 'InvalidParameterValue' outputs = { @@ -272,4 +279,4 @@ class BaseManager: return self._execute_handler_async(p, job_id, data_dict) def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' diff --git a/pygeoapi/process/manager/dummy.py b/pygeoapi/process/manager/dummy.py index e8b78c6..2493c4e 100644 --- a/pygeoapi/process/manager/dummy.py +++ b/pygeoapi/process/manager/dummy.py @@ -28,7 +28,9 @@ # ================================================================= import logging +from typing import Any, Tuple +from pygeoapi.process.base import BaseProcessor from pygeoapi.process.manager.base import BaseManager from pygeoapi.util import JobStatus @@ -38,7 +40,7 @@ LOGGER = logging.getLogger(__name__) class DummyManager(BaseManager): """generic Manager ABC""" - def __init__(self, manager_def): + def __init__(self, manager_def: dict): """ Initialize object @@ -49,7 +51,7 @@ class DummyManager(BaseManager): super().__init__(manager_def) - def get_jobs(self, status=None): + def get_jobs(self, status: JobStatus = None) -> list: """ Get process jobs, optionally filtered by status @@ -61,7 +63,8 @@ class DummyManager(BaseManager): return [] - def execute_process(self, p, job_id, data_dict, is_async=False): + def execute_process(self, p: BaseProcessor, job_id: str, data_dict: dict, + is_async: bool = False) -> Tuple[str, Any, int]: """ Default process execution handler @@ -93,4 +96,4 @@ class DummyManager(BaseManager): return jfmt, outputs, current_status def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' diff --git a/pygeoapi/process/manager/tinydb_.py b/pygeoapi/process/manager/tinydb_.py index 16d6f9b..37b0f62 100644 --- a/pygeoapi/process/manager/tinydb_.py +++ b/pygeoapi/process/manager/tinydb_.py @@ -28,10 +28,10 @@ # ================================================================= import fcntl -import io import json import logging -import os +from pathlib import Path +from typing import Any, Tuple import tinydb @@ -44,7 +44,7 @@ LOGGER = logging.getLogger(__name__) class TinyDBManager(BaseManager): """TinyDB Manager""" - def __init__(self, manager_def): + def __init__(self, manager_def: dict): """ Initialize object @@ -56,7 +56,7 @@ class TinyDBManager(BaseManager): super().__init__(manager_def) self.is_async = True - def _connect(self, mode='r'): + def _connect(self, mode: str = 'r') -> bool: """ connect to manager @@ -70,7 +70,7 @@ class TinyDBManager(BaseManager): return True - def destroy(self): + def destroy(self) -> bool: """ Destroy manager @@ -81,7 +81,7 @@ class TinyDBManager(BaseManager): self.db.close() return True - def get_jobs(self, status=None): + def get_jobs(self, status: JobStatus = None) -> list: """ Get jobs @@ -97,7 +97,7 @@ class TinyDBManager(BaseManager): return jobs_list - def add_job(self, job_metadata): + def add_job(self, job_metadata: dict) -> str: """ Add a job @@ -112,7 +112,7 @@ class TinyDBManager(BaseManager): return doc_id - def update_job(self, job_id, update_dict): + def update_job(self, job_id: str, update_dict: dict) -> bool: """ Updates a job @@ -128,7 +128,7 @@ class TinyDBManager(BaseManager): return True - def delete_job(self, job_id): + def delete_job(self, job_id: str) -> bool: """ Deletes a job @@ -139,9 +139,9 @@ class TinyDBManager(BaseManager): # delete result file if present job_result = self.get_job(job_id) if job_result: - location = job_result.get('location', None) + location = job_result.get('location') if location and self.output_dir is not None: - os.remove(location) + Path(location).unlink() self._connect(mode='w') removed = bool(self.db.remove(tinydb.where('identifier') == job_id)) @@ -149,7 +149,7 @@ class TinyDBManager(BaseManager): return removed - def get_job(self, job_id): + def get_job(self, job_id: str) -> dict: """ Get a single job @@ -166,7 +166,7 @@ class TinyDBManager(BaseManager): self.db.close() return result - def get_job_result(self, job_id): + def get_job_result(self, job_id: str) -> Tuple[str, Any]: """ Get a job's status, and actual output of executing the process @@ -180,8 +180,8 @@ class TinyDBManager(BaseManager): # job does not exist return None - location = job_result.get('location', None) - mimetype = job_result.get('mimetype', None) + location = job_result.get('location') + mimetype = job_result.get('mimetype') job_status = JobStatus[job_result['status']] if not job_status == JobStatus.successful: @@ -191,11 +191,13 @@ class TinyDBManager(BaseManager): # Job data was not written for some reason # TODO log/raise exception? return (None,) + else: + location = Path(location) - with io.open(location, 'r', encoding='utf-8') as filehandler: + with location.open('r', encoding='utf-8') as filehandler: result = json.load(filehandler) return mimetype, result def __repr__(self): - return ' {}'.format(self.name) + return f' {self.name}' diff --git a/pygeoapi/provider/base.py b/pygeoapi/provider/base.py index 769b8a0..82204af 100644 --- a/pygeoapi/provider/base.py +++ b/pygeoapi/provider/base.py @@ -61,11 +61,11 @@ class BaseProvider: raise RuntimeError('name/type/data are required') self.editable = provider_def.get('editable', False) - self.options = provider_def.get('options', None) - self.id_field = provider_def.get('id_field', None) - self.uri_field = provider_def.get('uri_field', None) - self.x_field = provider_def.get('x_field', None) - self.y_field = provider_def.get('y_field', None) + self.options = provider_def.get('options') + self.id_field = provider_def.get('id_field') + self.uri_field = provider_def.get('uri_field') + self.x_field = provider_def.get('x_field') + self.y_field = provider_def.get('y_field') self.time_field = provider_def.get('time_field') self.title_field = provider_def.get('title_field') self.properties = provider_def.get('properties', []) @@ -212,7 +212,7 @@ class BaseProvider: msg = None LOGGER.debug('Loading data') - LOGGER.debug('Data: {}'.format(item)) + LOGGER.debug(f'Data: {item}') try: json_data = json.loads(item) except TypeError as err: @@ -262,7 +262,7 @@ class BaseProvider: return identifier2, json_data def __repr__(self): - return ' {}'.format(self.type) + return f' {self.type}' class ProviderGenericError(Exception): diff --git a/pygeoapi/provider/csv_.py b/pygeoapi/provider/csv_.py index 983b506..75da06f 100644 --- a/pygeoapi/provider/csv_.py +++ b/pygeoapi/provider/csv_.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -193,9 +193,9 @@ class CSVProvider(BaseProvider): if item: return item else: - err = 'item {} not found'.format(identifier) + err = f'item {identifier} not found' LOGGER.error(err) raise ProviderItemNotFoundError(err) def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' diff --git a/pygeoapi/provider/elasticsearch_.py b/pygeoapi/provider/elasticsearch_.py index 47e2b69..e10c027 100644 --- a/pygeoapi/provider/elasticsearch_.py +++ b/pygeoapi/provider/elasticsearch_.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # Copyright (c) 2021 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person @@ -67,8 +67,8 @@ class ElasticsearchProvider(BaseProvider): LOGGER.debug('Setting Elasticsearch properties') self.is_gdal = False - LOGGER.debug('host: {}'.format(self.es_host)) - LOGGER.debug('index: {}'.format(self.index_name)) + LOGGER.debug(f'host: {self.es_host}') + LOGGER.debug(f'index: {self.index_name}') self.type_name = 'FeatureCollection' self.url_parsed = urlparse(self.es_host) @@ -94,7 +94,7 @@ class ElasticsearchProvider(BaseProvider): if self.url_parsed.path: url_settings['url_prefix'] = self.url_parsed.path - LOGGER.debug('URL settings: {}'.format(url_settings)) + LOGGER.debug(f'URL settings: {url_settings}') LOGGER.debug('Connecting to Elasticsearch') self.es = Elasticsearch([url_settings]) if not self.es.ping(): @@ -262,14 +262,14 @@ class ElasticsearchProvider(BaseProvider): LOGGER.debug('processing sortby') query['sort'] = [] for sort in sortby: - LOGGER.debug('processing sort object: {}'.format(sort)) + LOGGER.debug(f'processing sort object: {sort}') sp = sort['property'] if (self.fields[sp]['type'] == 'string' and self.fields[sp].get('format') != 'date'): LOGGER.debug('setting ES .raw on property') - sort_property = '{}.raw'.format(self.mask_prop(sp)) + sort_property = f'{self.mask_prop(sp)}.raw' else: sort_property = self.mask_prop(sp) @@ -318,7 +318,7 @@ class ElasticsearchProvider(BaseProvider): try: LOGGER.debug('querying Elasticsearch') if filterq: - LOGGER.debug('adding cql object: {}'.format(filterq.json())) + LOGGER.debug(f'adding cql object: {filterq.json()}') query = update_query(input_query=query, cql=filterq) LOGGER.debug(json.dumps(query, indent=4)) @@ -378,12 +378,12 @@ class ElasticsearchProvider(BaseProvider): """ try: - LOGGER.debug('Fetching identifier {}'.format(identifier)) + LOGGER.debug(f'Fetching identifier {identifier}') result = self.es.get(index=self.index_name, id=identifier) LOGGER.debug('Serializing feature') feature_ = self.esdoc2geojson(result) except exceptions.NotFoundError as err: - LOGGER.debug('Not found via ES id query: {}'.format(err)) + LOGGER.debug(f'Not found via ES id query: {err}') LOGGER.debug('Trying via a real query') query = { @@ -425,7 +425,7 @@ class ElasticsearchProvider(BaseProvider): identifier, json_data = self._load_and_prepare_item(item) - LOGGER.debug('Inserting data with identifier {}'.format(identifier)) + LOGGER.debug(f'Inserting data with identifier {identifier}') _ = self.es.index(index=self.index_name, id=identifier, body=json_data) LOGGER.debug('Item added') @@ -441,7 +441,7 @@ class ElasticsearchProvider(BaseProvider): :returns: `bool` of update result """ - LOGGER.debug('Updating item {}'.format(identifier)) + LOGGER.debug(f'Updating item {identifier}') identifier, json_data = self._load_and_prepare_item( item, identifier, raise_if_exists=False) @@ -458,7 +458,7 @@ class ElasticsearchProvider(BaseProvider): :returns: `bool` of deletion result """ - LOGGER.debug('Deleting item {}'.format(identifier)) + LOGGER.debug(f'Deleting item {identifier}') _ = self.es.delete(index=self.index_name, id=identifier) return True @@ -528,13 +528,13 @@ class ElasticsearchProvider(BaseProvider): if self.is_gdal: return property_name else: - return 'properties.{}'.format(property_name) + return f'properties.{property_name}' def get_properties(self): all_properties = [] - LOGGER.debug('configured properties: {}'.format(self.properties)) - LOGGER.debug('selected properties: {}'.format(self.select_properties)) + LOGGER.debug(f'configured properties: {self.properties}') + LOGGER.debug(f'selected properties: {self.select_properties}') if not self.properties and not self.select_properties: all_properties = self.get_fields() @@ -543,11 +543,11 @@ class ElasticsearchProvider(BaseProvider): else: all_properties = set(self.properties) | set(self.select_properties) - LOGGER.debug('resulting properties: {}'.format(all_properties)) + LOGGER.debug(f'resulting properties: {all_properties}') return all_properties def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' class ElasticsearchCatalogueProvider(ElasticsearchProvider): @@ -588,7 +588,7 @@ class ElasticsearchCatalogueProvider(ElasticsearchProvider): return records def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' class ESQueryBuilder: @@ -769,7 +769,5 @@ def update_query(input_query: Dict, cql: CQLModel): output_query = _build_query(query, cql) s = s.query(output_query) - LOGGER.debug('Enhanced query: {}'.format( - json.dumps(s.to_dict()) - )) + LOGGER.debug(f'Enhanced query: {json.dumps(s.to_dict())}') return s.to_dict() diff --git a/pygeoapi/provider/esri.py b/pygeoapi/provider/esri.py index 50330fd..0da9013 100644 --- a/pygeoapi/provider/esri.py +++ b/pygeoapi/provider/esri.py @@ -82,7 +82,7 @@ class ESRIServiceProvider(BaseProvider): resp = self.get_response(self.data, params=params) if resp.get('error') is not None: - msg = 'Connection error: {}'.format(resp['error']['message']) + msg = f"Connection error: {resp['error']['message']}" LOGGER.error(msg) raise ProviderConnectionError(msg) @@ -283,9 +283,8 @@ class ESRIServiceProvider(BaseProvider): if datetime_ is not None: def esri_dt(dt): - return "TIMESTAMP '{}'".format( - format_datetime(dt, '%Y-%m-%d %H:%M:%S') - ) + dt_ = format_datetime(dt, '%Y-%m-%d %H:%M:%S') + return f"TIMESTAMP '{dt_}'" tf = self.time_field if '/' in datetime_: diff --git a/pygeoapi/provider/filesystem.py b/pygeoapi/provider/filesystem.py index caf4e03..212c624 100644 --- a/pygeoapi/provider/filesystem.py +++ b/pygeoapi/provider/filesystem.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -56,7 +56,7 @@ class FileSystemProvider(BaseProvider): super().__init__(provider_def) if not os.path.exists(self.data): - msg = 'Directory does not exist: {}'.format(self.data) + msg = f'Directory does not exist: {self.data}' LOGGER.error(msg) raise ProviderConnectionError(msg) @@ -86,7 +86,7 @@ class FileSystemProvider(BaseProvider): parentpath = urljoin(thispath, '.') child_links.append({ 'rel': 'parent', - 'href': '{}?f=json'.format(parentpath), + 'href': f'{parentpath}?f=json', 'type': 'application/json' }) child_links.append({ @@ -102,7 +102,7 @@ class FileSystemProvider(BaseProvider): content = { 'links': [{ 'rel': 'root', - 'href': '{}?f=json'.format(root_link), + 'href': f'{root_link}?f=json', 'type': 'application/json' }, { 'rel': 'root', @@ -110,7 +110,7 @@ class FileSystemProvider(BaseProvider): 'type': 'text/html' }, { 'rel': 'self', - 'href': '{}?f=json'.format(thispath), + 'href': f'{thispath}?f=json', 'type': 'application/json', }, { 'rel': 'self', @@ -128,14 +128,14 @@ class FileSystemProvider(BaseProvider): else: LOGGER.debug('Checking if path exists as file via file_types') for ft in self.file_types: - tmp_path = '{}{}'.format(data_path, ft) + tmp_path = f'{data_path}{ft}' if os.path.exists(tmp_path): resource_type = 'file' data_path = tmp_path break if resource_type is None: - msg = 'Resource does not exist: {}'.format(data_path) + msg = f'Resource does not exist: {data_path}' LOGGER.error(msg) raise ProviderNotFoundError(msg) @@ -158,11 +158,6 @@ class FileSystemProvider(BaseProvider): if os.path.isdir(fullpath): newpath = os.path.join(baseurl, urlpath, dc) -# child_links.append({ -# 'rel': 'child', -# 'href': '{}?f=json'.format(newpath), -# 'type': 'application/json' -# }) child_links.append({ 'rel': 'child', 'href': newpath, @@ -173,7 +168,7 @@ class FileSystemProvider(BaseProvider): elif os.path.isfile(fullpath): basename, extension = os.path.splitext(dc) newpath = os.path.join(baseurl, urlpath, basename) - newpath2 = '{}{}'.format(newpath, extension) + newpath2 = f'{newpath}{extension}' if extension in self.file_types: fullpath = os.path.join(data_path, dc) child_links.append({ @@ -184,14 +179,6 @@ class FileSystemProvider(BaseProvider): 'file:size': filesize, 'entry:type': 'Item' }) -# child_links.append({ -# 'rel': 'item', -# 'title': get_path_basename(newpath2), -# 'href': newpath, -# 'type': 'text/html', -# 'created': filectime, -# 'file:size': filesize -# }) elif resource_type == 'file': filename = os.path.basename(data_path) @@ -199,7 +186,7 @@ class FileSystemProvider(BaseProvider): id_ = os.path.splitext(filename)[0] if urlpath: filename = filename.replace(id_, '') - url = '{}/{}{}'.format(baseurl, urlpath, filename) + url = f'{baseurl}/{urlpath}{filename}' filectime = file_modified_iso8601(data_path) filesize = os.path.getsize(data_path) @@ -225,7 +212,7 @@ class FileSystemProvider(BaseProvider): return content def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' def _describe_file(filepath): @@ -245,7 +232,7 @@ def _describe_file(filepath): 'properties': {} } - mcf_file = '{}.yml'.format(os.path.splitext(filepath)[0]) + mcf_file = f'{os.path.splitext(filepath)[0]}.yml' if os.path.isfile(mcf_file): try: @@ -260,9 +247,9 @@ def _describe_file(filepath): except ImportError: LOGGER.debug('pygeometa not found') except MCFReadError as err: - LOGGER.warning('MCF error: {}'.format(err)) + LOGGER.warning(f'MCF error: {err}') else: - LOGGER.debug('No mcf found at: {}'.format(mcf_file)) + LOGGER.debug(f'No mcf found at: {mcf_file}') if content['geometry'] is None and content['bbox'] is None: try: @@ -350,15 +337,14 @@ def _describe_file(filepath): id_ = os.path.splitext(os.path.basename(filepath))[0] content['assets'] = {} for suffix in ['shx', 'dbf', 'prj']: - fullpath = '{}.{}'.format( - os.path.splitext(filepath)[0], suffix) + fullpath = f'{os.path.splitext(filepath)[0]}.{suffix}' if os.path.exists(fullpath): filectime = file_modified_iso8601(fullpath) filesize = os.path.getsize(fullpath) content['assets'][suffix] = { - 'href': './{}.{}'.format(id_, suffix), + 'href': f'./{id_}.{suffix}', 'created': filectime, 'file:size': filesize } diff --git a/pygeoapi/provider/geojson.py b/pygeoapi/provider/geojson.py index 5b386c4..9b93fe2 100644 --- a/pygeoapi/provider/geojson.py +++ b/pygeoapi/provider/geojson.py @@ -3,7 +3,7 @@ # Authors: Matthew Perry # # Copyright (c) 2018 Matthew Perry -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -84,7 +84,7 @@ class GeoJSONProvider(BaseProvider): for f in data['features'][0]['properties'].keys(): fields[f] = {'type': 'string'} else: - LOGGER.warning('File {} does not exist.'.format(self.data)) + LOGGER.warning(f'File {self.data} does not exist.') return fields def _load(self, skip_geometry=None, properties=[], select_properties=[]): @@ -99,7 +99,7 @@ class GeoJSONProvider(BaseProvider): with open(self.data) as src: data = json.loads(src.read()) else: - LOGGER.warning('File {} does not exist.'.format(self.data)) + LOGGER.warning(f'File {self.data} does not exist.') data = { 'type': 'FeatureCollection', 'features': []} @@ -171,7 +171,7 @@ class GeoJSONProvider(BaseProvider): if str(feature.get('id')) == identifier: return feature # default, no match - err = 'item {} not found'.format(identifier) + err = f'item {identifier} not found' LOGGER.error(err) raise ProviderItemNotFoundError(err) @@ -230,4 +230,4 @@ class GeoJSONProvider(BaseProvider): dst.write(json.dumps(all_data)) def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' diff --git a/pygeoapi/provider/hateoas.py b/pygeoapi/provider/hateoas.py index da38425..b8da11b 100644 --- a/pygeoapi/provider/hateoas.py +++ b/pygeoapi/provider/hateoas.py @@ -77,7 +77,7 @@ class HateoasProvider(BaseProvider): parentpath = urljoin(thispath, '.') child_links.append({ 'rel': 'parent', - 'href': '{}?f=json'.format(parentpath), + 'href': f'{parentpath}?f=json', 'type': 'application/json' }) child_links.append({ @@ -93,7 +93,7 @@ class HateoasProvider(BaseProvider): content = { 'links': [{ 'rel': 'root', - 'href': '{}?f=json'.format(root_link), + 'href': f'{root_link}?f=json', 'type': 'application/json' }, { 'rel': 'root', @@ -101,7 +101,7 @@ class HateoasProvider(BaseProvider): 'type': 'text/html' }, { 'rel': 'self', - 'href': '{}?f=json'.format(thispath), + 'href': f'{thispath}?f=json', 'type': 'application/json', }, { 'rel': 'self', @@ -113,19 +113,19 @@ class HateoasProvider(BaseProvider): LOGGER.debug('Checking if path exists as Catalog, Collection or Asset') try: - jsondata = _get_json_data('{}/catalog.json'.format(data_path)) + jsondata = _get_json_data(f'{data_path}/catalog.json') resource_type = 'Catalog' except Exception: try: - jsondata = _get_json_data('{}/collection.json'.format(data_path)) # noqa + jsondata = _get_json_data(f'{data_path}/collection.json') resource_type = 'Collection' except Exception: try: filename = os.path.basename(data_path) - jsondata = _get_json_data('{}/{}.json'.format(data_path, filename)) # noqa + jsondata = _get_json_data(f'{data_path}/{filename}.json') resource_type = 'Assets' except Exception: - msg = 'Resource does not exist: {}'.format(data_path) + msg = f'Resource does not exist: {data_path}' LOGGER.error(msg) raise ProviderNotFoundError(msg) @@ -183,7 +183,7 @@ class HateoasProvider(BaseProvider): return content def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' def _get_json_data(jsonpath): diff --git a/pygeoapi/provider/mapscript_.py b/pygeoapi/provider/mapscript_.py index c638db9..a459b1e 100644 --- a/pygeoapi/provider/mapscript_.py +++ b/pygeoapi/provider/mapscript_.py @@ -64,7 +64,7 @@ class MapScriptProvider(BaseProvider): self.styles = [] self.default_format = 'png' - LOGGER.debug('MapScript version: {}'.format(mapscript.MS_VERSION)) + LOGGER.debug(f'MapScript version: {mapscript.MS_VERSION}') try: LOGGER.debug('Creating new mapObj and layerObj') @@ -91,7 +91,7 @@ class MapScriptProvider(BaseProvider): self._layer.setProjection(self._epsg2projstring(self.crs)) - LOGGER.debug('Layer projection: {}'.format(self._layer.getProjection())) # noqa + LOGGER.debug(f'Layer projection: {self._layer.getProjection()}') if 'style' in self.options: if self.options['style'].endswith(('xml', 'sld')): @@ -135,7 +135,7 @@ class MapScriptProvider(BaseProvider): try: image_obj_format = IMAGE_FORMATS[format_] except KeyError: - LOGGER.error('Bad output format: {}'.format(image_obj_format)) + LOGGER.error(f'Bad output format: {image_obj_format}') raise ProviderQueryError('Bad image format') LOGGER.debug('Setting output map CRS') @@ -167,8 +167,8 @@ class MapScriptProvider(BaseProvider): if self.time_field is None: LOGGER.debug('collection is not time enabled') else: - fe = '{} = "{}"'.format(self.time_field, datetime_) - LOGGER.debug('Setting temporal filter: {}'.format(fe)) + fe = f'{self.time_field} = "{datetime_}"' + LOGGER.debug(f'Setting temporal filter: {fe}') self._layer.setFilter(fe) LOGGER.debug('Setting output image properties') @@ -185,7 +185,7 @@ class MapScriptProvider(BaseProvider): self._map.setProjection(map_crs) self._map.setConfigOption('MS_NONSQUARE', 'yes') - LOGGER.debug('Mapfile: {}'.format(self._map.convertToString())) + LOGGER.debug(f'Mapfile: {self._map.convertToString()}') try: img = self._map.draw() except MapServerError as err: @@ -209,4 +209,4 @@ class MapScriptProvider(BaseProvider): return prj.ExportToProj4().strip() def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' diff --git a/pygeoapi/provider/mongo.py b/pygeoapi/provider/mongo.py index 8f881a7..3d5a211 100644 --- a/pygeoapi/provider/mongo.py +++ b/pygeoapi/provider/mongo.py @@ -3,7 +3,7 @@ # Authors: Timo Tuunanen # # Copyright (c) 2019 Timo Tuunanen -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -60,7 +60,7 @@ class MongoProvider(BaseProvider): super().__init__(provider_def) - LOGGER.info('Mongo source config: {}'.format(self.data)) + LOGGER.info(f'Mongo source config: {self.data}') dbclient = MongoClient(self.data) self.featuredb = dbclient.get_default_database() @@ -167,7 +167,7 @@ class MongoProvider(BaseProvider): if featurelist: return featurelist[0] else: - err = 'item {} not found'.format(identifier) + err = f'item {identifier} not found' LOGGER.error(err) raise ProviderItemNotFoundError(err) diff --git a/pygeoapi/provider/mvt.py b/pygeoapi/provider/mvt.py index 6485f84..38651b9 100644 --- a/pygeoapi/provider/mvt.py +++ b/pygeoapi/provider/mvt.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Francesco Bartoli +# Authors: Tom Kralidis # # Copyright (c) 2020 Francesco Bartoli +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -60,23 +62,15 @@ class MVTProvider(BaseTileProvider): super().__init__(provider_def) if is_url(self.data): url = urlparse(self.data) - baseurl = '{}://{}'.format(url.scheme, url.netloc) + baseurl = f'{url.scheme}://{url.netloc}' param_type = '?f=mvt' - layer = '/{}'.format(self.get_layer()) + layer = f'/{self.get_layer()}' LOGGER.debug('Extracting layer name from URL') - LOGGER.debug('Layer: {}'.format(layer)) + LOGGER.debug(f'Layer: {layer}') - tilepath = '{}/tiles'.format(layer) - servicepath = \ - '{}/{{{}}}/{{{}}}/{{{}}}/{{{}}}{}'.format( - tilepath, - 'tileMatrixSetId', - 'tileMatrix', - 'tileRow', - 'tileCol', - param_type - ) + tilepath = f'{layer}/tiles' + servicepath = f'{tilepath}/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}{param_type}' # noqa self._service_url = url_join(baseurl, servicepath) @@ -86,19 +80,19 @@ class MVTProvider(BaseTileProvider): else: data_path = Path(self.data) if not data_path.exists(): - msg = 'Service does not exist: {}'.format(self.data) + msg = f'Service does not exist: {self.data}' LOGGER.error(msg) raise ProviderConnectionError(msg) self._service_url = data_path metadata_path = data_path.joinpath('metadata.json') if not metadata_path.exists(): - msg = 'Service metadata does not exist: {}'.format( - metadata_path.name) + msg = f'Service metadata does not exist: {metadata_path.name}' + LOGGER.error(msg) LOGGER.warning(msg) self._service_metadata_url = metadata_path def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' @property def service_url(self): @@ -117,8 +111,7 @@ class MVTProvider(BaseTileProvider): if ('/{z}/{x}/{y}' not in url.path and '/{z}/{y}/{x}' not in url.path): - msg = 'This url template is not supported yet: {}'.format( - url.path) + msg = f'This url template is not supported yet: {url.path}' LOGGER.error(msg) raise ProviderConnectionError(msg) @@ -158,18 +151,11 @@ class MVTProvider(BaseTileProvider): """ url = urlparse(self.data) - baseurl = baseurl or '{}://{}'.format(url.scheme, url.netloc) + baseurl = baseurl or f'{url.scheme}://{url.netloc}' # @TODO: support multiple types tile_type = tile_type or self.format_type - servicepath = \ - servicepath or \ - '{}/tiles/{{{}}}/{{{}}}/{{{}}}/{{{}}}{}'.format( - url.path.split('/{z}/{x}/{y}')[0], - 'tileMatrixSetId', - 'tileMatrix', - 'tileRow', - 'tileCol', - tile_type) + basepath = url.path.split('/{z}/{x}/{y}')[0] + servicepath = servicepath or f'{basepath}/tiles/{{tileMatrixSetId}}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}{tile_type}' # noqa if servicepath.startswith(baseurl): self._service_url = servicepath @@ -185,18 +171,18 @@ class MVTProvider(BaseTileProvider): 'type': 'application/json', 'rel': 'self', 'title': 'This collection as multi vector tilesets', - 'href': '{}?f=json'.format(tile_matrix_set), + 'href': f'{tile_matrix_set}?f=json' }, { 'type': self.mimetype, 'rel': 'item', 'title': 'This collection as multi vector tiles', - 'href': self.service_url, + 'href': self.service_url }, { 'type': 'application/json', 'rel': 'describedby', 'title': 'Collection metadata in TileJSON format', - 'href': '{}?f=json'.format(self.service_metadata_url), + 'href': f'{self.service_metadata_url}?f=json' } ] } @@ -220,36 +206,32 @@ class MVTProvider(BaseTileProvider): format_ = self.format_type if is_url(self.data): url = urlparse(self.data) - base_url = '{}://{}'.format(url.scheme, url.netloc) + base_url = f'{url.scheme}://{url.netloc}' + + if url.query: + url_query = f'?{url.query}' + else: + url_query = '' + with requests.Session() as session: session.get(base_url) # There is a "." in the url path if '.' in url.path: - resp = session.get( - '{base_url}/{lyr}/{z}/{y}/{x}.{f}{q}'.format( - base_url=base_url, lyr=layer, - z=z, y=y, x=x, f=format_, q="?" + url.query - if url.query else '')) + resp = session.get(f'{base_url}/{layer}/{z}/{y}/{x}.{f}{url_query}') # noqa # There is no "." in the url )e.g. elasticsearch) else: - resp = session.get( - '{base_url}/{lyr}/{z}/{y}/{x}{q}'.format( - base_url=base_url, lyr=layer, - z=z, y=y, x=x, q="?" + url.query - if url.query else '')) + resp = session.get(f'{base_url}/{layer}/{z}/{y}/{x}{url_query}') # noqa resp.raise_for_status() return resp.content else: if not isinstance(self.service_url, Path): - msg = 'Wrong data path configuration: {}'.format( - self.service_url) + msg = f'Wrong data path configuration: {self.service_url}' LOGGER.error(msg) raise ProviderConnectionError(msg) else: try: - with open(self.service_url.joinpath( - '{z}/{y}/{x}.{f}'.format( - z=z, y=y, x=x, f=format_)), 'rb') as tile: + service_url_path = self.service_url.joinpath(f'{z}/{y}/{x}.{format_}') # noqa + with open(service_url_path) as tile: return tile.read() except FileNotFoundError as err: raise ProviderTileNotFoundError(err) @@ -272,28 +254,25 @@ class MVTProvider(BaseTileProvider): if is_url(self.data): url = urlparse(self.data) - base_url = '{}://{}'.format(url.scheme, url.netloc) + base_url = f'{url.scheme}://{url.netloc}' with requests.Session() as session: session.get(base_url) - resp = session.get('{base_url}/{lyr}/metadata.json'.format( - base_url=base_url, lyr=layer)) + resp = session.get(f'{base_url}/{layer}/metadata.json') resp.raise_for_status() metadata_json_content = resp.json() else: if not isinstance(self.service_metadata_url, Path): - msg = 'Wrong data path configuration: {}'.format( - self.service_metadata_url) + msg = f'Wrong data path configuration: {self.service_metadata_url}' # noqa LOGGER.error(msg) raise ProviderConnectionError(msg) + if self.service_metadata_url.exists(): with open(self.service_metadata_url, 'r') as md_file: metadata_json_content = json.loads(md_file.read()) service_url = urljoin( server_url, - 'collections/{}/tiles/{}/{{{}}}/{{{}}}/{{{}}}{}'.format( - dataset, tileset, 'tileMatrix', - 'tileRow', 'tileCol', '?f=mvt')) + f'collections/{dataset}/tiles/{tileset}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}?f=mvt') # noqa content = {} if metadata_format == TilesMetadataFormat.TILEJSON: @@ -304,8 +283,7 @@ class MVTProvider(BaseTileProvider): metadata_json_content["json"])["vector_layers"] return content.dict() else: - msg = 'No tiles metadata json available: {}'.format( - self.service_metadata_url) + msg = f'No tiles metadata json available: {self.service_metadata_url}' # noqa LOGGER.error(msg) raise ProviderConnectionError(msg) elif metadata_format == TilesMetadataFormat.CUSTOMJSON: @@ -315,8 +293,7 @@ class MVTProvider(BaseTileProvider): content['json'] = json.loads(metadata_json_content['json']) return content else: - msg = 'No custom JSON for tiles metadata available: {}'.format( - self.service_metadata_url) + msg = f'No custom JSON for tiles metadata available: {self.service_metadata_url}' # noqa LOGGER.error(msg) raise ProviderConnectionError(msg) else: @@ -336,9 +313,7 @@ class MVTProvider(BaseTileProvider): links = [] service_url_link_type = "application/vnd.mapbox-vector-tile" - service_url_link_title = "{} vector tiles for {}".format( - tileset, layer - ) + service_url_link_title = f'{tileset} vector tiles for {layer}' service_url_link = LinkType(href=service_url, rel="item", type=service_url_link_type, title=service_url_link_title) diff --git a/pygeoapi/provider/ogr.py b/pygeoapi/provider/ogr.py index 3d2bbb1..d5d331b 100644 --- a/pygeoapi/provider/ogr.py +++ b/pygeoapi/provider/ogr.py @@ -5,7 +5,7 @@ # # Copyright (c) 2019 Just van den Broecke # Copyright (c) 2020 Francesco Bartoli -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -172,7 +172,7 @@ class OGRProvider(BaseProvider): self._load_source_helper(self.data_def['source_type']) # Layer name is required - self.layer_name = provider_def.get('layer', None) + self.layer_name = provider_def.get('layer') if not self.layer_name: msg = 'Need explicit \'layer\' attr in provider config' LOGGER.error(msg) @@ -193,7 +193,7 @@ class OGRProvider(BaseProvider): source_type = self.data_def['source_type'] self.driver = self.ogr.GetDriverByName(source_type) if not self.driver: - msg = 'No Driver for Source: {}'.format(source_type) + msg = f'No Driver for Source: {source_type}' LOGGER.error(msg) raise Exception(msg) if self.open_options: @@ -206,8 +206,7 @@ class OGRProvider(BaseProvider): LOGGER.error(err) raise ProviderConnectionError(err) except Exception: - msg = 'Ignore errors during the connection for Driver \ - {}'.format(source_type) + msg = f'Ignore errors during the connection for Driver {source_type}' # noqa LOGGER.error(msg) self.conn = _ignore_gdal_error( self.gdal, 'OpenEx', self.data_def['source'], @@ -220,8 +219,7 @@ class OGRProvider(BaseProvider): LOGGER.error(err) raise ProviderConnectionError(err) except Exception: - msg = 'Ignore errors during the connection for Driver \ - {}'.format(source_type) + msg = f'Ignore errors during the connection for Driver {source_type}' # noqa LOGGER.error(msg) # ignore errors for ESRIJSON not having geometry member # see https://github.com/OSGeo/gdal/commit/38b0feed67f80ded32be6c508323d862e1a14474 # noqa @@ -323,12 +321,10 @@ class OGRProvider(BaseProvider): if bbox: LOGGER.debug('processing bbox parameter') - minx, miny, maxx, maxy = bbox + minx, miny, maxx, maxy = [float(b) for b in bbox] - wkt = "POLYGON (({minx} {miny},{minx} {maxy},{maxx} {maxy}," \ - "{maxx} {miny},{minx} {miny}))".format( - minx=float(minx), miny=float(miny), - maxx=float(maxx), maxy=float(maxy)) + wkt = f"POLYGON (({minx} {miny},{minx} {maxy},{maxx} {maxy}," \ + "{maxx} {miny},{minx} {miny}))" polygon = self.ogr.CreateGeometryFromWkt(wkt) if self.transform_in: @@ -343,10 +339,7 @@ class OGRProvider(BaseProvider): LOGGER.debug('processing properties') attribute_filter = ' and '.join( - map( - lambda x: '{} = \'{}\''.format(x[0], x[1]), - properties - ) + map(lambda x: f'{x[0]} = \'{x[1]}\'', properties) ) LOGGER.debug(attribute_filter) @@ -389,11 +382,10 @@ class OGRProvider(BaseProvider): """ result = None try: - LOGGER.debug('Fetching identifier {}'.format(identifier)) + LOGGER.debug(f'Fetching identifier {identifier}') layer = self._get_layer() - layer.SetAttributeFilter("{field} = '{id}'".format( - field=self.id_field, id=identifier)) + layer.SetAttributeFilter(f"{self.id_field} = '{identifier}'") ogr_feature = self._get_next_feature(layer, identifier) result = self._ogr_feature_to_json(ogr_feature) @@ -417,7 +409,7 @@ class OGRProvider(BaseProvider): return result def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' def _load_source_helper(self, source_type): """ @@ -442,9 +434,9 @@ class OGRProvider(BaseProvider): def _get_next_feature(self, layer, feature_id): try: if layer.GetFeatureCount() == 0: - LOGGER.error("item {} is not found".format(feature_id)) - raise ProviderItemNotFoundError( - "item {} not found".format(feature_id)) + msg = f"item {feature_id} is not found" + LOGGER.error(msg) + raise ProviderItemNotFoundError(msg) # Ignore gdal error next_feature = _ignore_gdal_error(layer, 'GetNextFeature') if next_feature: @@ -455,8 +447,7 @@ class OGRProvider(BaseProvider): ) else: raise RuntimeError( - "GDAL has returned a null feature for item {}".format( - feature_id)) + f"GDAL has returned a null feature for item {feature_id}") return next_feature except RuntimeError as gdalerr: LOGGER.error(self.gdal.GetLastErrorMsg()) @@ -476,11 +467,7 @@ class OGRProvider(BaseProvider): self.id_field, json_feature['id'] ) except KeyError as err: - LOGGER.error( - "Cannot use configured id_field nor fid as id, err={}".format( - err - ) - ) + LOGGER.error(f"Cannot use configured id_field nor fid as id, err={err}") # noqa return json_feature @@ -578,8 +565,7 @@ class SourceHelper: layer = self.provider.conn.GetLayerByName(self.provider.layer_name) if not layer: - msg = 'Cannot get Layer {} from OGR Source'.\ - format(self.provider.layer_name) + msg = f'Cannot get Layer {self.provider.layer_name} from OGR Source' # noqa LOGGER.error(msg) raise Exception(msg) @@ -635,8 +621,7 @@ class CommonSourceHelper(SourceHelper): try: self.provider.conn.ReleaseResultSet(self.result_set) except Exception as err: - msg = 'ReleaseResultSet exception for Layer {}'.format( - self.provider.layer_name) + msg = f'ReleaseResultSet exception for Layer {self.provider.layer_name}' # noqa LOGGER.error(msg, err) finally: self.result_set = None @@ -671,10 +656,7 @@ class CommonSourceHelper(SourceHelper): self.close() - sql = 'SELECT * FROM "{ds_name}" LIMIT {limit} OFFSET {offset}'.format( - ds_name=self.provider.layer_name, - limit=self.limit, - offset=self.offset) + sql = f'SELECT * FROM "{self.provider.layer_name}" LIMIT {self.limit} OFFSET {self.offset}' # noqa self.result_set = self.provider.conn.ExecuteSQL(sql) # Reset since needs to be set each time explicitly @@ -682,8 +664,7 @@ class CommonSourceHelper(SourceHelper): self.limit = -1 if not self.result_set: - msg = 'Cannot get Layer {} via ExecuteSQL'.format( - self.provider.layer_name) + msg = f'Cannot get Layer {self.provider.layer_name} via ExecuteSQL' LOGGER.error(msg) raise Exception(msg) @@ -735,10 +716,7 @@ class ESRIJSONHelper(CommonSourceHelper): self.close() - sql = "SELECT * FROM {ds_name} LIMIT {limit} OFFSET {offset}".format( - ds_name=self.provider.layer_name, - limit=self.limit, - offset=self.offset) + sql = f"SELECT * FROM {self.provider.layer_name} LIMIT {self.limit} OFFSET {self.offset}" # noqa self.result_set = self.provider.conn.ExecuteSQL(sql) # Reset since needs to be set each time explicitly @@ -746,8 +724,7 @@ class ESRIJSONHelper(CommonSourceHelper): self.limit = -1 if not self.result_set: - msg = 'Cannot get Layer {} via ExecuteSQL'.format( - self.provider.layer_name) + msg = f'Cannot get Layer {self.provider.layer_name} via ExecuteSQL' LOGGER.error(msg) raise Exception(msg) diff --git a/pygeoapi/provider/postgresql.py b/pygeoapi/provider/postgresql.py index d523f73..7af632c 100644 --- a/pygeoapi/provider/postgresql.py +++ b/pygeoapi/provider/postgresql.py @@ -7,7 +7,7 @@ # Colin Blackburn # # Copyright (c) 2018 Jorge Samuel Mendes de Jesus -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # Copyright (c) 2022 John A Stevenson and Colin Blackburn # # Permission is hereby granted, free of charge, to any person @@ -90,15 +90,15 @@ class PostgreSQLProvider(BaseProvider): self.id_field = provider_def['id_field'] self.geom = provider_def.get('geom_field', 'geom') - LOGGER.debug('Name: {}'.format(self.name)) - LOGGER.debug('Table: {}'.format(self.table)) - LOGGER.debug('ID field: {}'.format(self.id_field)) - LOGGER.debug('Geometry field: {}'.format(self.geom)) + LOGGER.debug(f'Name: {self.name}') + LOGGER.debug(f'Table: {self.table}') + LOGGER.debug(f'ID field: {self.id_field}') + LOGGER.debug(f'Geometry field: {self.geom}') # Read table information from database self._store_db_parameters(provider_def['data']) self._engine, self.table_model = self._get_engine_and_table_model() - LOGGER.debug('DB connection: {}'.format(repr(self._engine.url))) + LOGGER.debug(f'DB connection: {repr(self._engine.url)}') self.fields = self.get_fields() def query(self, offset=0, limit=10, resulttype='results', diff --git a/pygeoapi/provider/rasterio_.py b/pygeoapi/provider/rasterio_.py index 475481e..7414fcc 100644 --- a/pygeoapi/provider/rasterio_.py +++ b/pygeoapi/provider/rasterio_.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -132,7 +132,7 @@ class RasterioProvider(BaseProvider): for i, dtype, nodataval in zip(self._data.indexes, self._data.dtypes, self._data.nodatavals): - LOGGER.debug('Determing rangetype for band {}'.format(i)) + LOGGER.debug(f'Determing rangetype for band {i}') name, units = None, None if self._data.units[i-1] is None: @@ -146,12 +146,11 @@ class RasterioProvider(BaseProvider): 'type': 'Quantity', 'name': name, 'encodingInfo': { - 'dataType': 'http://www.opengis.net/def/dataType/OGC/0/{}'.format(dtype) # noqa + 'dataType': f'http://www.opengis.net/def/dataType/OGC/0/{dtype}' # noqa }, 'nodata': nodataval, 'uom': { - 'id': 'http://www.opengis.net/def/uom/UCUM/{}'.format( - units), + 'id': f'http://www.opengis.net/def/uom/UCUM/{units}', 'type': 'UnitReference', 'code': units }, @@ -176,7 +175,7 @@ class RasterioProvider(BaseProvider): """ bands = properties - LOGGER.debug('Bands: {}, subsets: {}'.format(bands, subsets)) + LOGGER.debug(f'Bands: {bands}, subsets: {subsets}') args = { 'indexes': None @@ -227,10 +226,8 @@ class RasterioProvider(BaseProvider): minx2, miny2 = t.transform(minx, miny) maxx2, maxy2 = t.transform(maxx, maxy) - LOGGER.debug('Source coordinates: {}'.format( - [minx, miny, maxx, maxy])) - LOGGER.debug('Destination coordinates: {}'.format( - [minx2, miny2, maxx2, maxy2])) + LOGGER.debug(f'Source coordinates: {minx}, {miny}, {maxx}, {maxy}') # noqa + LOGGER.debug(f'Destination: {minx2}, {miny2}, {maxx2}, {maxy2}') # noqa shapes = [{ 'type': 'Polygon', @@ -371,7 +368,7 @@ class RasterioProvider(BaseProvider): else: bands_select = metadata['bands'] - LOGGER.debug('bands selected: {}'.format(bands_select)) + LOGGER.debug(f'bands selected: {bands_select}') for bs in bands_select: pm = _get_parameter_metadata( self._data.profile['driver'], self._data.tags(bs)) @@ -437,10 +434,7 @@ class RasterioProvider(BaseProvider): if self._data.crs is not None: if self._data.crs.is_projected: - properties['bbox_crs'] = '{}/{}'.format( - 'http://www.opengis.net/def/crs/OGC/1.3/', - self._data.crs.to_epsg()) - + properties['bbox_crs'] = f'http://www.opengis.net/def/crs/OGC/1.3/{self._data.crs.to_epsg()}' # noqa properties['x_axis_label'] = 'x' properties['y_axis_label'] = 'y' properties['bbox_units'] = self._data.crs.linear_units diff --git a/pygeoapi/provider/sensorthings.py b/pygeoapi/provider/sensorthings.py index d045cc4..692a2c8 100644 --- a/pygeoapi/provider/sensorthings.py +++ b/pygeoapi/provider/sensorthings.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Benjamin Webb +# Authors: Tom Kralidis # # Copyright (c) 2021 Benjamin Webb +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -119,8 +121,8 @@ class SensorThingsProvider(BaseProvider): try: results = r.json()['value'][0] except JSONDecodeError as err: - LOGGER.error('Entity {} error: {}'.format(self.entity, err)) - LOGGER.error('Bad url response at {}'.format(r.url)) + LOGGER.error(f'Entity {self.entity} error: {err}') + LOGGER.error(f'Bad url response at {r.url}') raise ProviderQueryError(err) for (n, v) in results.items(): @@ -262,7 +264,7 @@ class SensorThingsProvider(BaseProvider): hits_ = 1 if identifier else min(limit, response.get('@iot.count')) while len(v) < hits_: LOGGER.debug('Fetching next set of values') - next_ = response.get('@iot.nextLink', None) + next_ = response.get('@iot.nextLink') if next_ is None: break else: @@ -415,7 +417,6 @@ class SensorThingsProvider(BaseProvider): for k, v in entity.items(): # Create intra links - path_ = 'collections/{}/items/{}' ks = f'{k}s' if self.uri_field is not None and k in ['properties']: uri = v.get(self.uri_field, '') @@ -426,14 +427,9 @@ class SensorThingsProvider(BaseProvider): v[i] = _v['properties'][self._linkables[k]['u']] continue for i, _v in enumerate(v): - id = _v[self.id_field] - id = f"'{id}'" if isinstance(id, str) else str(id) - v[i] = url_join( - self._rel_link, - path_.format( - self._linkables[k]['n'], id - ) - ) + id_ = _v[self.id_field] + id_ = f"'{id_}'" if isinstance(id_, str) else str(id_) + v[i] = url_join(self._rel_link, f"collections/{self._linkables[k]['n']}/items/{id_}") # noqa elif ks in self._linkables.keys(): if self._linkables[ks]['u'] != '': @@ -443,13 +439,10 @@ class SensorThingsProvider(BaseProvider): id = f"'{id}'" if isinstance(id, str) else str(id) entity[k] = url_join( self._rel_link, - path_.format( - self._linkables[ks]['n'], id - ) - ) + f"collections/{self._linkables[ks]['n']}/items/{id_}") # Make properties block - if entity.get('properties', None): + if entity.get('properties'): entity.update(entity.pop('properties')) if keys: @@ -465,4 +458,4 @@ class SensorThingsProvider(BaseProvider): return entity def __repr__(self): - return ' {}, {}'.format(self.data, self.entity) + return f' {self.data}, {self.entity}' diff --git a/pygeoapi/provider/socrata.py b/pygeoapi/provider/socrata.py index 6cb17ab..a961d20 100644 --- a/pygeoapi/provider/socrata.py +++ b/pygeoapi/provider/socrata.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Benjamin Webb +# Authors: Tom Kralidis # # Copyright (c) 2022 Benjamin Webb +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -79,7 +81,7 @@ class SODAServiceProvider(BaseProvider): [dataset] = self.client.datasets(ids=[self.resource_id]) resource = dataset['resource'] except json.decoder.JSONDecodeError as err: - LOGGER.error('Bad response at {}'.format(self.data)) + LOGGER.error(f'Bad response at {self.data}') raise ProviderConnectionError(err) fields = self.properties or resource[FIELD_NAME] @@ -269,4 +271,4 @@ class SODAServiceProvider(BaseProvider): return int(response['count']) def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' diff --git a/pygeoapi/provider/sqlite.py b/pygeoapi/provider/sqlite.py index cec1f56..8e6caf8 100644 --- a/pygeoapi/provider/sqlite.py +++ b/pygeoapi/provider/sqlite.py @@ -5,7 +5,7 @@ # Francesco Bartoli # # Copyright (c) 2018 Jorge Samuel Mendes de Jesus -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # Copyright (c) 2020 Francesco Bartoli # # Permission is hereby granted, free of charge, to any person @@ -68,10 +68,10 @@ class SQLiteGPKGProvider(BaseProvider): self.geom_col = None LOGGER.debug('Setting SQLite properties:') - LOGGER.debug('Data source: {}'.format(self.data)) - LOGGER.debug('Name: {}'.format(self.name)) - LOGGER.debug('ID_field: {}'.format(self.id_field)) - LOGGER.debug('Table: {}'.format(self.table)) + LOGGER.debug(f'Data source: {self.data}') + LOGGER.debug(f'Name: {self.name}') + LOGGER.debug(f'ID_field: {self.id_field}') + LOGGER.debug(f'Table: {self.table}') self.cursor = self.__load() @@ -89,7 +89,7 @@ class SQLiteGPKGProvider(BaseProvider): if not self.fields: results = self.cursor.execute( - 'PRAGMA table_info({})'.format(self.table)).fetchall() + f'PRAGMA table_info({self.table})').fetchall() for item in results: self.fields[item['name']] = {'type': item['type']} @@ -116,14 +116,13 @@ class SQLiteGPKGProvider(BaseProvider): if properties: where_clause += " AND ".join( - ["{}=?".format(k) for k, v in properties]) + [f"{k}=?" for k, v in properties]) where_values += where_values + tuple((v for k, v in properties)) if bbox: if properties: where_clause += " AND " - where_clause += " Intersects({}, \ - BuildMbr(?,?,?,?)) ".format(self.geom_col) + where_clause += f" Intersects({self.geom_col}, BuildMbr(?,?,?,?)) " where_values += tuple(bbox) # WHERE continent=? : ('Europe',) return where_clause, where_values @@ -144,7 +143,7 @@ class SQLiteGPKGProvider(BaseProvider): 'type': 'Feature' } feature["geometry"] = json.loads( - rd.pop('AsGeoJSON({})'.format(self.geom_col)) + rd.pop(f'AsGeoJSON({self.geom_col})') ) if skip_geometry: feature["geometry"] = None @@ -185,7 +184,7 @@ class SQLiteGPKGProvider(BaseProvider): try: conn.enable_load_extension(True) except AttributeError as err: - LOGGER.error('Extension loading not enabled: {}'.format(err)) + LOGGER.error(f'Extension loading not enabled: {err}') raise ProviderConnectionError() conn.row_factory = sqlite3.Row @@ -193,10 +192,9 @@ class SQLiteGPKGProvider(BaseProvider): # conn.set_trace_callback(LOGGER.debug) cursor = conn.cursor() try: - cursor.execute("SELECT load_extension('{}')".format( - SPATIALITE_EXTENSION)) + cursor.execute(f"SELECT load_extension('{SPATIALITE_EXTENSION}')") except sqlite3.OperationalError as err: - LOGGER.error('Extension loading error: {}'.format(err)) + LOGGER.error(f'Extension loading error: {err}') raise ProviderConnectionError() result = cursor.fetchall() @@ -229,10 +227,10 @@ class SQLiteGPKGProvider(BaseProvider): self.geom_col = "geometry" try: - cursor.execute('PRAGMA table_info({})'.format(self.table)) + cursor.execute(f'PRAGMA table_info({self.table})') result = cursor.fetchall() except sqlite3.OperationalError: - LOGGER.error('Couldnt find table: {}'.format(self.table)) + LOGGER.error(f'Couldnt find table: {self.table}') raise ProviderConnectionError() try: @@ -245,11 +243,10 @@ class SQLiteGPKGProvider(BaseProvider): self.columns = [item[1] for item in result if item[1] not in [self.geom_col, self.geom_col.upper()]] - self.columns = ','.join(self.columns)+',AsGeoJSON({})'.format( - self.geom_col) + self.columns = ','.join(self.columns)+f',AsGeoJSON({self.geom_col})' if self.application_id: - self.table = "vgpkg_{}".format(self.table) + self.table = f"vgpkg_{self.table}" return cursor @@ -282,23 +279,21 @@ class SQLiteGPKGProvider(BaseProvider): if resulttype == 'hits': - sql_query = "SELECT COUNT(*) as hits FROM {} {} ".format( - self.table, where_clause) + sql_query = f"SELECT COUNT(*) as hits FROM {self.table} {where_clause} " # noqa res = self.cursor.execute(sql_query, where_values) hits = res.fetchone()["hits"] return self.__response_feature_hits(hits) - sql_query = "SELECT DISTINCT {} from \ - {} {} limit ? offset ?".format( - self.columns, self.table, where_clause) + sql_query = f"SELECT DISTINCT {self.columns} from \ + {self.table} {where_clause} limit ? offset ?" end_index = offset + limit - LOGGER.debug('SQL Query: {}'.format(sql_query)) - LOGGER.debug('Start Index: {}'.format(offset)) - LOGGER.debug('End Index: {}'.format(end_index)) + LOGGER.debug(f'SQL Query: {sql_query}') + LOGGER.debug(f'Start Index: {offset}') + LOGGER.debug(f'End Index: {end_index}') row_data = self.cursor.execute( sql_query, where_values + (limit, offset)) @@ -326,12 +321,11 @@ class SQLiteGPKGProvider(BaseProvider): LOGGER.debug('Get item from SQLite/GPKG') - sql_query = 'SELECT {} FROM \ - {} WHERE {}==?;'.format( - self.columns, self.table, self.id_field) + sql_query = f'SELECT {self.columns} FROM \ + {self.table} WHERE {self.id_field}==?;' - LOGGER.debug('SQL Query: {}'.format(sql_query)) - LOGGER.debug('Identifier: {}'.format(identifier)) + LOGGER.debug(f'SQL Query: {sql_query}') + LOGGER.debug(f'Identifier: {identifier}') row_data = self.cursor.execute(sql_query, (identifier, )).fetchone() @@ -339,9 +333,9 @@ class SQLiteGPKGProvider(BaseProvider): if feature: return feature else: - err = 'item {} not found'.format(identifier) + err = f'item {identifier} not found' LOGGER.error(err) raise ProviderItemNotFoundError(err) def __repr__(self): - return ' {}, {}'.format(self.data, self.table) + return f' {self.data}, {self.table}' diff --git a/pygeoapi/provider/tile.py b/pygeoapi/provider/tile.py index 9433b7c..61c61a2 100644 --- a/pygeoapi/provider/tile.py +++ b/pygeoapi/provider/tile.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Francesco Bartoli +# Authors: Tom Kralidis # # Copyright (c) 2020 Francesco Bartoli +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -50,7 +52,7 @@ class BaseTileProvider: self.data = provider_def['data'] self.format_type = provider_def['format']['name'] self.mimetype = provider_def['format']['mimetype'] - self.options = provider_def.get('options', None) + self.options = provider_def.get('options') self.fields = {} def get_layer(self): diff --git a/pygeoapi/provider/tinydb_.py b/pygeoapi/provider/tinydb_.py index f4fb12c..62eae56 100644 --- a/pygeoapi/provider/tinydb_.py +++ b/pygeoapi/provider/tinydb_.py @@ -58,7 +58,7 @@ class TinyDBCatalogueProvider(BaseProvider): super().__init__(provider_def) - LOGGER.debug('Connecting to TinyDB db at {}'.format(self.data)) + LOGGER.debug(f'Connecting to TinyDB db at {self.data}') if not os.path.exists(self.data): msg = 'TinyDB does not exist' @@ -117,7 +117,7 @@ class TinyDBCatalogueProvider(BaseProvider): """ Q = Query() - LOGGER.debug('Query initiated: {}'.format(Q)) + LOGGER.debug(f'Query initiated: {Q}') QUERY = [] @@ -133,7 +133,7 @@ class TinyDBCatalogueProvider(BaseProvider): if bbox: LOGGER.debug('processing bbox parameter') bbox_as_string = ','.join(str(s) for s in bbox) - QUERY.append("Q.properties.extent.spatial.bbox.test(bbox_intersects, '{}')".format(bbox_as_string)) # noqa + QUERY.append(f"Q.properties.extent.spatial.bbox.test(bbox_intersects, '{bbox_as_string}')") # noqa if datetime_ is not None: LOGGER.debug('processing datetime parameter') @@ -146,31 +146,31 @@ class TinyDBCatalogueProvider(BaseProvider): time_begin, time_end = datetime_.split('/') if time_begin != '..': - QUERY.append("(Q.properties[self.time_field]>='{}')".format(time_begin)) # noqa + QUERY.append(f"(Q.properties[self.time_field]>='{time_begin}')") # noqa if time_end != '..': - QUERY.append("(Q.properties[self.time_field]<='{}')".format(time_end)) # noqa + QUERY.append(f"(Q.properties[self.time_field]<='{time_end}')") # noqa else: # time instant LOGGER.debug('detected time instant') - QUERY.append("(Q.properties[self.time_field]=='{}')".format(datetime_)) # noqa + QUERY.append(f"(Q.properties[self.time_field]=='{datetime_}')") # noqa if properties: LOGGER.debug('processing properties') for prop in properties: - QUERY.append("(Q.properties['{}']=='{}')".format(*prop)) + QUERY.append(f"(Q.properties['{prop[0]}']=='{prop[1]}')") if q is not None: for t in q.split(): - QUERY.append("(Q.properties['_metadata-anytext'].search('{}', flags=re.IGNORECASE))".format(t)) # noqa + QUERY.append(f"(Q.properties['_metadata-anytext'].search('{t}', flags=re.IGNORECASE))") # noqa QUERY_STRING = '&'.join(QUERY) - LOGGER.debug('QUERY_STRING: {}'.format(QUERY_STRING)) - SEARCH_STRING = 'self.db.search({})'.format(QUERY_STRING) - LOGGER.debug('SEARCH_STRING: {}'.format(SEARCH_STRING)) + LOGGER.debug(f'QUERY_STRING: {QUERY_STRING}') + SEARCH_STRING = f'self.db.search({QUERY_STRING})' + LOGGER.debug(f'SEARCH_STRING: {SEARCH_STRING}') LOGGER.debug('querying database') if len(QUERY) > 0: - LOGGER.debug('running eval on {}'.format(SEARCH_STRING)) + LOGGER.debug(f'running eval on {SEARCH_STRING}') results = eval(SEARCH_STRING) else: results = self.db.all() @@ -185,11 +185,11 @@ class TinyDBCatalogueProvider(BaseProvider): try: del r['properties'][e] except KeyError: - LOGGER.debug('Missing excluded property {}'.format(e)) + LOGGER.debug(f'Missing excluded property {e}') len_results = len(results) - LOGGER.debug('Results found: {}'.format(len_results)) + LOGGER.debug(f'Results found: {len_results}') if len_results > limit: returned = limit @@ -221,7 +221,7 @@ class TinyDBCatalogueProvider(BaseProvider): :returns: `dict` of single record """ - LOGGER.debug('Fetching identifier {}'.format(identifier)) + LOGGER.debug(f'Fetching identifier {identifier}') record = self.db.get(Query().id == identifier) @@ -232,7 +232,7 @@ class TinyDBCatalogueProvider(BaseProvider): try: del record['properties'][e] except KeyError: - LOGGER.debug('Missing excluded property {}'.format(e)) + LOGGER.debug(f'Missing excluded property {e}') return record @@ -256,10 +256,10 @@ class TinyDBCatalogueProvider(BaseProvider): LOGGER.debug('Missing title and description') json_data['properties']['_metadata_anytext'] = '' - LOGGER.debug('Inserting data with identifier {}'.format(identifier)) + LOGGER.debug(f'Inserting data with identifier {identifier}') result = self.db.insert(json_data) - LOGGER.debug('Item added with internal id {}'.format(result)) + LOGGER.debug(f'Item added with internal id {result}') return identifier @@ -273,7 +273,7 @@ class TinyDBCatalogueProvider(BaseProvider): :returns: `bool` of update result """ - LOGGER.debug('Updating item {}'.format(identifier)) + LOGGER.debug(f'Updating item {identifier}') identifier, json_data = self._load_and_prepare_item( item, identifier, raise_if_exists=False) self.db.update(json_data, where('id') == identifier) @@ -289,7 +289,7 @@ class TinyDBCatalogueProvider(BaseProvider): :returns: `bool` of deletion result """ - LOGGER.debug('Deleting item {}'.format(identifier)) + LOGGER.debug(f'Deleting item {identifier}') self.db.remove(where('id') == identifier) return True @@ -307,7 +307,7 @@ class TinyDBCatalogueProvider(BaseProvider): return True def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' def bbox_intersects(record_bbox, input_bbox): @@ -323,8 +323,8 @@ def bbox_intersects(record_bbox, input_bbox): bbox1 = record_bbox[0] bbox2 = [float(c) for c in input_bbox.split(',')] - LOGGER.debug('Record bbox: {}'.format(bbox1)) - LOGGER.debug('Input bbox: {}'.format(bbox2)) + LOGGER.debug(f'Record bbox: {bbox1}') + LOGGER.debug(f'Input bbox: {bbox2}') # any point in bbox1 should be in bbox2 bbox_tests = [ diff --git a/pygeoapi/provider/wms_facade.py b/pygeoapi/provider/wms_facade.py index 9af5a6f..5835988 100644 --- a/pygeoapi/provider/wms_facade.py +++ b/pygeoapi/provider/wms_facade.py @@ -61,7 +61,7 @@ class WMSFacadeProvider(BaseProvider): BaseProvider.__init__(self, provider_def) - LOGGER.debug('pyproj version: {}'.format(pyproj.__version__)) + LOGGER.debug(f'pyproj version: {pyproj.__version__}') def query(self, style=None, bbox=[-180, -90, 180, 90], width=500, height=300, crs=4326, datetime_=None, transparent=True, @@ -90,7 +90,7 @@ class WMSFacadeProvider(BaseProvider): [bbox[1], bbox[0], bbox[3], bbox[2]]) else: LOGGER.debug('Reprojecting coordinates') - LOGGER.debug('Output CRS: {}'.format(CRS_CODES[crs])) + LOGGER.debug(f'Output CRS: {CRS_CODES[crs]}') src_crs = pyproj.CRS.from_string('epsg:4326') dest_crs = pyproj.CRS.from_string(CRS_CODES[crs]) @@ -128,7 +128,7 @@ class WMSFacadeProvider(BaseProvider): else: request_url = '?'.join([self.data, urlencode(params)]) - LOGGER.debug('WMS 1.3.0 request url: {}'.format(request_url)) + LOGGER.debug(f'WMS 1.3.0 request url: {request_url}') response = requests.get(request_url) @@ -140,4 +140,4 @@ class WMSFacadeProvider(BaseProvider): return response.content def __repr__(self): - return ' {}'.format(self.data) + return f' {self.data}' diff --git a/pygeoapi/provider/xarray_.py b/pygeoapi/provider/xarray_.py index 8c08d0e..48eec09 100644 --- a/pygeoapi/provider/xarray_.py +++ b/pygeoapi/provider/xarray_.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Gregory Petrochenkov +# Authors: Tom Kralidis # # Copyright (c) 2020 Gregory Petrochenkov +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -153,7 +155,7 @@ class XarrayProvider(BaseProvider): } for name, var in self._data.variables.items(): - LOGGER.debug('Determining rangetype for {}'.format(name)) + LOGGER.debug(f'Determining rangetype for {name}') desc, units = None, None if len(var.shape) >= 3: @@ -167,12 +169,11 @@ class XarrayProvider(BaseProvider): 'type': 'Quantity', 'name': var.attrs.get('long_name') or desc, 'encodingInfo': { - 'dataType': 'http://www.opengis.net/def/dataType/OGC/0/{}'.format(str(var.dtype)) # noqa + 'dataType': f'http://www.opengis.net/def/dataType/OGC/0/{var.dtype}' # noqa }, 'nodata': 'null', 'uom': { - 'id': 'http://www.opengis.net/def/uom/UCUM/{}'.format( - units), + 'id': f'http://www.opengis.net/def/uom/UCUM/{units}', 'type': 'UnitReference', 'code': units }, @@ -219,7 +220,7 @@ class XarrayProvider(BaseProvider): query_params = {} for key, val in subsets.items(): - LOGGER.debug('Processing subset: {}'.format(key)) + LOGGER.debug(f'Processing subset: {key}') if data.coords[key].values[0] > data.coords[key].values[-1]: LOGGER.debug('Reversing slicing from high to low') query_params[key] = slice(val[1], val[0]) @@ -257,7 +258,7 @@ class XarrayProvider(BaseProvider): else: query_params[self.time_field] = datetime_ - LOGGER.debug('Query parameters: {}'.format(query_params)) + LOGGER.debug(f'Query parameters: {query_params}') try: data = data.sel(query_params) except Exception as err: @@ -329,7 +330,7 @@ class XarrayProvider(BaseProvider): tmp_max = data.coords[self.y_field].values if tmp_min > tmp_max: - LOGGER.debug('Reversing direction of {}'.format(self.y_field)) + LOGGER.debug(f'Reversing direction of {self.y_field}') miny = tmp_max maxy = tmp_min @@ -471,9 +472,7 @@ class XarrayProvider(BaseProvider): } if 'crs' in self._data.variables.keys(): - properties['bbox_crs'] = '{}/{}'.format( - 'http://www.opengis.net/def/crs/OGC/1.3/', - self._data.crs.epsg_code) + properties['bbox_crs'] = f'http://www.opengis.net/def/crs/OGC/1.3/{self._data.crs.epsg_code}' # noqa properties['inverse_flattening'] = self._data.crs.\ inverse_flattening @@ -502,11 +501,11 @@ class XarrayProvider(BaseProvider): return { 'id': name, - 'description': attrs.get('long_name', None), - 'unit_label': attrs.get('units', None), - 'unit_symbol': attrs.get('units', None), + 'description': attrs.get('long_name'), + 'unit_label': attrs.get('units'), + 'unit_symbol': attrs.get('units'), 'observed_property_id': name, - 'observed_property_name': attrs.get('long_name', None) + 'observed_property_name': attrs.get('long_name') } def get_time_resolution(self): @@ -542,7 +541,7 @@ class XarrayProvider(BaseProvider): 'seconds': int(ms_difference / 1000) % 60 } - times = ['{} {}'.format(val, key) for key, val + times = [f'{val} {key}' for key, val in time_dict.items() if val > 0] return ', '.join(times) @@ -605,12 +604,17 @@ def _get_zarr_data(data): """ tmp_dir = tempfile.TemporaryDirectory().name - data.to_zarr('{}zarr.zarr'.format(tmp_dir), mode='w') - with zipfile.ZipFile('{}zarr.zarr.zip'.format(tmp_dir), - 'w', zipfile.ZIP_DEFLATED) as zipf: - _zip_dir('{}zarr.zarr'.format(tmp_dir), zipf, os.getcwd()) - zip_file = open('{}zarr.zarr.zip'.format(tmp_dir), 'rb') - return zip_file.read() + + zarr_data_filename = f'{tmp_dir}zarr.zarr' + zarr_zip_filename = f'{tmp_dir}zarr.zarr.zip' + + data.to_zarr(zarr_data_filename, mode='w') + + with zipfile.ZipFile(zarr_zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: # noqa + _zip_dir(zarr_data_filename, zipf, os.getcwd()) + + with open(zarr_zip_filename, 'rb') as fh: + return fh.read() def _convert_float32_to_float64(data): diff --git a/pygeoapi/provider/xarray_edr.py b/pygeoapi/provider/xarray_edr.py index b7a9a0b..7281d81 100644 --- a/pygeoapi/provider/xarray_edr.py +++ b/pygeoapi/provider/xarray_edr.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -77,14 +77,14 @@ class XarrayEDRProvider(BaseEDRProvider, XarrayProvider): query_params = {} - LOGGER.debug('Query parameters: {}'.format(kwargs)) + LOGGER.debug(f'Query parameters: {kwargs}') - LOGGER.debug('Query type: {}'.format(kwargs.get('query_type'))) + LOGGER.debug(f"Query type: {kwargs.get('query_type')}") wkt = kwargs.get('wkt') if wkt is not None: LOGGER.debug('Processing WKT') - LOGGER.debug('Geometry type: {}'.format(wkt.type)) + LOGGER.debug(f'Geometry type: {wkt.type}') if wkt.type == 'Point': query_params[self._coverage_properties['x_axis_label']] = wkt.x query_params[self._coverage_properties['y_axis_label']] = wkt.y @@ -102,13 +102,13 @@ class XarrayEDRProvider(BaseEDRProvider, XarrayProvider): # example of fetching instance passed # TODO: apply accordingly instance = kwargs.get('instance') - LOGGER.debug('instance: {}'.format(instance)) + LOGGER.debug(f'instance: {instance}') datetime_ = kwargs.get('datetime_') if datetime_ is not None: query_params[self._coverage_properties['time_axis_label']] = datetime_ # noqa - LOGGER.debug('query parameters: {}'.format(query_params)) + LOGGER.debug(f'query parameters: {query_params}') try: if select_properties: diff --git a/pygeoapi/starlette_app.py b/pygeoapi/starlette_app.py index cafa772..7bf494b 100644 --- a/pygeoapi/starlette_app.py +++ b/pygeoapi/starlette_app.py @@ -33,6 +33,7 @@ """ Starlette module providing the route paths to the api""" import os +from pathlib import Path import click @@ -53,10 +54,14 @@ if 'PYGEOAPI_CONFIG' not in os.environ: with open(os.environ.get('PYGEOAPI_CONFIG'), encoding='utf8') as fh: CONFIG = yaml_load(fh) -STATIC_DIR = '{}{}static'.format(os.path.dirname(os.path.realpath(__file__)), - os.sep) -if 'templates' in CONFIG['server']: - STATIC_DIR = CONFIG['server']['templates'].get('static', STATIC_DIR) +p = Path(__file__) + +STATIC_DIR = Path(p).parent.resolve() / 'static' + +try: + STATIC_DIR = Path(CONFIG['server']['templates']['static']) +except KeyError: + pass app = Starlette() app.mount('/static', StaticFiles(directory=STATIC_DIR)) @@ -66,11 +71,14 @@ if CONFIG['server'].get('cors', False): from starlette.middleware.cors import CORSMiddleware app.add_middleware(CORSMiddleware, allow_origins=['*']) -OGC_SCHEMAS_LOCATION = CONFIG['server'].get('ogc_schemas_location', None) +try: + OGC_SCHEMAS_LOCATION = Path(CONFIG['server']['ogc_schemas_location']) +except KeyError: + OGC_SCHEMAS_LOCATION = None if (OGC_SCHEMAS_LOCATION is not None and - not OGC_SCHEMAS_LOCATION.startswith('http')): - if not os.path.exists(OGC_SCHEMAS_LOCATION): + not OGC_SCHEMAS_LOCATION.name.startswith('http')): + if not OGC_SCHEMAS_LOCATION.exists(): raise RuntimeError('OGC schemas misconfigured') app.mount('/schemas', StaticFiles(directory=OGC_SCHEMAS_LOCATION)) diff --git a/pygeoapi/templates/_base.html b/pygeoapi/templates/_base.html index 025fbcd..1436fef 100644 --- a/pygeoapi/templates/_base.html +++ b/pygeoapi/templates/_base.html @@ -13,8 +13,8 @@ - - + + diff --git a/pygeoapi/templates/collections/items/item.html b/pygeoapi/templates/collections/items/item.html index d013a54..e10dbf4 100644 --- a/pygeoapi/templates/collections/items/item.html +++ b/pygeoapi/templates/collections/items/item.html @@ -1,5 +1,5 @@ {% extends "_base.html" %} -{% set ptitle = data['properties'][data['title_field']] or ("Item {}".format(data['id'])) %} +{% set ptitle = data['properties'][data['title_field']] or "Item " + data['id'] %} {% block desc %}{{ data.get('properties',{}).get('description', {}) | string | truncate(250) }}{% endblock %} {% block tags %}{{ data['properties'].get('themes', [{}])[0].get('concepts', []) | join(',') }}{% endblock %} {# Optionally renders an img element, otherwise standard value or link rendering #} diff --git a/pygeoapi/util.py b/pygeoapi/util.py index 8abe099..a7eb4dd 100644 --- a/pygeoapi/util.py +++ b/pygeoapi/util.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -34,12 +34,13 @@ from typing import List from datetime import date, datetime, time from decimal import Decimal from enum import Enum -import io import json import logging import mimetypes import os +from pathlib import Path import re +from typing import Any, IO, Union from urllib.request import urlopen from urllib.parse import urlparse @@ -59,14 +60,13 @@ LOGGER = logging.getLogger(__name__) DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' -TEMPLATES = '{}{}templates'.format(os.path.dirname( - os.path.realpath(__file__)), os.sep) +TEMPLATES = Path(__file__).parent.resolve() / 'templates' mimetypes.add_type('text/plain', '.yaml') mimetypes.add_type('text/plain', '.yml') -def dategetter(date_property, collection): +def dategetter(date_property: str, collection: dict) -> str: """ Attempts to obtain a date value from a collection. @@ -77,7 +77,7 @@ def dategetter(date_property, collection): for an open interval using null) """ - value = collection.get(date_property, None) + value = collection.get(date_property) if value is None: return None @@ -85,7 +85,7 @@ def dategetter(date_property, collection): return value.isoformat() -def get_typed_value(value): +def get_typed_value(value: str) -> Union[float, int, str]: """ Derive true type from data value @@ -107,7 +107,7 @@ def get_typed_value(value): return value2 -def yaml_load(fh): +def yaml_load(fh: IO) -> dict: """ serializes a YAML files into a pyyaml object @@ -123,7 +123,7 @@ def yaml_load(fh): def path_constructor(loader, node): env_var = path_matcher.match(node.value).group(1) if env_var not in os.environ: - msg = 'Undefined environment variable {} in config'.format(env_var) + msg = f'Undefined environment variable {env_var} in config' raise EnvironmentError(msg) return get_typed_value(os.path.expandvars(node.value)) @@ -136,7 +136,7 @@ def yaml_load(fh): return yaml.load(fh, Loader=EnvVarLoader) -def str2bool(value): +def str2bool(value: Union[bool, str]) -> bool: """ helper function to return Python boolean type (source: https://stackoverflow.com/a/715468) @@ -156,7 +156,7 @@ def str2bool(value): return value2 -def to_json(dict_, pretty=False): +def to_json(dict_: dict, pretty: bool = False) -> str: """ Serialize dict to json @@ -175,7 +175,7 @@ def to_json(dict_, pretty=False): indent=indent) -def format_datetime(value, format_=DATETIME_FORMAT): +def format_datetime(value: str, format_: str = DATETIME_FORMAT) -> str: """ Parse datetime as ISO 8601 string; re-present it in particular format for display in HTML @@ -192,7 +192,7 @@ def format_datetime(value, format_=DATETIME_FORMAT): return dateutil.parser.isoparse(value).strftime(format_) -def file_modified_iso8601(filepath): +def file_modified_iso8601(filepath: Path) -> str: """ Provide a file's ctime in ISO8601 @@ -205,7 +205,7 @@ def file_modified_iso8601(filepath): os.path.getctime(filepath)).strftime('%Y-%m-%dT%H:%M:%SZ') -def human_size(nbytes): +def human_size(nbytes: int) -> str: """ Provides human readable file size @@ -230,12 +230,12 @@ def human_size(nbytes): elif suffixes[i] == 'B': return nbytes else: - f = '{:.1f}'.format(nbytes).rstrip('0').rstrip('.') + f = f'{nbytes:.1f}'.rstrip('0').rstrip('.') - return '{}{}'.format(f, suffixes[i]) + return f'{f}{suffixes[i]}' -def format_duration(start, end=None): +def format_duration(start: str, end: str = None) -> str: """ Parse a start and (optional) end datetime as ISO 8601 strings, calculate the difference, and return that duration as a string. @@ -245,6 +245,7 @@ def format_duration(start, end=None): :returns: string """ + if not isinstance(start, str) or not start.strip(): return '' end = end or start @@ -252,7 +253,7 @@ def format_duration(start, end=None): return str(duration) -def get_path_basename(urlpath): +def get_path_basename(urlpath: str) -> str: """ Helper function to derive file basename @@ -261,14 +262,16 @@ def get_path_basename(urlpath): :returns: string of basename of URL path """ - return os.path.basename(urlpath) + return Path(urlpath).name -def json_serial(obj): +def json_serial(obj: Any) -> str: """ helper function to convert to JSON non-default types (source: https://stackoverflow.com/a/22238613) + :param obj: `object` to be evaluated + :returns: JSON non-default type to `str` """ @@ -290,17 +293,19 @@ def json_serial(obj): elif isinstance(obj, l10n.Locale): return l10n.locale2str(obj) - msg = '{} type {} not serializable'.format(obj, type(obj)) + msg = f'{obj} type {type(obj)} not serializable' LOGGER.error(msg) raise TypeError(msg) -def is_url(urlstring): +def is_url(urlstring: str) -> bool: """ Validation function that determines whether a candidate URL should be considered a URI. No remote resource is obtained; this does not check the existence of any remote resource. + :param urlstring: `str` to be evaluated as candidate URL. + :returns: `bool` of whether the URL looks like a URL. """ try: @@ -310,7 +315,8 @@ def is_url(urlstring): return False -def render_j2_template(config, template, data, locale_=None): +def render_j2_template(config: dict, template: Path, + data: dict, locale_: str = None) -> str: """ render Jinja2 template @@ -330,13 +336,13 @@ def render_j2_template(config, template, data, locale_=None): 'jinja2.ext.autoescape'], autoescape=select_autoescape(['html', 'xml'])) custom_templates = True - LOGGER.debug('using custom templates: {}'.format(templates_path)) + LOGGER.debug(f'using custom templates: {templates_path}') except (KeyError, TypeError): env = Environment(loader=FileSystemLoader(TEMPLATES), extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], autoescape=select_autoescape(['html', 'xml'])) - LOGGER.debug('using default templates: {}'.format(TEMPLATES)) + LOGGER.debug(f'using default templates: {TEMPLATES}') env.filters['to_json'] = to_json env.filters['format_datetime'] = format_datetime @@ -373,7 +379,7 @@ def render_j2_template(config, template, data, locale_=None): data=data, locale=locale_, version=__version__) -def get_mimetype(filename): +def get_mimetype(filename: str) -> str: """ helper function to return MIME type of a given file @@ -385,7 +391,7 @@ def get_mimetype(filename): return mimetypes.guess_type(filename)[0] -def get_breadcrumbs(urlpath): +def get_breadcrumbs(urlpath: str) -> list: """ helper function to make breadcrumbs from a URL path @@ -412,7 +418,7 @@ def get_breadcrumbs(urlpath): return links -def filter_dict_by_key_value(dict_, key, value): +def filter_dict_by_key_value(dict_: dict, key: str, value: str) -> dict: """ helper function to filter a dict by a dict key @@ -426,7 +432,7 @@ def filter_dict_by_key_value(dict_, key, value): return {k: v for (k, v) in dict_.items() if v[key] == value} -def filter_providers_by_type(providers, type): +def filter_providers_by_type(providers: list, type: str) -> dict: """ helper function to filter a list of providers by type @@ -437,20 +443,20 @@ def filter_providers_by_type(providers, type): """ providers_ = {provider['type']: provider for provider in providers} - return providers_.get(type, None) + return providers_.get(type) -def get_provider_by_type(providers, provider_type): +def get_provider_by_type(providers: list, provider_type: str) -> dict: """ helper function to load a provider by a provider type :param providers: ``list`` of providers - :param provider_type: type of provider (feature) + :param provider_type: type of provider (e.g. feature) :returns: provider based on type """ - LOGGER.debug('Searching for provider type {}'.format(provider_type)) + LOGGER.debug(f'Searching for provider type {provider_type}') try: p = (next(d for i, d in enumerate(providers) if d['type'] == provider_type)) @@ -460,7 +466,7 @@ def get_provider_by_type(providers, provider_type): return p -def get_provider_default(providers): +def get_provider_default(providers: list) -> dict: """ helper function to get a resource's default provider @@ -477,7 +483,7 @@ def get_provider_default(providers): LOGGER.debug('no default provider type. Returning first provider') default = providers[0] - LOGGER.debug('Default provider: {}'.format(default['type'])) + LOGGER.debug(f"Default provider: {default['type']}") return default @@ -494,17 +500,16 @@ class JobStatus(Enum): dismissed = 'dismissed' -def read_data(path): +def read_data(path: Union[Path, str]) -> Union[bytes, str]: """ helper function to read data (file or networrk) """ - LOGGER.debug('Attempting to read {}'.format(path)) - scheme = urlparse(path).scheme + LOGGER.debug(f'Attempting to read {path}') - if scheme in ['', 'file']: + if isinstance(path, Path) or not path.startswith(('http', 's3')): LOGGER.debug('local file on disk') - with io.open(path, 'rb') as fh: + with Path(path).open('rb') as fh: return fh.read() else: LOGGER.debug('network file') @@ -512,7 +517,7 @@ def read_data(path): return r.read() -def url_join(*parts): +def url_join(*parts: list) -> str: """ helper function to join a URL from a number of parts/fragments. Implemented because urllib.parse.urljoin strips subpaths from @@ -528,7 +533,7 @@ def url_join(*parts): return '/'.join([p.strip().strip('/') for p in parts]) -def get_envelope(coords_list: List[List[float]]): +def get_envelope(coords_list: List[List[float]]) -> list: """ helper function to get the envelope for a given coordinates list through the Shapely API. diff --git a/setup.py b/setup.py index 93f1de7..0ab6fcf 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2018 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -27,8 +27,8 @@ # # ================================================================= -import io import os +from pathlib import Path import re from setuptools import Command, find_packages, setup import shutil @@ -58,7 +58,7 @@ class PyCleanBuild(Command): for file_ in remove_files: try: - os.remove(file_) + Path(file_).unlink() except OSError: pass @@ -68,9 +68,9 @@ class PyCleanBuild(Command): except OSError: pass - for file_ in os.listdir('..'): - if file_.endswith(('.deb', '.build', '.changes')): - os.remove('../{}'.format(file_)) + for file_ in [Path(p) for p in os.listdir('..')]: + if file_.stem in ['.deb', '.build', '.changes']: + os.unlink(Path('..', file_)) class PyTest(Command): @@ -107,16 +107,20 @@ class PyCoverage(Command): raise SystemExit(errno) -def read(filename, encoding='utf-8'): +def read(filename): """read file contents""" - full_path = os.path.join(os.path.dirname(__file__), filename) - with io.open(full_path, encoding=encoding) as fh: + + fullpath = Path(__file__).resolve().parent / filename + + with fullpath.open() as fh: contents = fh.read().strip() + return contents def get_package_version(): """get version from top-level package init""" + version_file = read('pygeoapi/__init__.py') version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) @@ -129,8 +133,9 @@ LONG_DESCRIPTION = read('README.md') DESCRIPTION = 'pygeoapi provides an API to geospatial data' -if os.path.exists('MANIFEST'): - os.unlink('MANIFEST') +MANIFEST = Path('MANIFEST') +if MANIFEST.exists(): + MANIFEST.unlink() setup( name='pygeoapi', diff --git a/tests/load_es_data.py b/tests/load_es_data.py index 0d12cd1..d31efba 100644 --- a/tests/load_es_data.py +++ b/tests/load_es_data.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -28,17 +28,17 @@ # ================================================================= import json -import os +from pathlib import Path import sys from elasticsearch import Elasticsearch, helpers es = Elasticsearch() if len(sys.argv) < 3: - print('Usage: {} '.format(sys.argv[0])) + print(f'Usage: {sys.argv[0]} ') sys.exit(1) -index_name = os.path.splitext(os.path.basename(sys.argv[1]))[0].lower() +index_name = Path(sys.argv[1]).stem.lower() id_field = sys.argv[2] if es.indices.exists(index_name): diff --git a/tests/load_mongo_data.py b/tests/load_mongo_data.py index b6aa914..53b673f 100644 --- a/tests/load_mongo_data.py +++ b/tests/load_mongo_data.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Timo Tuunanen +# Authors: Tom Kralidis # # Copyright (c) 2019 Timo Tuunanen +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -37,7 +39,7 @@ mongodb = 'testdb' mongocollection = 'testplaces' if len(sys.argv) == 1: - print('Usage: {} '.format(sys.argv[0])) + print(f'Usage: {sys.argv[0]} ') sys.exit(1) myclient = MongoClient(monogourl) diff --git a/tests/load_sta_data.py b/tests/load_sta_data.py index 2146a30..397e3c3 100644 --- a/tests/load_sta_data.py +++ b/tests/load_sta_data.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Benjamin Webb +# Authors: Tom Kralidis # # Copyright (c) 2021 Benjamin Webb +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -27,17 +29,17 @@ # # ================================================================= +from pathlib import Path import requests import sys import json -import os.path url = 'http://localhost:8080/FROST-Server/v1.1/Datastreams' data_url = 'https://raw.githubusercontent.com/webb-ben/data/main/' -def main(path_): - filename = os.path.basename(path_) +def main(path_: Path): + filename = path_.name r = requests.get(f'{data_url}{filename}') data = r.json().get('value') for v in data: @@ -65,7 +67,7 @@ def clean(dirty_dict): if __name__ == "__main__": if len(sys.argv) == 1: - print('Usage: {} '.format(sys.argv[0])) + print(f'Usage: {sys.argv[0]} ') sys.exit(1) - main(sys.argv[1]) + main(Path(sys.argv[1])) diff --git a/tests/load_tinydb_records.py b/tests/load_tinydb_records.py index 0c54cc9..eea2f88 100644 --- a/tests/load_tinydb_records.py +++ b/tests/load_tinydb_records.py @@ -28,8 +28,7 @@ # ================================================================= from datetime import datetime -from glob import glob -import os +from pathlib import Path import sys from typing import Union @@ -39,14 +38,14 @@ from tinydb import TinyDB if len(sys.argv) < 3: - print('Usage: {} '.format(sys.argv[0])) + print(f'Usage: {sys.argv[0]} ') sys.exit(1) -xml_dir = sys.argv[1] -index_name = sys.argv[2] +xml_dir = Path(sys.argv[1]) +index_name = Path(sys.argv[2]) -if os.path.exists(index_name): - os.remove(index_name) +if index_name.exists(): + index_name.unlink() db = TinyDB(index_name) @@ -124,7 +123,7 @@ def get_anytext(bag: Union[list, str]) -> str: return ' '.join(text_bag) -for xml_file in glob('{}/*.xml'.format(xml_dir)): +for xml_file in xml_dir.glob('*.xml'): m = MD_Metadata(etree.parse(xml_file)) _raw_metadata = m.xml.decode('utf-8') @@ -231,7 +230,6 @@ for xml_file in glob('{}/*.xml'.format(xml_dir)): try: res = db.insert(json_record) - print('Metadata record {} loaded with internal id {}'.format( - xml_file, res)) + print(f'Metadata record {xml_file} loaded with internal id {res}') except Exception as err: print(err) diff --git a/tests/test_ogr_csv_provider.py b/tests/test_ogr_csv_provider.py index 7e14750..a96d5db 100644 --- a/tests/test_ogr_csv_provider.py +++ b/tests/test_ogr_csv_provider.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Francesco Bartoli +# Authors: Tom Kralidis # # Copyright (c) 2020 Francesco Bartoli +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -103,10 +105,10 @@ def test_query_hits_vsicurl(config_vsicurl_csv): p = OGRProvider(config_vsicurl_csv) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 100 @@ -118,10 +120,10 @@ def test_query_bbox_hits_vsicurl(config_vsicurl_csv): feature_collection = p.query( bbox=[10.497565, 41.520355, 15.111823, 43.308645], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 1 @@ -131,15 +133,15 @@ def test_query_with_limit_vsicurl(config_vsicurl_csv): p = OGRProvider(config_vsicurl_csv) feature_collection = p.query(limit=2, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 2 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -148,17 +150,17 @@ def test_query_with_offset_vsicurl(config_vsicurl_csv): p = OGRProvider(config_vsicurl_csv) feature_collection = p.query(offset=20, limit=10, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 10 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 21 assert 'Veneto' in properties['denominazione_regione'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -169,8 +171,8 @@ def test_query_with_property_vsicurl(config_vsicurl_csv): feature_collection = p.query( offset=20, limit=10, resulttype='results', properties=[('denominazione_regione', 'Lazio')]) - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 10 for feature in features: assert 'Lazio' in feature['properties']['denominazione_regione'] diff --git a/tests/test_ogr_esrijson_provider.py b/tests/test_ogr_esrijson_provider.py index a8af71e..b6af4ce 100644 --- a/tests/test_ogr_esrijson_provider.py +++ b/tests/test_ogr_esrijson_provider.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Francesco Bartoli +# Authors: Tom Kralidis # # Copyright (c) 2020 Francesco Bartoli +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -75,7 +77,7 @@ def config_random_id(config_ArcGIS_ESRIJSON): p = OGRProvider(config_ArcGIS_ESRIJSON) # Get bunch of features to randomly have an id feature_collection = p.query(offset=0, limit=10, resulttype='results') - features = feature_collection.get('features', None) + features = feature_collection.get('features') features_list = [] for feature in features: features_list.append(feature['id']) @@ -117,10 +119,10 @@ def test_query_hits_agol(config_ArcGIS_ESRIJSON): p = OGRProvider(config_ArcGIS_ESRIJSON) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 100 @@ -133,10 +135,10 @@ def test_query_bbox_hits_agol(config_ArcGIS_ESRIJSON): bbox=[-9822165.181154, 5112669.004249, -9807305.104750, 5133712.297986], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 1 @@ -146,15 +148,15 @@ def test_query_with_limit_agol(config_ArcGIS_ESRIJSON): p = OGRProvider(config_ArcGIS_ESRIJSON) feature_collection = p.query(limit=2, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 2 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -163,14 +165,14 @@ def test_query_with_offset(config_ArcGIS_ESRIJSON): p = OGRProvider(config_ArcGIS_ESRIJSON) feature_collection = p.query(offset=10, limit=10, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 10 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert properties['fulladdr'] is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None diff --git a/tests/test_ogr_gpkg_provider.py b/tests/test_ogr_gpkg_provider.py index 51c61eb..ad1d941 100644 --- a/tests/test_ogr_gpkg_provider.py +++ b/tests/test_ogr_gpkg_provider.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Just van den Broecke +# Authors: Tom Kralidis # # Copyright (c) 2019 Just van den Broecke +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -64,13 +66,13 @@ def test_query(config_poi_portugal): p = OGRProvider(config_poi_portugal) feature_collection = p.query() - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert features is not None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -164,10 +166,10 @@ def test_query_hits_28992(config_gpkg_28992): p = OGRProvider(config_gpkg_28992) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 2481 @@ -177,10 +179,10 @@ def test_query_hits_4326(config_gpkg_4326): p = OGRProvider(config_gpkg_4326) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 2481 @@ -193,10 +195,10 @@ def test_query_bbox_hits_4326(config_gpkg_4326): # bbox=[120000, 480000, 124000, 487000], resulttype='hits') feature_collection = p.query( bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -210,10 +212,10 @@ def test_query_bbox_hits_28992(config_gpkg_28992): feature_collection = p.query( bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -226,15 +228,15 @@ def test_query_bbox_28992(config_gpkg_28992): # bbox=[180800, 452500, 181200, 452700], resulttype='results') feature_collection = p.query( bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Planken Wambuisweg' @@ -247,15 +249,15 @@ def test_query_bbox_4326(config_gpkg_4326): # bbox=[180800, 452500, 181200, 452700], resulttype='results') feature_collection = p.query( bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Planken Wambuisweg' @@ -265,15 +267,15 @@ def test_query_with_limit_28992(config_gpkg_28992): p = OGRProvider(config_gpkg_28992) feature_collection = p.query(limit=2, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 2 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -282,15 +284,15 @@ def test_query_with_limit_4326(config_gpkg_4326): p = OGRProvider(config_gpkg_4326) feature_collection = p.query(limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -299,17 +301,17 @@ def test_query_with_offset_28992(config_gpkg_28992): p = OGRProvider(config_gpkg_28992) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'inspireadressen.1744969' assert 'Egypte' in properties['straatnaam'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -318,17 +320,17 @@ def test_query_with_offset_4326(config_gpkg_4326): p = OGRProvider(config_gpkg_4326) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'inspireadressen.1744969' assert 'Egypte' in properties['straatnaam'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -340,15 +342,15 @@ def test_query_bbox_with_offset_28992(config_gpkg_28992): offset=10, limit=5, bbox=(5.742, 52.053, 5.773, 52.098), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Buurtweg' assert properties['huisnummer'] == '4' @@ -362,15 +364,15 @@ def test_query_bbox_with_offset_4326(config_gpkg_4326): offset=1, limit=5, bbox=(5.742, 52.053, 5.773, 52.098), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Egypte' assert properties['huisnummer'] == '6' @@ -387,8 +389,8 @@ def test_query_with_property_filtering(config_gpkg_4326): ] ) - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) > 1 for feature in features: diff --git a/tests/test_ogr_shapefile_provider.py b/tests/test_ogr_shapefile_provider.py index 5bf7b42..28372b1 100644 --- a/tests/test_ogr_shapefile_provider.py +++ b/tests/test_ogr_shapefile_provider.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Just van den Broecke +# Authors: Tom Kralidis # # Copyright (c) 2019 Just van den Broecke +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -120,10 +122,10 @@ def test_query_hits_28992(config_shapefile_28992): p = OGRProvider(config_shapefile_28992) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 2481 @@ -133,10 +135,10 @@ def test_query_hits_4326(config_shapefile_4326): p = OGRProvider(config_shapefile_4326) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 2481 @@ -149,10 +151,10 @@ def test_query_bbox_hits_4326(config_shapefile_4326): # bbox=[120000, 480000, 124000, 487000], resulttype='hits') feature_collection = p.query( bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -166,10 +168,10 @@ def test_query_bbox_hits_28992(config_shapefile_28992): feature_collection = p.query( bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -182,15 +184,15 @@ def test_query_bbox_28992(config_shapefile_28992): # bbox=[180800, 452500, 181200, 452700], resulttype='results') feature_collection = p.query( bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Planken Wambuisweg' @@ -203,15 +205,15 @@ def test_query_bbox_4326(config_shapefile_4326): # bbox=[180800, 452500, 181200, 452700], resulttype='results') feature_collection = p.query( bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Planken Wambuisweg' @@ -221,15 +223,15 @@ def test_query_with_limit_28992(config_shapefile_28992): p = OGRProvider(config_shapefile_28992) feature_collection = p.query(limit=2, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 2 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -238,15 +240,15 @@ def test_query_with_limit_4326(config_shapefile_4326): p = OGRProvider(config_shapefile_4326) feature_collection = p.query(limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -255,17 +257,17 @@ def test_query_with_offset_28992(config_shapefile_28992): p = OGRProvider(config_shapefile_28992) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'inspireadressen.1744969' assert 'Egypte' in properties['straatnaam'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -274,17 +276,17 @@ def test_query_with_offset_4326(config_shapefile_4326): p = OGRProvider(config_shapefile_4326) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'inspireadressen.1744969' assert 'Egypte' in properties['straatnaam'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -296,15 +298,15 @@ def test_query_bbox_with_offset_28992(config_shapefile_28992): offset=10, limit=5, bbox=(5.742, 52.053, 5.773, 52.098), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Buurtweg' assert properties['huisnummer'] == '4' @@ -318,15 +320,15 @@ def test_query_bbox_with_offset_4326(config_shapefile_4326): offset=1, limit=5, bbox=(5.742, 52.053, 5.773, 52.098), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Egypte' assert properties['huisnummer'] == '6' @@ -343,8 +345,8 @@ def test_query_with_property_filtering(config_shapefile_4326): ] ) - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) > 1 for feature in features: diff --git a/tests/test_ogr_sqlite_provider.py b/tests/test_ogr_sqlite_provider.py index fda95d8..554eb94 100644 --- a/tests/test_ogr_sqlite_provider.py +++ b/tests/test_ogr_sqlite_provider.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Just van den Broecke +# Tom Kralidis # # Copyright (c) 2019 Just van den Broecke +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -92,10 +94,10 @@ def test_query_hits_4326(config_sqlite_4326): p = OGRProvider(config_sqlite_4326) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 2481 @@ -108,10 +110,10 @@ def test_query_bbox_hits_4326(config_sqlite_4326): # bbox=[120000, 480000, 124000, 487000], resulttype='hits') feature_collection = p.query( bbox=[5.763409, 52.060197, 5.769256, 52.061976], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -124,15 +126,15 @@ def test_query_bbox_4326(config_sqlite_4326): # bbox=[180800, 452500, 181200, 452700], resulttype='results') feature_collection = p.query( bbox=(5.763409, 52.060197, 5.769256, 52.061976), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Planken Wambuisweg' @@ -142,15 +144,15 @@ def test_query_with_limit_4326(config_sqlite_4326): p = OGRProvider(config_sqlite_4326) feature_collection = p.query(limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -159,17 +161,17 @@ def test_query_with_offset_4326(config_sqlite_4326): p = OGRProvider(config_sqlite_4326) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'inspireadressen.1744969' assert 'Egypte' in properties['straatnaam'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -181,15 +183,15 @@ def test_query_bbox_with_offset_4326(config_sqlite_4326): offset=1, limit=50, bbox=(5.742, 52.053, 5.773, 52.098), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 3 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['straatnaam'] == 'Egypte' assert properties['huisnummer'] == '4' @@ -206,8 +208,8 @@ def test_query_with_property_filtering(config_sqlite_4326): ] ) - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) > 1 for feature in features: diff --git a/tests/test_ogr_wfs_provider.py b/tests/test_ogr_wfs_provider.py index 9e98327..ea629ae 100644 --- a/tests/test_ogr_wfs_provider.py +++ b/tests/test_ogr_wfs_provider.py @@ -6,7 +6,7 @@ # # Copyright (c) 2019 Just van den Broecke # Copyright (c) 2020 Francesco Bartoli -# Copyright (c) 2020 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -259,10 +259,10 @@ def test_query_hits_ms(config_MapServer_WFS_cities): p = OGRProvider(config_MapServer_WFS_cities) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 5000 @@ -272,10 +272,10 @@ def test_query_hits_geosol_gs(config_geosol_gs_WFS): p = OGRProvider(config_geosol_gs_WFS) feature_collection = p.query(resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 186 @@ -286,10 +286,10 @@ def test_query_hits_geosol_gs(config_geosol_gs_WFS): # # p = OGRProvider(config_MapServer_WFS_continents) # feature_collection = p.query(resulttype='hits') -# assert feature_collection.get('type', None) == 'FeatureCollection' -# features = feature_collection.get('features', None) +# assert feature_collection.get('type') == 'FeatureCollection' +# features = feature_collection.get('features') # assert len(features) == 0 -# hits = feature_collection.get('numberMatched', None) +# hits = feature_collection.get('numberMatched') # assert hits is not None # assert hits > 8000000 @@ -301,10 +301,10 @@ def test_query_bbox_hits_ms(config_MapServer_WFS_cities): feature_collection = p.query( bbox=[-47, -24, -45, -22], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits > 1 @@ -315,10 +315,10 @@ def test_query_bbox_hits_gs(config_MapServer_WFS_continents): p = OGRProvider(config_MapServer_WFS_continents) feature_collection = p.query(bbox=[-61, 46, -60, 47], resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 3 @@ -332,10 +332,10 @@ def test_query_bbox_hits_geosol_gs(config_geosol_gs_WFS): # feature_collection = p.query(bbox=( # 5.763409, 52.060197, 5.769256, 52.061976), resulttype='hits') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is not None assert hits == 1 @@ -348,15 +348,15 @@ def test_query_bbox_ms(config_MapServer_WFS_cities): # bbox=[120000, 480000, 124000, 487000], resulttype='results') feature_collection = p.query( bbox=[4.874016, 52.306852, 4.932020, 52.370004], resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) > 0 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -365,15 +365,15 @@ def test_query_bbox_gs(config_MapServer_WFS_continents): p = OGRProvider(config_MapServer_WFS_continents) feature_collection = p.query(bbox=(5, 52, 6, 53), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 4 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None assert properties['NA2DESC'] == 'Netherlands' assert properties['NA3DESC'] == 'Europe' @@ -388,16 +388,16 @@ def test_query_bbox_geosol_gs(config_geosol_gs_WFS): feature_collection = p.query( bbox=(681417.0, 4849032.0, 681417.3, 4849032.3), resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 1 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert properties['sito'] == 'Centro storico di Firenze' - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -406,15 +406,15 @@ def test_query_with_limit_ms(config_MapServer_WFS_cities): p = OGRProvider(config_MapServer_WFS_cities) feature_collection = p.query(limit=2, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 2 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -423,17 +423,17 @@ def test_query_with_offset(config_MapServer_WFS_cities): p = OGRProvider(config_MapServer_WFS_cities) feature_collection = p.query(offset=20, limit=5, resulttype='results') - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert len(features) == 5 - hits = feature_collection.get('numberMatched', None) + hits = feature_collection.get('numberMatched') assert hits is None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None assert feature['id'] == 'cities.411' assert '6610764' in properties['POPULATION'] - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None diff --git a/tests/test_postgresql_provider.py b/tests/test_postgresql_provider.py index 6aace06..5c2314f 100644 --- a/tests/test_postgresql_provider.py +++ b/tests/test_postgresql_provider.py @@ -6,7 +6,7 @@ # Colin Blackburn # # Copyright (c) 2019 Just van den Broecke -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # Copyright (c) 2022 John A Stevenson and Colin Blackburn # # Permission is hereby granted, free of charge, to any person @@ -73,13 +73,13 @@ def test_query(config): """Testing query for a valid JSON object with geometry""" p = PostgreSQLProvider(config) feature_collection = p.query() - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert features is not None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -97,14 +97,14 @@ def test_query_with_property_filter(config): """Test query valid features when filtering by property""" p = PostgreSQLProvider(config) feature_collection = p.query(properties=[("waterway", "stream")]) - features = feature_collection.get('features', None) + features = feature_collection.get('features') stream_features = list( filter(lambda feature: feature['properties']['waterway'] == 'stream', features)) assert (len(features) == len(stream_features)) feature_collection = p.query(limit=50) - features = feature_collection.get('features', None) + features = feature_collection.get('features') stream_features = list( filter(lambda feature: feature['properties']['waterway'] == 'stream', features)) @@ -127,7 +127,7 @@ def test_query_with_config_properties(config): assert provider.properties == properties_subset result = provider.query() feature = result.get('features')[0] - properties = feature.get('properties', None) + properties = feature.get('properties') for property_name in properties.keys(): assert property_name in config["properties"] @@ -240,9 +240,9 @@ def test_query_cql(config, cql, expected_ids): provider = PostgreSQLProvider(config) feature_collection = provider.query(filterq=ast) - assert feature_collection.get('type', None) == 'FeatureCollection' + assert feature_collection.get('type') == 'FeatureCollection' - features = feature_collection.get('features', None) + features = feature_collection.get('features') ids = [feature["id"] for feature in features] assert ids == expected_ids diff --git a/tests/test_sqlite_geopackage_provider.py b/tests/test_sqlite_geopackage_provider.py index 02066ae..d46c0f3 100644 --- a/tests/test_sqlite_geopackage_provider.py +++ b/tests/test_sqlite_geopackage_provider.py @@ -4,7 +4,7 @@ # Tom Kralidis # # Copyright (c) 2019 Just van den Broecke -# Copyright (c) 2019 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -66,13 +66,13 @@ def test_query_sqlite(config_sqlite): p = SQLiteGPKGProvider(config_sqlite) feature_collection = p.query() - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert features is not None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -81,13 +81,13 @@ def test_query_geopackage(config_geopackage): p = SQLiteGPKGProvider(config_geopackage) feature_collection = p.query() - assert feature_collection.get('type', None) == 'FeatureCollection' - features = feature_collection.get('features', None) + assert feature_collection.get('type') == 'FeatureCollection' + features = feature_collection.get('features') assert features is not None feature = features[0] - properties = feature.get('properties', None) + properties = feature.get('properties') assert properties is not None - geometry = feature.get('geometry', None) + geometry = feature.get('geometry') assert geometry is not None @@ -109,7 +109,7 @@ def test_query_with_property_filter_sqlite_geopackage(config_sqlite): p = SQLiteGPKGProvider(config_sqlite) feature_collection = p.query(properties=[ ("continent", "Europe")], limit=100) - features = feature_collection.get('features', None) + features = feature_collection.get('features') assert len(features) == 39 @@ -118,7 +118,7 @@ def test_query_with_property_filter_bbox_sqlite_geopackage(config_sqlite): p = SQLiteGPKGProvider(config_sqlite) feature_collection = p.query(properties=[("continent", "Europe")], bbox=[29.3373, -3.4099, 29.3761, -3.3924]) - features = feature_collection.get('features', None) + features = feature_collection.get('features') assert len(features) == 0 diff --git a/tests/test_tinydb_manager_for_parallel_requests.py b/tests/test_tinydb_manager_for_parallel_requests.py index ba20cc1..0f15602 100644 --- a/tests/test_tinydb_manager_for_parallel_requests.py +++ b/tests/test_tinydb_manager_for_parallel_requests.py @@ -1,8 +1,10 @@ # ================================================================= # # Authors: Martin Pontius +# Tom Kralidis # # Copyright (c) 2022 52°North Spatial Information Research GmbH +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -27,7 +29,7 @@ # # ================================================================= -import os +from pathlib import Path from werkzeug.wrappers import Request from werkzeug.test import create_environ @@ -79,10 +81,10 @@ def _create_request(name, message, locales): def test_async_hello_world_process_parallel(api_, config): - index_name = config['server']['manager']['connection'] + index_name = Path(config['server']['manager']['connection']) - if os.path.exists(index_name): - os.remove(index_name) + if index_name.exists(): + index_name.unlink() NUM_PROCS = 4 process_id = "hello-world" @@ -118,10 +120,8 @@ def test_async_hello_world_process_parallel(api_, config): assert job_dict["mimetype"] == process_out['headers'][ 'Content-Type'] try: - with open( - "{}/hello-world-{}".format(os.path.dirname(index_name), - job_id)) as json_file: - out_json = json.load(json_file) + with open(f'{index_name.parent()}/hello-world-{job_id}') as fh: + out_json = json.load(fh) assert out_json["id"] == "echo" assert out_json["value"] == "Hello World! Hello" except FileNotFoundError as e: diff --git a/tests/util.py b/tests/util.py index 3375dd5..d0634b0 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,7 +2,7 @@ # # Authors: Tom Kralidis # -# Copyright (c) 2021 Tom Kralidis +# Copyright (c) 2022 Tom Kralidis # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -27,8 +27,8 @@ # # ================================================================= -import os import logging +import os.path from werkzeug.test import create_environ from werkzeug.wrappers import Request @@ -37,7 +37,7 @@ from werkzeug.datastructures import ImmutableMultiDict LOGGER = logging.getLogger(__name__) -def get_test_file_path(filename): +def get_test_file_path(filename: str) -> str: """helper function to open test file safely""" if os.path.isfile(filename):