diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 62a8cbc..2ba33dc 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -202,6 +202,31 @@ default. :ref:`plugins` for more information on plugins +Publishing non-advertised resources +----------------------------------- + +pygeoapi allows for publishing resources without advertising them explicitly +via its collections and OpenAPI endpoints. The resource is available if the +client knows the name of the resource apriori. + +To provide non-advertised resources, the resource name must start with ``_``. For +example, considering the following resource: + +.. code-block:: yaml + + resources: + _foo: + title: my non-advertised resource + +Examples: + +.. code-block:: bash + + curl https://example.org/collections # resource _foo is not advertised + curl https://example.org/openapi # resource _foo is not advertised + curl https://example.org/collections/_foo # user can access resource normally + + Validating the configuration ---------------------------- diff --git a/docs/source/data-publishing/index.rst b/docs/source/data-publishing/index.rst index feb64b5..c66aeed 100644 --- a/docs/source/data-publishing/index.rst +++ b/docs/source/data-publishing/index.rst @@ -25,3 +25,7 @@ return back data to the pygeoapi API framework in a plug and play fashion. ogcapi-records ogcapi-edr stac + + +.. seealso:: + :ref:`configuration` for more information on publishing any resource as non-advertised. diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 7e848f3..fcb6f95 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -844,6 +844,9 @@ class API: LOGGER.debug('Creating collections') for k, v in collections_dict.items(): + if k.startswith('_'): + LOGGER.debug('Skipping hidden layer: {}'.format(k)) + continue collection_data = get_provider_default(v['providers']) collection_data_type = collection_data['type'] diff --git a/pygeoapi/openapi.py b/pygeoapi/openapi.py index 5879217..ada462a 100644 --- a/pygeoapi/openapi.py +++ b/pygeoapi/openapi.py @@ -421,6 +421,9 @@ def get_oas_30(cfg): 'type', 'collection') for k, v in collections.items(): + if k.startswith('_'): + LOGGER.debug('Skipping hidden layer: {}'.format(k)) + continue name = l10n.translate(k, locale_) title = l10n.translate(v['title'], locale_) desc = l10n.translate(v['description'], locale_) @@ -884,6 +887,9 @@ def get_oas_30(cfg): LOGGER.debug('setting up processes') for k, v in processes.items(): + if k.startswith('_'): + LOGGER.debug('Skipping hidden layer: {}'.format(k)) + continue name = l10n.translate(k, locale_) p = load_plugin('process', v['processor']) diff --git a/tests/pygeoapi-test-config-non-advertised-resources.yml b/tests/pygeoapi-test-config-non-advertised-resources.yml new file mode 100644 index 0000000..36cea64 --- /dev/null +++ b/tests/pygeoapi-test-config-non-advertised-resources.yml @@ -0,0 +1,180 @@ +# ================================================================= +# +# Authors: Tom Kralidis +# +# Copyright (c) 2019 Tom Kralidis +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= + +server: + bind: + host: 0.0.0.0 + port: 5000 + url: http://localhost:5000/ + mimetype: application/json; charset=UTF-8 + encoding: utf-8 + gzip: false + languages: + # First language is the default language + - en-US + - fr-CA + cors: true + pretty_print: true + limit: 10 + # templates: /path/to/templates + map: + url: https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png + attribution: 'Wikimedia maps | Map data © OpenStreetMap contributors' + manager: + name: TinyDB + connection: /tmp/pygeoapi-test-process-manager.db + output_dir: /tmp + +logging: + level: ERROR + #logfile: /tmp/pygeoapi.log + +metadata: + identification: + title: + en: pygeoapi default instance + fr: instance par défaut de pygeoapi + description: + en: pygeoapi provides an API to geospatial data + fr: pygeoapi fournit une API aux données géospatiales + keywords: + en: + - geospatial + - data + - api + fr: + - géospatiale + - données + - api + keywords_type: theme + terms_of_service: https://creativecommons.org/licenses/by/4.0/ + url: http://example.org + license: + name: CC-BY 4.0 license + url: https://creativecommons.org/licenses/by/4.0/ + provider: + name: Organization Name + url: https://pygeoapi.io + contact: + name: Lastname, Firstname + position: Position Title + address: Mailing Address + city: City + stateorprovince: Administrative Area + postalcode: Zip or Postal Code + country: Country + phone: +xx-xxx-xxx-xxxx + fax: +xx-xxx-xxx-xxxx + email: you@example.org + url: Contact URL + hours: Hours of Service + instructions: During hours of service. Off on weekends. + role: pointOfContact + +resources: + _obs: + type: collection + title: + en: Observations + fr: Observations + description: + en: My cool observations + fr: Mes belles observations + keywords: + - observations + - monitoring + links: + - type: text/csv + rel: canonical + title: data + href: https://github.com/mapserver/mapserver/blob/branch-7-0/msautotest/wxs/data/obs.csv + hreflang: en-US + - type: text/csv + rel: alternate + title: data + href: https://raw.githubusercontent.com/mapserver/mapserver/branch-7-0/msautotest/wxs/data/obs.csv + hreflang: en-US + context: + - schema: https://schema.org/ + stn_id: + "@id": schema:identifier + "@type": schema:Text + datetime: + "@type": schema:DateTime + "@id": schema:observationDate + value: + "@type": schema:Number + "@id": schema:QuantitativeValue + extents: + spatial: + bbox: [-180,-90,180,90] + crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84 + temporal: + begin: 2000-10-30T18:24:39Z + end: 2007-10-30T08:57:29Z + trs: http://www.opengis.net/def/uom/ISO-8601/0/Gregorian + providers: + - type: feature + name: CSV + data: tests/data/obs.csv + id_field: id + geometry: + x_field: long + y_field: lat + + objects: + type: collection + title: GeoJSON objects + description: GeoJSON geometry types for GeoSparql and Schema Geometry conversion. + keywords: + - shapes + links: + - type: text/html + rel: canonical + title: data source + href: https://en.wikipedia.org/wiki/GeoJSON + hreflang: en-US + extents: + spatial: + bbox: [-180,-90,180,90] + crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84 + temporal: + begin: null + end: null # or empty (either means open ended) + providers: + - type: feature + name: GeoJSON + data: tests/data/items.geojson + id_field: fid + uri_field: uri + + hello-world: + type: process + processor: + name: HelloWorld diff --git a/tests/test_api.py b/tests/test_api.py index bd73e5e..54ac2b2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -51,6 +51,13 @@ def config(): return yaml_load(fh) +@pytest.fixture() +def config_non_advertised_resources(): + filename = 'pygeoapi-test-config-non-advertised-resources.yml' + with open(get_test_file_path(filename)) as fh: + return yaml_load(fh) + + @pytest.fixture() def openapi(): with open(get_test_file_path('pygeoapi-test-openapi.yml')) as fh: @@ -62,6 +69,11 @@ def api_(config): return API(config) +@pytest.fixture() +def api_non_advertised_resources(config_non_advertised_resources): + return API(config_non_advertised_resources) + + def test_apirequest(api_): # Test without (valid) locales with pytest.raises(ValueError): @@ -500,6 +512,15 @@ def test_describe_collections(config, api_): assert collection['id'] == 'naturalearth/lakes' +def test_describe_collections_non_advertised_resources( + config_non_advertised_resources, api_non_advertised_resources): + req = mock_request({}) + rsp_headers, code, response = api_non_advertised_resources.describe_collections(req) # noqa + assert code == 200 + + print(response) + + def test_get_collection_queryables(config, api_): req = mock_request() rsp_headers, code, response = api_.get_collection_queryables(req, diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 24e7560..bbdb094 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.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 @@ -44,6 +44,13 @@ def config(): return yaml_load(fh) +@pytest.fixture() +def config_non_advertised_resources(): + filename = 'pygeoapi-test-config-non-advertised-resources.yml' + with open(get_test_file_path(filename)) as fh: + return yaml_load(fh) + + @pytest.fixture() def openapi(): with open(get_test_file_path('pygeoapi-test-openapi.yml')) as fh: @@ -83,3 +90,10 @@ def test_validate_openapi_document(openapi): with pytest.raises(ValidationError): is_valid = validate_openapi_document({'foo': 'bar'}) + + +def test_non_advertised_resources(config_non_advertised_resources): + openapi_doc = get_oas(config_non_advertised_resources) + + assert '/collections/_obs' not in openapi_doc['paths'] + assert '/collections/_obs/items' not in openapi_doc['paths']