Files
pygeoapi/docs/source/plugins.rst
T
Sander Schaminee 023f24d26b Multilingual support (alternative) (#664)
* 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>
2021-06-08 18:46:35 -04:00

253 lines
8.5 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.. _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