Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ default.
- name: path.to.formatter # Python path of formatter definition
attachment: true # whether or not to provide as an attachment or normal response
geom: false # whether or not to include geometry
validator:
name: path.to.validator # Python path of validation definition

hello-world: # name of process
type: process # REQUIRED (collection, process, or stac-collection)
Expand Down
2 changes: 1 addition & 1 deletion docs/source/cql2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Queries

The PostgreSQL provider uses `pygeofilter <https://github.com/geopython/pygeofilter>`_ allowing a range of filter expressions, see examples for:

* `Comparison predicates (`Advanced <https://docs.ogc.org/is/21-065r2/21-065r2.html#advanced-comparison-operators>`_, `Case-insensitive <https://docs.ogc.org/is/21-065r2/21-065r2.html#case-insensitive-comparison>`_)
* Comparison predicates (`Advanced <https://docs.ogc.org/is/21-065r2/21-065r2.html#advanced-comparison-operators>`_, `Case-insensitive <https://docs.ogc.org/is/21-065r2/21-065r2.html#case-insensitive-comparison>`_)
* `Spatial predicates <https://docs.ogc.org/is/21-065r2/21-065r2.html#spatial-functions>`_
* `Temporal predicates <https://docs.ogc.org/is/21-065r2/21-065r2.html#temporal-functions>`_

Expand Down
38 changes: 37 additions & 1 deletion docs/source/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ Below is a sample process definition as a Python dictionary:

.. note::

Additional processing plugins can also be found in ``pygeoapi/process``.
Additional processing plugins can be found in ``pygeoapi/process``.

.. _example-custom-pygeoapi-formatter:

Expand Down Expand Up @@ -503,6 +503,42 @@ The below template provides a minimal example (let's call the file ``mycooljsonf

return out_data

Example: custom pygeoapi validator
----------------------------------

Python code
^^^^^^^^^^^

The below template provides a minimal example (let's call the file ``mycooldatavalidator.py``:

.. code-block:: python

from typing import Any

from pygeoapi.validator.base import BaseValidator, ValidatorValidationError

class MyCoolDataValidator(BaseValidator):
def __init__(self, validator_def):
"""Inherit from parent class"""

super().__init__(validator_def)

def validate(self, data: Any, partial: bool = False) -> None:
if partial: # plugin does not support partial updates to a given item (PATCH)
msg = 'Partial validation not supported'
raise ValidatorValidationError(msg)

# data is a dict of incoming data, validate accordingly
if 'some_property' not in data:
msg = 'Invalid data payload!' # to add more detailed messaging, pass user_msg="string of text" to ValidatorValidationError
raise ValidatorValidationError(msg)

def __repr__(self):
return '<MyCoolValidator>'

.. note::

Additional validator plugins can be found in ``pygeoapi/validator``.

Featured plugins
----------------
Expand Down
28 changes: 26 additions & 2 deletions docs/source/transactions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pygeoapi supports the `OGC API - Features - Part 4: Create, Replace, Update and
for transactional capabilities against feature and record data.

To enable transactions in pygeoapi, a given resource provider needs to be editable (via the configuration resource provider
``editable: true`` property). Note that the feature or record provider MUST support create/update/delete. See the
:ref:`ogcapi-features` and :ref:`ogcapi-records` documentation for transaction support status of pygeoapi backends.
``editable: true`` property). Note that the feature or record provider MUST support create/update/delete. See
:ref:`ogcapi-features` and :ref:`ogcapi-records` for transaction support status of pygeoapi backends.

Access control
^^^^^^^^^^^^^^
Expand All @@ -17,3 +17,27 @@ It should be made clear that authentication and authorization is beyond the resp
if a pygeoapi user enables transactions, they must provide access control explicitly via another service.

.. _`OGC API - Features - Part 4: Create, Replace, Update and Delete`: https://docs.ogc.org/DRAFTS/20-002.html

Validation
^^^^^^^^^^

pygeoapi transaction support includes the option to implement custom validation when adding or updating features or records.

To enable validation in transactions in pygeoapi, a given resource provider can specify a custom validator plugin to implement
custom business rules as needed to ensure data is valid prior to adding or updating a given provider backend.

Given the example below:

.. code-block:: yaml

providers:
- type: feature
name: Elasticsearch
data /path/to/file
id_field: stn_id
editable: true
validator:
name: mycooldatapackage.mycooldatavalidator.MyCoolDataValidator

The ``validator`` element refers to a Python module/class that implements a pygeoapi validator plugin. See :ref:`plugins`
for more information on implementing validator plugins.
22 changes: 22 additions & 0 deletions pygeoapi/api/itemtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,28 @@ def manage_collection_item(
HTTPStatus.BAD_REQUEST, headers, request.format,
'InvalidParameterValue', msg)

if action in ['create', 'update']:
if p.validator is not None:
LOGGER.debug('Provider is configured for validation')
LOGGER.debug('Loading validator')
try:
v = load_plugin('validator', {'name': p.validator['name']})
except Exception:
msg = 'Invalid validator configured'
return api.get_exception(
HTTPStatus.INTERNAL_SERVER_ERROR, headers, request.format,
'NoApplicableCode', msg)

LOGGER.debug('Validating item')
try:
v.validate(request.data)
except Exception as err:
msg = err.user_msg or 'Item is not valid, please check and validate payload' # noqa
LOGGER.error(f'Validation errors: {err.message}')
return api.get_exception(
HTTPStatus.INTERNAL_SERVER_ERROR, headers, request.format,
'InvalidParameterValue', msg)

if action == 'create':
LOGGER.debug('Creating item')
try:
Expand Down
3 changes: 3 additions & 0 deletions pygeoapi/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
'HTTP': 'pygeoapi.pubsub.http.HTTPPubSubClient',
'Kafka': 'pygeoapi.pubsub.kafka.KafkaPubSubClient',
'MQTT': 'pygeoapi.pubsub.mqtt.MQTTPubSubClient'
},
'validator': {
'GeoJSON': 'pygeoapi.validator.geojson.GeoJSONValidator'
}
}

Expand Down
3 changes: 2 additions & 1 deletion pygeoapi/provider/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(self, provider_def):
self.include_extra_query_parameters = provider_def.get('include_extra_query_parameters', False) # noqa
self._fields = {}
self.filename = None
self.validator = provider_def.get('validator')

# CRS properties
storage_crs_uri = provider_def.get('storage_crs', DEFAULT_STORAGE_CRS)
Expand Down Expand Up @@ -337,7 +338,7 @@ class ProviderTypeError(ProviderGenericError):

class ProviderInvalidQueryError(ProviderGenericError):
"""provider invalid query error"""
ogc_exception_code = 'InvalidQuery'
ogc_exception_code = 'InvalidParameterValue'
http_status_code = HTTPStatus.BAD_REQUEST
default_msg = "query error"

Expand Down
11 changes: 10 additions & 1 deletion pygeoapi/resources/schemas/config/pygeoapi-config-0.x.yml
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,15 @@ properties:
description: whether to provide as an attachment
required:
- name
validator:
type: object
description: custom validator to apply on transactions
properties:
name:
type: string
description: name of validator
required:
- name
required:
- type
- title
Expand Down Expand Up @@ -753,4 +762,4 @@ required:
- server
- logging
- metadata
- resources
- resources
Loading
Loading