diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 0660739..6b4028b 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -46,7 +46,7 @@ from pygeoapi.log import setup_logger from pygeoapi.plugin import load_plugin, PLUGINS from pygeoapi.provider.base import ( ProviderGenericError, ProviderConnectionError, ProviderNotFoundError, - ProviderQueryError) + ProviderQueryError, ProviderItemNotFoundError) from pygeoapi.util import (dategetter, json_serial, render_j2_template, str2bool, TEMPLATES) @@ -875,6 +875,13 @@ class API(object): } LOGGER.error(err) return headers_, 500, json.dumps(exception) + except ProviderItemNotFoundError: + exception = { + 'code': 'NotFound', + 'description': 'identifier not found' + } + LOGGER.error(exception) + return headers_, 404, json.dumps(exception) except ProviderQueryError as err: exception = { 'code': 'NoApplicableCode', diff --git a/pygeoapi/provider/base.py b/pygeoapi/provider/base.py index 6bd1e74..24e8ef5 100644 --- a/pygeoapi/provider/base.py +++ b/pygeoapi/provider/base.py @@ -135,6 +135,11 @@ class ProviderQueryError(ProviderGenericError): pass +class ProviderItemNotFoundError(ProviderGenericError): + """provider query error""" + pass + + class ProviderNotFoundError(ProviderGenericError): """provider not found error""" pass diff --git a/pygeoapi/provider/csv_.py b/pygeoapi/provider/csv_.py index d4f6252..60f2848 100644 --- a/pygeoapi/provider/csv_.py +++ b/pygeoapi/provider/csv_.py @@ -32,7 +32,8 @@ import csv import itertools import logging -from pygeoapi.provider.base import BaseProvider, ProviderQueryError +from pygeoapi.provider.base import (BaseProvider, ProviderQueryError, + ProviderItemNotFoundError) LOGGER = logging.getLogger(__name__) @@ -145,8 +146,13 @@ class CSVProvider(BaseProvider): :returns: dict of single GeoJSON feature """ - - return self._load(identifier=identifier) + item = self._load(identifier=identifier) + if item: + return item + else: + err = 'item {} not found'.format(identifier) + LOGGER.error(err) + raise ProviderItemNotFoundError(err) def __repr__(self): return ' {}'.format(self.data) diff --git a/pygeoapi/provider/elasticsearch_.py b/pygeoapi/provider/elasticsearch_.py index 4d8e13f..82ebfe9 100644 --- a/pygeoapi/provider/elasticsearch_.py +++ b/pygeoapi/provider/elasticsearch_.py @@ -34,7 +34,8 @@ from elasticsearch import Elasticsearch, exceptions, helpers from elasticsearch.client.indices import IndicesClient from pygeoapi.provider.base import (BaseProvider, ProviderConnectionError, - ProviderQueryError) + ProviderQueryError, + ProviderItemNotFoundError) LOGGER = logging.getLogger(__name__) @@ -322,7 +323,7 @@ class ElasticsearchProvider(BaseProvider): result = self.es.search(index=self.index_name, body=query) if len(result['hits']['hits']) == 0: LOGGER.error(err) - return None + raise ProviderItemNotFoundError(err) LOGGER.debug('Serializing feature') feature_ = self.esdoc2geojson(result['hits']['hits'][0]) except Exception as err: diff --git a/pygeoapi/provider/geojson.py b/pygeoapi/provider/geojson.py index d169d9c..05350de 100644 --- a/pygeoapi/provider/geojson.py +++ b/pygeoapi/provider/geojson.py @@ -32,7 +32,7 @@ import logging import os import uuid -from pygeoapi.provider.base import BaseProvider +from pygeoapi.provider.base import BaseProvider, ProviderItemNotFoundError LOGGER = logging.getLogger(__name__) @@ -133,8 +133,9 @@ class GeoJSONProvider(BaseProvider): return feature # default, no match - LOGGER.error('feature {} not found'.format(identifier)) - return None + err = 'item {} not found'.format(identifier) + LOGGER.error(err) + raise ProviderItemNotFoundError(err) def create(self, new_feature): """Create a new feature diff --git a/pygeoapi/provider/mongo.py b/pygeoapi/provider/mongo.py index ae1bed3..e883fbb 100644 --- a/pygeoapi/provider/mongo.py +++ b/pygeoapi/provider/mongo.py @@ -34,7 +34,7 @@ from pymongo import MongoClient from pymongo import GEOSPHERE from pymongo import ASCENDING, DESCENDING from pymongo.collection import ObjectId -from pygeoapi.provider.base import BaseProvider +from pygeoapi.provider.base import BaseProvider, ProviderItemNotFoundError LOGGER = logging.getLogger(__name__) @@ -149,7 +149,12 @@ class MongoProvider(BaseProvider): """ featurelist, matchcount = self._get_feature_list( {'_id': ObjectId(identifier)}) - return featurelist[0] if featurelist else None + if featurelist: + return featurelist[0] + else: + err = 'item {} not found'.format(identifier) + LOGGER.error(err) + raise ProviderItemNotFoundError(err) def create(self, new_feature): """Create a new feature diff --git a/pygeoapi/provider/ogr.py b/pygeoapi/provider/ogr.py index e64fd33..0e6c5c9 100644 --- a/pygeoapi/provider/ogr.py +++ b/pygeoapi/provider/ogr.py @@ -40,7 +40,8 @@ from osgeo import osr as osgeo_osr from pygeoapi.provider.base import ( BaseProvider, ProviderGenericError, - ProviderQueryError, ProviderConnectionError) + ProviderQueryError, ProviderConnectionError, + ProviderItemNotFoundError) LOGGER = logging.getLogger(__name__) @@ -370,7 +371,7 @@ class OGRProvider(BaseProvider): layer.SetAttributeFilter("{field} = '{id}'".format( field=self.id_field, id=identifier)) - ogr_feature = self._get_next_feature(layer) + ogr_feature = self._get_next_feature(layer, identifier) result = self._ogr_feature_to_json(ogr_feature) except RuntimeError as err: @@ -379,6 +380,9 @@ class OGRProvider(BaseProvider): except ProviderConnectionError as err: LOGGER.error(err) raise ProviderConnectionError(err) + except ProviderItemNotFoundError as err: + LOGGER.error(err) + raise ProviderItemNotFoundError(err) except Exception as err: LOGGER.error(err) raise ProviderGenericError(err) @@ -411,14 +415,19 @@ class OGRProvider(BaseProvider): class_ = getattr(module, classname) self.source_helper = class_(self) - def _get_next_feature(self, layer): + def _get_next_feature(self, layer, feature_id): try: # Ignore gdal error next_feature = _ignore_gdal_error(layer, 'GetNextFeature') - if all(val is None for val in next_feature.items().values()): - self.gdal.Error( - self.gdal.CE_Failure, 1, "Object properties are all null" - ) + if next_feature: + if all(val is None for val in next_feature.items().values()): + self.gdal.Error( + self.gdal.CE_Failure, 1, + "Object properties are all null" + ) + else: + raise ProviderItemNotFoundError( + "item {} not found".format(feature_id)) return next_feature except RuntimeError as gdalerr: LOGGER.error(self.gdal.GetLastErrorMsg()) diff --git a/pygeoapi/provider/postgresql.py b/pygeoapi/provider/postgresql.py index bff66b4..54a9eeb 100644 --- a/pygeoapi/provider/postgresql.py +++ b/pygeoapi/provider/postgresql.py @@ -48,7 +48,7 @@ import json import psycopg2 from psycopg2.sql import SQL, Identifier, Literal from pygeoapi.provider.base import BaseProvider, \ - ProviderConnectionError, ProviderQueryError + ProviderConnectionError, ProviderQueryError, ProviderItemNotFoundError from psycopg2.extras import RealDictCursor @@ -353,12 +353,20 @@ class PostgreSQLProvider(BaseProvider): LOGGER.error(err) raise ProviderQueryError() - row_data = cursor.fetchall()[0] + results = cursor.fetchall() + row_data = None + if results: + row_data = results[0] feature = self.__response_feature(row_data) - feature['prev'] = self.get_previous(cursor, identifier) - feature['next'] = self.get_next(cursor, identifier) - return feature + if feature: + feature['prev'] = self.get_previous(cursor, identifier) + feature['next'] = self.get_next(cursor, identifier) + return feature + else: + err = 'item {} not found'.format(identifier) + LOGGER.error(err) + raise ProviderItemNotFoundError(err) def __response_feature(self, row_data): """ @@ -369,17 +377,20 @@ class PostgreSQLProvider(BaseProvider): :returns: `dict` of GeoJSON Feature """ - rd = dict(row_data) - feature = { - 'type': 'Feature' - } - feature["geometry"] = json.loads( - rd.pop('st_asgeojson')) + if row_data: + rd = dict(row_data) + feature = { + 'type': 'Feature' + } + feature["geometry"] = json.loads( + rd.pop('st_asgeojson')) - feature['properties'] = rd - feature['id'] = feature['properties'].get(self.id_field) + feature['properties'] = rd + feature['id'] = feature['properties'].get(self.id_field) - return feature + return feature + else: + return None def __response_feature_hits(self, hits): """Assembles GeoJSON/Feature number diff --git a/pygeoapi/provider/sqlite.py b/pygeoapi/provider/sqlite.py index 3d8f3d2..aaa5103 100644 --- a/pygeoapi/provider/sqlite.py +++ b/pygeoapi/provider/sqlite.py @@ -36,7 +36,8 @@ import logging import os import json from pygeoapi.plugin import InvalidPluginError -from pygeoapi.provider.base import BaseProvider, ProviderConnectionError +from pygeoapi.provider.base import (BaseProvider, ProviderConnectionError, + ProviderItemNotFoundError) LOGGER = logging.getLogger(__name__) @@ -137,17 +138,20 @@ class SQLiteGPKGProvider(BaseProvider): :returns: `dict` of GeoJSON Feature """ - rd = dict(row_data) # sqlite3.Row is doesnt support pop - feature = { - 'type': 'Feature' - } - feature["geometry"] = json.loads( - rd.pop('AsGeoJSON({})'.format(self.geom_col)) - ) - feature['properties'] = rd - feature['id'] = feature['properties'].pop(self.id_field) + if row_data: + rd = dict(row_data) # sqlite3.Row is doesnt support pop + feature = { + 'type': 'Feature' + } + feature["geometry"] = json.loads( + rd.pop('AsGeoJSON({})'.format(self.geom_col)) + ) + feature['properties'] = rd + feature['id'] = feature['properties'].pop(self.id_field) - return feature + return feature + else: + return None def __response_feature_hits(self, hits): """Assembles GeoJSON/Feature number @@ -325,7 +329,12 @@ class SQLiteGPKGProvider(BaseProvider): row_data = self.cursor.execute(sql_query, (identifier, )).fetchone() feature = self.__response_feature(row_data) - return feature + if feature: + return feature + else: + err = 'item {} not found'.format(identifier) + LOGGER.error(err) + raise ProviderItemNotFoundError(err) def __repr__(self): return ' {}, {}'.format(self.data, self.table) diff --git a/tests/test_csv__provider.py b/tests/test_csv__provider.py index 54ef210..f3d7dd1 100644 --- a/tests/test_csv__provider.py +++ b/tests/test_csv__provider.py @@ -29,6 +29,7 @@ import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.csv_ import CSVProvider @@ -92,9 +93,14 @@ def test_query(fixture, config): def test_get(fixture, config): p = CSVProvider(config) - results = p.get('404') - assert results is None result = p.get('964') assert result['id'] == '964' assert result['properties']['value'] == '99.9' + + +def test_get_not_existing_item_raise_exception(fixture, config): + """Testing query for a not existing object""" + p = CSVProvider(config) + with pytest.raises(ProviderItemNotFoundError): + p.get('404') diff --git a/tests/test_elasticsearch__provider.py b/tests/test_elasticsearch__provider.py index 6e79392..9ad5746 100644 --- a/tests/test_elasticsearch__provider.py +++ b/tests/test_elasticsearch__provider.py @@ -29,6 +29,7 @@ import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.elasticsearch_ import ElasticsearchProvider @@ -92,9 +93,14 @@ def test_query(config): def test_get(config): p = ElasticsearchProvider(config) - results = p.get('404') - assert results is None result = p.get('3413829') assert result['id'] == 3413829 assert result['properties']['ls_name'] == 'Reykjavik' + + +def test_get_not_existing_item_raise_exception(config): + """Testing query for a not existing object""" + p = ElasticsearchProvider(config) + with pytest.raises(ProviderItemNotFoundError): + p.get('404') diff --git a/tests/test_geojson_provider.py b/tests/test_geojson_provider.py index d390de6..0390bf4 100644 --- a/tests/test_geojson_provider.py +++ b/tests/test_geojson_provider.py @@ -31,6 +31,7 @@ import json import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.geojson import GeoJSONProvider @@ -80,6 +81,15 @@ def test_get(fixture, config): assert 'Dinagat' in results['properties']['name'] +def test_get_not_existing_item_raise_exception( + fixture, config +): + """Testing query for a not existing object""" + p = GeoJSONProvider(config) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_delete(fixture, config): p = GeoJSONProvider(config) p.delete('123-456') @@ -139,7 +149,8 @@ def test_update_safe_id(fixture, config): p.update('123-456', new_feature) # Don't let the id change, should not exist - assert p.get('SOMETHING DIFFERENT') is None + with pytest.raises(ProviderItemNotFoundError): + p.get('SOMETHING DIFFERENT') # Should still be at the old id results = p.get('123-456') diff --git a/tests/test_mongo_provider.py b/tests/test_mongo_provider.py index 87abcd5..2b09687 100644 --- a/tests/test_mongo_provider.py +++ b/tests/test_mongo_provider.py @@ -29,6 +29,7 @@ import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.mongo import MongoProvider monogourl = 'mongodb://localhost:27017/testdb' @@ -100,8 +101,6 @@ def test_query(config): def test_get(config): p = MongoProvider(config) init(p) - results = p.get('123456789012345678901234') - assert results is None res = p.query(properties=[['nameascii', 'Reykjavik']]) result = p.get(res['features'][0]['id']) @@ -109,6 +108,14 @@ def test_get(config): assert 'Reykjavik' in result['properties']['ls_name'] +def test_get_not_existing_item_raise_exception(config): + """Testing query for a not existing object""" + p = MongoProvider(config) + init(p) + with pytest.raises(ProviderItemNotFoundError): + p.get('123456789012345678901234') + + def test_get_fields(config): p = MongoProvider(config) init(p) @@ -211,7 +218,8 @@ def test_update_safe_id(config): p.update(res['features'][0]['id'], updated_feature) # Don't let the id change, should not exist - assert p.get('123456789012345678901234') is None + with pytest.raises(ProviderItemNotFoundError): + p.get('123456789012345678901234') # Should still be at the old id results = p.get(res['features'][0]['id']) diff --git a/tests/test_ogr_csv_provider.py b/tests/test_ogr_csv_provider.py index 8fa2d51..6a5b88b 100644 --- a/tests/test_ogr_csv_provider.py +++ b/tests/test_ogr_csv_provider.py @@ -34,8 +34,11 @@ import logging import pytest + +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.ogr import OGRProvider + LOGGER = logging.getLogger(__name__) @@ -85,6 +88,15 @@ def test_get_vsicurl(config_vsicurl_csv): assert '11' in result['properties']['codice_regione'] +def test_get_not_existing_feature_raise_exception( + config_vsicurl_csv +): + """Testing query for a not existing object""" + p = OGRProvider(config_vsicurl_csv) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_query_hits_vsicurl(config_vsicurl_csv): """Testing query on entire collection for hits""" diff --git a/tests/test_ogr_esrijson_provider.py b/tests/test_ogr_esrijson_provider.py index db187eb..fe538af 100644 --- a/tests/test_ogr_esrijson_provider.py +++ b/tests/test_ogr_esrijson_provider.py @@ -35,8 +35,11 @@ import logging import random import pytest + +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.ogr import OGRProvider + LOGGER = logging.getLogger(__name__) @@ -99,6 +102,15 @@ def test_get_agol(config_ArcGIS_ESRIJSON, config_random_id): assert addr_number in result['properties']['fulladdr'] +def test_get_agol_not_existing_feature_raise_exception( + config_ArcGIS_ESRIJSON +): + """Testing query for a not existing object""" + p = OGRProvider(config_ArcGIS_ESRIJSON) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_query_hits_agol(config_ArcGIS_ESRIJSON): """Testing query on entire collection for hits""" diff --git a/tests/test_ogr_gpkg_provider.py b/tests/test_ogr_gpkg_provider.py index d1f4994..9a45500 100644 --- a/tests/test_ogr_gpkg_provider.py +++ b/tests/test_ogr_gpkg_provider.py @@ -33,8 +33,10 @@ import logging import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.ogr import OGRProvider + LOGGER = logging.getLogger(__name__) @@ -78,6 +80,15 @@ def test_get(config_poi_portugal): assert 'cafe' in result['properties']['fclass'] +def test_get_not_existing_feature_raise_exception( + config_poi_portugal +): + """Testing query for a not existing object""" + p = OGRProvider(config_poi_portugal) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + # Testing with GeoPackage files with identical features # (all 2481 addresses in Otterlo Netherlands) # in different projections. diff --git a/tests/test_ogr_shapefile_provider.py b/tests/test_ogr_shapefile_provider.py index 8da1f1b..4fd3a44 100644 --- a/tests/test_ogr_shapefile_provider.py +++ b/tests/test_ogr_shapefile_provider.py @@ -33,6 +33,7 @@ import logging import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.ogr import OGRProvider LOGGER = logging.getLogger(__name__) @@ -103,6 +104,15 @@ def test_get_4326(config_shapefile_4326): assert 'Mosselsepad' in result['properties']['straatnaam'] +def test_get_not_existing_feature_raise_exception( + config_shapefile_4326 +): + """Testing query for a not existing object""" + p = OGRProvider(config_shapefile_4326) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_query_hits_28992(config_shapefile_28992): """Testing query on entire collection for hits""" diff --git a/tests/test_ogr_sqlite_provider.py b/tests/test_ogr_sqlite_provider.py index f3ad83b..d63285e 100644 --- a/tests/test_ogr_sqlite_provider.py +++ b/tests/test_ogr_sqlite_provider.py @@ -33,6 +33,7 @@ import logging import pytest +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.ogr import OGRProvider LOGGER = logging.getLogger(__name__) @@ -76,6 +77,15 @@ def test_get_4326(config_sqlite_4326): assert 'Mosselsepad' in result['properties']['straatnaam'] +def test_get_not_existing_feature_raise_exception( + config_sqlite_4326 +): + """Testing query for a not existing object""" + p = OGRProvider(config_sqlite_4326) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_query_hits_4326(config_sqlite_4326): """Testing query on entire collection for hits""" diff --git a/tests/test_ogr_wfs_provider.py b/tests/test_ogr_wfs_provider.py index 1da93c0..be28484 100644 --- a/tests/test_ogr_wfs_provider.py +++ b/tests/test_ogr_wfs_provider.py @@ -32,11 +32,14 @@ # Needs to be run like: python3 -m pytest import logging -from pygeoapi.provider.base import ProviderQueryError import pytest + +from pygeoapi.provider.base import ( + ProviderQueryError, ProviderItemNotFoundError) from pygeoapi.provider.ogr import OGRProvider + LOGGER = logging.getLogger(__name__) @@ -230,6 +233,24 @@ def test_get_gs_with_geojson_output_too_complex_raise_exception( p.get(272) +def test_get_gs_not_existing_feature_raise_exception( + config_GeoServer_WFS +): + """Testing query for a not existing object""" + p = OGRProvider(config_GeoServer_WFS) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + +def test_get_ms_not_existing_feature_raise_exception( + config_MapServer_WFS +): + """Testing query for a not existing object""" + p = OGRProvider(config_MapServer_WFS) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) + + def test_query_hits_ms(config_MapServer_WFS): """Testing query on entire collection for hits""" diff --git a/tests/test_postgresql_provider.py b/tests/test_postgresql_provider.py index 94a9e05..88e3b1e 100644 --- a/tests/test_postgresql_provider.py +++ b/tests/test_postgresql_provider.py @@ -32,6 +32,8 @@ # Needs to be run like: python3 -m pytest import pytest + +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.postgresql import PostgreSQLProvider @@ -120,3 +122,10 @@ def test_get(config): assert 'properties' in result assert 'id' in result assert 'Kanyosha' in result['properties']['name'] + + +def test_get_not_existing_item_raise_exception(config): + """Testing query for a not existing object""" + p = PostgreSQLProvider(config) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1) diff --git a/tests/test_sqlite_geopackage_provider.py b/tests/test_sqlite_geopackage_provider.py index a29b65b..76320a0 100644 --- a/tests/test_sqlite_geopackage_provider.py +++ b/tests/test_sqlite_geopackage_provider.py @@ -34,6 +34,8 @@ # (Arguments as py.test and set external variables to the correct config path) import pytest + +from pygeoapi.provider.base import ProviderItemNotFoundError from pygeoapi.provider.sqlite import SQLiteGPKGProvider @@ -149,3 +151,17 @@ def test_get_geopackage(config_geopackage): assert 'properties' in result assert 'id' in result assert 'Académico' in result['properties']['name'] + + +def test_get_sqlite_not_existing_item_raise_exception(config_sqlite): + """Testing query for a not existing object""" + p = SQLiteGPKGProvider(config_sqlite) + with pytest.raises(ProviderItemNotFoundError): + p.get(1234567890) + + +def test_get_geopackage_not_existing_item_raise_exception(config_geopackage): + """Testing query for a not existing object""" + p = SQLiteGPKGProvider(config_geopackage) + with pytest.raises(ProviderItemNotFoundError): + p.get(-1)