023f24d26b
* Created localization (l10n) module + tests. Added l10n support to API and plugins (wip).
* Big refactor:
* All routed API methods are now decorated by @pre_process (consistency) and no longer have a headers+format argument but a request argument (**kwargs also removed)
* The pre_process decorator turns an incoming Flask/Starlette request into a generic APIRequest instance
* The new APIRequest class extracts all relevant info (params, data, locale, etc.) from the request and exposes them as properties
* Removed a lot of boilerplate (i.e. format checking) and wrapped that into methods
* Updated server-specific API calls in each route method (pass entire request object, not headers and query params)
* Several improvements and fixes:
* Updated OpenAPI page with "l" query param
* Added example translations (metadata)
* Changed plugin signature: added explicit locale attribute (instead of **kwargs)
* Moved locale processing to get_plugin_locale() function in l10n module
* API should pass raw requested locale to plugins, locale should always be set
* Fixed API tests and added APIRequest tests
* Prepared utils.py for Jinja2 i18n extension
* Rebased on commit b40297a8 and fixed compatibility with #661 and #662
* Updated documentation for language support
* Rebased and fixed compatibility with PR #658:
* Fixed EDR provider signature (added locale)
* Fixed EDR API routes and query function (and improved parameter-name handling)
* Fixed EDR tests
* Translate entire config in render_j2_template for requested locale:
* Added new translate_dict function to l10n module (+ tests)
* Updated all render_j2_template calls with locale parameter
* Updated pygeoapi-test-config.yml with some language structs
* Minor improvements
* support both 'language' and 'languages' property in server config and provider definitions
* renamed and modified translate_dict() to more generic translate_struct() function (l10n module)
* remove Content-Language header from provider responses if provider has no language support and format is json(ld)
* updated tests
* Leave provider locale handling to API
* Moved code to determine locale from providers to API class (and remove for formatters and processes)
* Removed locale parameter from plugin __init__ signatures
* Removed locale parameter from load_plugin()
* Added **kwargs to provider implementations for get, query, get_metadata, get_coverage_domainset and get_coverage_rangetype method signatures
* Added language=<locale> to all API calls to provider get, query, get_metadata, get_coverage_domainset and get_coverage_rangetype methods
* Use 'lang' instead of 'l' as language query parameter
* Updated Open API
* Updated documentation
* Fixed tests
* Implemented requested PR changes:
* Added usage examples to the APIRequest docstring
* Removed language support from coverage functions
* Updated plugins.rst and language.rst to match new behavior
* Removed language struct from resource links in pygeoapi-config.yml
* Rebased on latest master (fixed test_api.py)
* Rebased and applied fixes:
* Data property in APIRequest now is an awaitable attribute (fixed for Starlette compatibility)
* Named references to 'l' parameter to 'lang'
* Final changes/improvements:
* Make sure that Content-Language is always set;
* Added more tests to ensure that the default language returned is the first configured language (if no language was requested by the user);
* Updated docs;
* Replaced re-occuring strings with constants in api.py;
* Fixed Flake8 checks.
* add missing async to starlette routes (#704)
Co-authored-by: Tom Kralidis <tomkralidis@gmail.com>
253 lines
8.5 KiB
ReStructuredText
253 lines
8.5 KiB
ReStructuredText
.. _plugins:
|
||
|
||
Customizing pygeoapi: plugins
|
||
=============================
|
||
|
||
In this section we will explain how pygeoapi provides plugin architecture for data providers, formatters and processes.
|
||
|
||
Plugin development requires knowledge of how to program in Python as well as Python's package/module system.
|
||
|
||
Overview
|
||
--------
|
||
|
||
pygeoapi provides a robust plugin architecture that enables developers to extend functionality. Infact,
|
||
pygeoapi itself implements numerous formats, data providers and the process functionality as plugins.
|
||
|
||
The pygeoapi architecture supports the following subsystems:
|
||
|
||
- data providers
|
||
- output formats
|
||
- processes
|
||
|
||
The core pygeoapi plugin registry can be found in ``pygeoapi.plugin.PLUGINS``.
|
||
|
||
Each plugin type implements its relevant base class as the API contract:
|
||
|
||
- data providers: ``pygeoapi.provider.base``
|
||
- output formats: ``pygeoapi.formatter.base``
|
||
- processes: ``pygeoapi.process.base``
|
||
|
||
.. todo:: link PLUGINS to API doc
|
||
|
||
Plugins can be developed outside of the pygeoapi codebase and be dynamically loaded
|
||
by way of the pygeoapi configuration. This allows your custom plugins to live outside
|
||
pygeoapi for easier maintenance of software updates.
|
||
|
||
.. note::
|
||
It is recommended to store pygeoapi plugins outside of pygeoapi for easier software
|
||
updates and package management
|
||
|
||
|
||
Example: custom pygeoapi vector data provider
|
||
---------------------------------------------
|
||
|
||
Lets consider the steps for a vector data provider plugin (source code is located here: :ref:`data Provider`).
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycoolvectordata.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
from pygeoapi.provider.base import BaseProvider
|
||
|
||
class MyCoolVectorDataProvider(BaseProvider):
|
||
"""My cool vector data provider"""
|
||
|
||
def __init__(self, provider_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__(provider_def)
|
||
|
||
def get_fields(self):
|
||
|
||
# open dat file and return fields and their datatypes
|
||
return {
|
||
'field1': 'string',
|
||
'field2': 'string'
|
||
}
|
||
|
||
def query(self,startindex=0, limit=10, resulttype='results',
|
||
bbox=[], datetime_=None, properties=[], sortby=[],
|
||
select_properties=[], skip_geometry=False, **kwargs):
|
||
|
||
# open data file (self.data) and process, return
|
||
return {
|
||
'type': 'FeatureCollection',
|
||
'features': [{
|
||
'type': 'Feature',
|
||
'id': '371',
|
||
'geometry': {
|
||
'type': 'Point',
|
||
'coordinates': [ -75, 45 ]
|
||
},
|
||
'properties': {
|
||
'stn_id': '35',
|
||
'datetime': '2001-10-30T14:24:55Z',
|
||
'value': '89.9'
|
||
}
|
||
}]
|
||
}
|
||
|
||
|
||
For brevity, the above code will always return the single feature of the dataset. In reality, the plugin
|
||
developer would connect to a data source with capabilities to run queries and return a relevant result set,
|
||
as well as implement the ``get`` method accordingly. As long as the plugin implements the API contract of
|
||
its base provider, all other functionality is left to the provider implementation.
|
||
|
||
Each base class documents the functions, arguments and return types required for implementation.
|
||
|
||
.. note:: You can add language support to your plugin using :ref:`these guides<language>`.
|
||
|
||
|
||
Connecting to pygeoapi
|
||
^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The following methods are options to connect the plugin to pygeoapi:
|
||
|
||
**Option 1**: Update in core pygeoapi:
|
||
|
||
- copy ``mycoolvectordata.py`` into ``pygeoapi/provider``
|
||
- update the plugin registry in ``pygeoapi/plugin.py:PLUGINS['provider']`` with the plugin's
|
||
shortname (say ``MyCoolVectorData``) and dotted path to the class (i.e. ``pygeoapi.provider.mycoolvectordata.MyCoolVectorDataProvider``)
|
||
- specify in your dataset provider configuration as follows:
|
||
|
||
.. code-block:: yaml
|
||
|
||
providers:
|
||
- type: feature
|
||
name: MyCoolVectorData
|
||
data: /path/to/file
|
||
id_field: stn_id
|
||
|
||
|
||
**Option 2**: implement outside of pygeoapi and add to configuration (recommended)
|
||
|
||
- create a Python package of the ``mycoolvectordata.py`` module (see `Cookiecutter`_ as an example)
|
||
- install your Python package onto your system (``python setup.py install``). At this point your new package
|
||
should be in the ``PYTHONPATH`` of your pygeoapi installation
|
||
- specify in your dataset provider configuration as follows:
|
||
|
||
.. code-block:: yaml
|
||
|
||
providers:
|
||
- type: feature
|
||
name: mycooldatapackage.mycoolvectordata.MyCoolVectorDataProvider
|
||
data: /path/to/file
|
||
id_field: stn_id
|
||
|
||
BEGIN
|
||
|
||
Example: custom pygeoapi raster data provider
|
||
---------------------------------------------
|
||
|
||
Lets consider the steps for a raster data provider plugin (source code is located here: :ref:`data Provider`).
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycoolrasterdata.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
from pygeoapi.provider.base import BaseProvider
|
||
|
||
class MyCoolRasterDataProvider(BaseProvider):
|
||
"""My cool raster data provider"""
|
||
|
||
def __init__(self, provider_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__(provider_def)
|
||
self.num_bands = 4
|
||
self.axes = ['Lat', 'Long']
|
||
|
||
def get_coverage_domainset(self):
|
||
# return a CIS JSON DomainSet
|
||
|
||
def get_coverage_rangetype(self):
|
||
# return a CIS JSON RangeType
|
||
|
||
def query(self, bands=[], subsets={}, format_='json', **kwargs):
|
||
# process bands and subsets parameters
|
||
# query/extract coverage data
|
||
if format_ == 'json':
|
||
# return a CoverageJSON representation
|
||
return {'type': 'Coverage', ...} # trimmed for brevity
|
||
else:
|
||
# return default (likely binary) representation
|
||
return bytes(112)
|
||
|
||
For brevity, the above code will always JSON for metadata and binary or CoverageJSON for the data. In reality, the plugin
|
||
developer would connect to a data source with capabilities to run queries and return a relevant result set,
|
||
As long as the plugin implements the API contract of its base provider, all other functionality is left to the provider
|
||
implementation.
|
||
|
||
Each base class documents the functions, arguments and return types required for implementation.
|
||
|
||
END
|
||
|
||
Example: custom pygeoapi formatter
|
||
----------------------------------
|
||
|
||
Python code
|
||
^^^^^^^^^^^
|
||
|
||
The below template provides a minimal example (let's call the file ``mycooljsonformat.py``:
|
||
|
||
.. code-block:: python
|
||
|
||
import json
|
||
from pygeoapi.formatter.base import BaseFormatter
|
||
|
||
class MyCoolJSONFormatter(BaseFormatter):
|
||
"""My cool JSON formatter"""
|
||
|
||
def __init__(self, formatter_def):
|
||
"""Inherit from parent class"""
|
||
|
||
super().__init__({'name': 'cooljson', 'geom': None})
|
||
self.mimetype = 'text/json; subtype:mycooljson'
|
||
|
||
def write(self, options={}, data=None):
|
||
"""custom writer"""
|
||
|
||
out_data {'rows': []}
|
||
|
||
for feature in data['features']:
|
||
out_data.append(feature['properties'])
|
||
|
||
return out_data
|
||
|
||
|
||
Processing plugins
|
||
------------------
|
||
|
||
Processing plugins are following the OGC API - Processes development. Given that the specification is
|
||
under development, the implementation in ``pygeoapi/process/hello_world.py`` provides a suitable example
|
||
for the time being.
|
||
|
||
|
||
Featured plugins
|
||
----------------
|
||
|
||
The following plugins provide useful examples of pygeoapi plugins implemented
|
||
by downstream applications.
|
||
|
||
.. csv-table::
|
||
:header: "Plugin(s)", "Organization/Project","Description"
|
||
:align: left
|
||
|
||
`msc-pygeoapi`_,Meteorological Service of Canada,processes for weather/climate/water data workflows
|
||
`pygeoapi-kubernetes-papermill`_,Euro Data Cube,processes for executing Jupyter notebooks via Kubernetes
|
||
`local-outlier-factor-plugin`_,Manaaki Whenua – Landcare Research,processes for local outlier detection
|
||
`ogc-edc`_,Euro Data Cube,coverage provider atop the EDC API
|
||
|
||
|
||
.. _`Cookiecutter`: https://github.com/audreyr/cookiecutter-pypackage
|
||
.. _`msc-pygeoapi`: https://github.com/ECCC-MSC/msc-pygeoapi
|
||
.. _`pygeoapi-kubernetes-papermill`: https://github.com/eurodatacube/pygeoapi-kubernetes-papermill
|
||
.. _`local-outlier-factor-plugin`: https://github.com/manaakiwhenua/local-outlier-factor-plugin
|
||
.. _`ogc-edc`: https://github.com/eurodatacube/ogc-edc/tree/oapi/edc_ogc/pygeoapi
|