[Git][debian-gis-team/pywps][upstream] New upstream version 4.5.0

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Thu Aug 12 15:36:33 BST 2021



Bas Couwenberg pushed to branch upstream at Debian GIS Project / pywps


Commits:
25ad6aa4 by Bas Couwenberg at 2021-08-12T16:26:32+02:00
New upstream version 4.5.0
- - - - -


26 changed files:

- VERSION.txt
- debian/changelog
- + docs/api_rest.rst
- docs/index.rst
- pywps/__init__.py
- pywps/app/Process.py
- pywps/app/Service.py
- pywps/app/WPSRequest.py
- pywps/app/basic.py
- pywps/configuration.py
- pywps/exceptions.py
- + pywps/inout/array_encode.py
- pywps/inout/basic.py
- pywps/inout/formats/__init__.py
- pywps/inout/inputs.py
- pywps/inout/literaltypes.py
- pywps/inout/outputs.py
- + pywps/inout/types.py
- pywps/response/__init__.py
- pywps/response/capabilities.py
- pywps/response/describe.py
- pywps/response/execute.py
- pywps/tests.py
- setup.cfg
- tests/test_execute.py
- tests/test_processing.py


Changes:

=====================================
VERSION.txt
=====================================
@@ -1 +1 @@
-4.4.5
+4.5.0


=====================================
debian/changelog
=====================================
@@ -1,8 +1,14 @@
+pywps (4.5.0) trusty; urgency=medium
+
+  * Initial implementation of OGC API - Processes / REST API (#612, #614)
+
+ -- Carsten Ehbrecht <ehbrecht at dkrz.de>  Thu, 12 Aug 2021 18:00:00 +0000
+
 pywps (4.4.5) trusty; urgency=medium
 
   * Fixed lxml default parser (#616).
 
- -- Carsten Ehbrecht <ehbrecht at dkrz.de>  Tue, 10 Aug 2021 18:00:00 +0000
+-- Carsten Ehbrecht <ehbrecht at dkrz.de>  Tue, 10 Aug 2021 18:00:00 +0000
 
 pywps (4.4.4) trusty; urgency=medium
 


=====================================
docs/api_rest.rst
=====================================
@@ -0,0 +1,407 @@
+.. _api_rest:
+
+###################
+PyWPS Rest API Doc
+###################
+
+Since version 4.5, PyWPS includes an experimental implementation of the novel OGC API.
+This standard defines the OGC API - Processes API standard.
+This standard builds on the OGC Web Processing Service (WPS) 2.0 Standard
+and defines the processing interface to communicate over a RESTful protocol using JSON encodings.
+
+For more details about the standard please refer to https://github.com/opengeospatial/ogcapi-processes
+
+Defining the input/output format (JSON or XML)
+================================================
+
+WPS 1.0 standard defines input and outputs in XML format.
+OGC API - Processes: rest-api, json.
+PyWPS >= 4.5 allows inputs and outputs to be in both XML and JSON formats.
+
+The default format (mimetype) of the input/output is determinate by the URL:
+
+* Default XML - if the url starts with `/wps`
+* Default JSON - if the url starts with `/jobs` or `/processes`
+
+Please refer to `app.basic.parse_http_url` for full details about those defaults.
+
+
+GET request:
+-------------
+
+The default mimetype (output format) can be set by adding `&f=json` or `&f=xml` parameter.
+
+GET GetCapabilities Request URL:
+
+.. code-block::
+
+    http://localhost:5000/processes/?service=WPS
+    http://localhost:5000/wps/?request=GetCapabilities&service=WPS&f=json
+
+GET GetCapabilities Response:
+
+.. code-block::
+
+    {
+      "pywps_version": "4.5.0",
+      "version": "1.0.0",
+      "title": "PyWPS WPS server",
+      "abstract": "PyWPS WPS server server.",
+      "keywords": [
+        "WPS",
+        "PyWPS",
+      ],
+      "provider": {
+        "name": "PyWPS Development team",
+        "site": "https://github.com/geopython/pywps-flask",
+      },
+      "serviceurl": "http://localhost:5000/wps",
+      "languages": [
+        "en-US"
+      ],
+      "language": "en-US",
+      "processes": [
+        {
+          "class": "processes.sayhello:SayHello",
+          "uuid": "None",
+          "workdir": null,
+          "version": "1.3.3.8",
+          "identifier": "say_hello",
+          "title": "Process Say Hello",
+          "abstract": "Returns a literal string output with Hello plus the inputed name",
+          "keywords": [],
+          "metadata": [],
+          "inputs": [
+            {
+              "identifier": "name",
+              "title": "Input name",
+              "abstract": "",
+              "keywords": [],
+              "metadata": [],
+              "type": "literal",
+              "data_type": "string",
+              "workdir": null,
+              "allowed_values": [],
+              "any_value": false,
+              "mode": 1,
+              "min_occurs": 1,
+              "max_occurs": 1,
+              "translations": null,
+              "data": "World"
+            }
+          ],
+          "outputs": [
+            {
+              "identifier": "output",
+              "title": "Output response",
+              "abstract": "",
+              "keywords": [],
+              "data": null,
+              "data_type": "string",
+              "type": "literal",
+              "uoms": [],
+              "translations": null
+            }
+          ],
+          "store_supported": "true",
+          "status_supported": "true",
+          "profile": [],
+          "translations": null
+        }
+      ]
+    }
+
+GET DescribeProcess Request URL:
+
+.. code-block::
+
+    http://localhost:5000/processes/say_hello?service=WPS
+    http://localhost:5000/wps/?request=DescribeProcess&service=WPS&identifier=say_hello&version=1.0.0&f=json
+
+GET DescribeProcess Response:
+
+.. code-block::
+
+    {
+      "pywps_version": "4.5.0",
+      "processes": [
+        {
+          "class": "processes.sayhello:SayHello",
+          "uuid": "None",
+          "workdir": null,
+          "version": "1.3.3.8",
+          "identifier": "say_hello",
+          "title": "Process Say Hello",
+          "abstract": "Returns a literal string output with Hello plus the inputed name",
+          "keywords": [],
+          "metadata": [],
+          "inputs": [
+            {
+              "identifier": "name",
+              "title": "Input name",
+              "abstract": "",
+              "keywords": [],
+              "metadata": [],
+              "type": "literal",
+              "data_type": "string",
+              "workdir": null,
+              "allowed_values": [],
+              "any_value": false,
+              "mode": 1,
+              "min_occurs": 1,
+              "max_occurs": 1,
+              "translations": null,
+              "data": "World"
+            }
+          ],
+          "outputs": [
+            {
+              "identifier": "output",
+              "title": "Output response",
+              "abstract": "",
+              "keywords": [],
+              "data": null,
+              "data_type": "string",
+              "type": "literal",
+              "uoms": [],
+              "translations": null
+            }
+          ],
+          "store_supported": "true",
+          "status_supported": "true",
+          "profile": [],
+          "translations": null
+        }
+      ],
+      "language": "en-US"
+    }
+
+GET Execute Request URL:
+
+.. code-block::
+
+    http://localhost:5000/wps?/service=wps&version=1.0.0&request=execute&Identifier=say_hello&storeExecuteResponse=true&DataInputs=name=Dude&f=json
+
+GET Execute Response:
+
+.. code-block::
+
+    {
+        "status": {
+            "status": "succeeded",
+            "time": "2021-06-15T14:19:28Z",
+            "percent_done": "100",
+            "message": "PyWPS Process Process Say Hello finished"
+        },
+        "outputs": {
+            "output": "Hello Dude"
+        }
+    }
+
+GET Execute Request URL (Raw output):
+
+.. code-block::
+
+    http://localhost:5000/wps?/service=wps&version=1.0.0&request=execute&Identifier=say_hello&storeExecuteResponse=true&DataInputs=name=Dude&RawDataOutput=output
+
+GET Execute Response:
+
+.. code-block::
+
+    Hello Dude
+
+
+POST request:
+---------------
+
+The default mimetype (input and output formats) can be changed by setting the following headers
+of a POST request to one following values `text/xml` or `application/json`:
+
+    * `Content-Type` (format of the input)
+    * `Accept` (format of the output)
+
+Example of a `Say Hello` POST request:
+
+POST Execute Request URL:
+
+.. code-block::
+
+    http://localhost:5000/jobs
+
+POST Execute Request Body:
+
+.. code-block::
+
+    {
+        "identifier": "say_hello",
+        "inputs": {
+            "name": "Dude"
+        }
+    }
+
+POST Execute Response:
+
+.. code-block::
+
+    {
+        "status": {
+            "status": "succeeded",
+            "time": "2021-06-15T14:19:28Z",
+            "percent_done": "100",
+            "message": "PyWPS Process Process Say Hello finished"
+        },
+        "outputs": {
+            "output": "Hello Dude"
+        }
+    }
+
+
+Example of a `Say Hello` POST request with raw output:
+
+POST Execute Request Body:
+
+.. code-block::
+
+    {
+        "identifier": "say_hello",
+        "outputs": "output",
+        "inputs": {
+            "name": "Dude"
+        }
+    }
+
+
+POST Execute Response:
+
+.. code-block::
+
+    Hello Dude
+
+Alternatively, the `identifier` and optionally the raw output name can be encoded in the Request URL:
+
+POST Execute Request URL (with `identifier`):
+
+.. code-block::
+
+    http://localhost:5000/jobs/say_hello
+
+POST Execute Request Body:
+
+.. code-block::
+
+    {
+        "name": "Dude"
+    }
+
+POST Execute Response:
+
+.. code-block::
+
+    {
+        "status": {
+            "status": "succeeded",
+            "time": "2021-06-15T14:19:28Z",
+            "percent_done": "100",
+            "message": "PyWPS Process Process Say Hello finished"
+        },
+        "outputs": {
+            "output": "Hello Dude"
+        }
+    }
+
+POST Execute Request URL (with `identifier` and output name):
+
+.. code-block::
+
+    http://localhost:5000/jobs/say_hello/output
+
+POST Execute Request Body:
+
+.. code-block::
+
+    {
+        "name": "Dude"
+    }
+
+POST Execute Response:
+
+.. code-block::
+
+    Hello Dude
+
+
+Example for a reference input:
+
+.. code-block::
+
+    "raster": {
+        "type": "reference",
+        "href": "file:./path/to/data/data.tif"
+    }
+
+Example for a BoundingBox input:
+(bbox default axis order is yx (EPSG:4326), i.e. miny, minx, maxy, maxx)
+
+.. code-block::
+
+    "extent": {
+        "type": "bbox",
+        "bbox": [32, 34.7, 32.1, 34.8]
+    }
+
+
+Example for a ComplexInput input:
+(the data is a standard GeoJSON)
+
+.. code-block::
+
+    "cutline": {
+        "type": "complex",
+        "data": {
+            "type": "FeatureCollection",
+            "name": "Center",
+            "crs": {
+                "type": "name",
+                "properties": {
+                    "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+                }
+            },
+            "features": [
+                {
+                    "type": "Feature",
+                    "properties": {},
+                    "geometry": {
+                        "type": "Polygon",
+                        "coordinates": [
+                            [
+                                [
+                                    34.76844787397541,
+                                    32.07247233606565
+                                ],
+                                [
+                                    34.78658619364754,
+                                    32.07260143442631
+                                ],
+                                [
+                                    34.77780750512295,
+                                    32.09532274590172
+                                ],
+                                [
+                                    34.76844787397541,
+                                    32.07247233606565
+                                ]
+                            ]
+                        ]
+                    }
+                }
+            ]
+        }
+    }
+
+
+The examples above show some `Literal`, 'Complex', `BoundingBox` inputs.
+Internally, PyWPS always keeps the inputs in JSON formats (also in previous versions)
+So potentially all input types that are supported in XML should also be supported in JSON,
+though only a small subset of them were tested in this preliminary implementation.
+
+Multiple inputs for the same parameter can be passed by using a list as the parameter value.


=====================================
docs/index.rst
=====================================
@@ -38,6 +38,7 @@ Contents:
    external-tools
    extensions
    api
+   api_rest
    contributing
    exceptions
 


=====================================
pywps/__init__.py
=====================================
@@ -9,7 +9,7 @@ import os
 
 from lxml.builder import ElementMaker
 
-__version__ = "4.4.5"
+__version__ = "4.5.0"
 
 LOGGER = logging.getLogger('PYWPS')
 LOGGER.debug('setting core variables')


=====================================
pywps/app/Process.py
=====================================
@@ -252,7 +252,7 @@ class Process(object):
             if wps_response.status != WPS_STATUS.SUCCEEDED and wps_response.status != WPS_STATUS.FAILED:
                 # if (not wps_response.status_percentage) or (wps_response.status_percentage != 100):
                 LOGGER.debug('Updating process status to 100% if everything went correctly')
-                wps_response._update_status(WPS_STATUS.SUCCEEDED, 'PyWPS Process {} finished'.format(self.title), 100)
+                wps_response._update_status(WPS_STATUS.SUCCEEDED, f'PyWPS Process {self.title} finished', 100)
         except Exception as e:
             traceback.print_exc()
             LOGGER.debug('Retrieving file and line number where exception occurred')
@@ -287,7 +287,7 @@ class Process(object):
             wps_response._update_status(WPS_STATUS.FAILED, msg, 100)
 
         finally:
-            # The run of the next pending request if finished here, weather or not it successfull
+            # The run of the next pending request if finished here, weather or not it successful
             self.launch_next_process()
 
         return wps_response


=====================================
pywps/app/Service.py
=====================================
@@ -5,6 +5,8 @@
 
 import logging
 import tempfile
+from typing import Sequence, Optional, Dict
+
 from werkzeug.exceptions import HTTPException
 from werkzeug.wrappers import Request, Response
 from urllib.parse import urlparse
@@ -39,9 +41,10 @@ class Service(object):
     :param cfgfiles: A list of configuration files
     """
 
-    def __init__(self, processes=[], cfgfiles=None):
+    def __init__(self, processes: Sequence = [], cfgfiles=None, preprocessors: Optional[Dict] = None):
         # ordered dict of processes
         self.processes = OrderedDict((p.identifier, p) for p in processes)
+        self.preprocessors = preprocessors or dict()
 
         if cfgfiles:
             config.load_configuration(cfgfiles)
@@ -85,7 +88,7 @@ class Service(object):
             process = self.processes[identifier]
         except KeyError:
             raise InvalidParameterValue("Unknown process '{}'".format(identifier), 'Identifier')
-        # make deep copy of the process instace
+        # make deep copy of the process instance
         # so that processes are not overriding each other
         # just for execute
         process = copy.deepcopy(process)
@@ -281,7 +284,7 @@ class Service(object):
                 LOGGER.debug('Setting PYWPS_CFG to {}'.format(environ_cfg))
                 os.environ['PYWPS_CFG'] = environ_cfg
 
-            wps_request = WPSRequest(http_request)
+            wps_request = WPSRequest(http_request, self.preprocessors)
             LOGGER.info('Request: {}'.format(wps_request.operation))
             if wps_request.operation in ['getcapabilities',
                                          'describeprocess',


=====================================
pywps/app/WPSRequest.py
=====================================
@@ -4,32 +4,37 @@
 ##################################################################
 
 import logging
+
 import lxml
 from pywps import xml_util as etree
 from werkzeug.exceptions import MethodNotAllowed
 from pywps import get_ElementMakerForVersion
 import base64
 import datetime
-from pywps.app.basic import get_xpath_ns
+from pywps.app.basic import get_xpath_ns, parse_http_url
 from pywps.inout.inputs import input_from_json
 from pywps.exceptions import NoApplicableCode, OperationNotSupported, MissingParameterValue, VersionNegotiationFailed, \
     InvalidParameterValue, FileSizeExceeded
 from pywps import configuration
+from pywps.configuration import wps_strict
 from pywps import get_version_from_ns
 
 import json
 from urllib.parse import unquote
 
 LOGGER = logging.getLogger("PYWPS")
+default_version = '1.0.0'
 
 
 class WPSRequest(object):
 
-    def __init__(self, http_request=None):
+    def __init__(self, http_request=None, preprocessors=None):
         self.http_request = http_request
 
         self.operation = None
         self.version = None
+        self.api = None
+        self.default_mimetype = None
         self.language = None
         self.identifier = None
         self.identifiers = None
@@ -37,13 +42,23 @@ class WPSRequest(object):
         self.status = None
         self.lineage = None
         self.inputs = {}
+        self.output_ids = None
         self.outputs = {}
         self.raw = None
         self.WPS = None
         self.OWS = None
         self.xpath_ns = None
-
-        if self.http_request:
+        self.preprocessors = preprocessors or dict()
+        self.preprocess_request = None
+        self.preprocess_response = None
+
+        if http_request:
+            d = parse_http_url(http_request)
+            self.operation = d.get('operation')
+            self.identifier = d.get('identifier')
+            self.output_ids = d.get('output_ids')
+            self.api = d.get('api')
+            self.default_mimetype = d.get('default_mimetype')
             request_parser = self._get_request_parser_method(http_request.method)
             request_parser()
 
@@ -61,7 +76,7 @@ class WPSRequest(object):
         """
 
         # service shall be WPS
-        service = _get_get_param(self.http_request, 'service')
+        service = _get_get_param(self.http_request, 'service', None if wps_strict else 'wps')
         if service:
             if str(service).lower() != 'wps':
                 raise InvalidParameterValue(
@@ -69,9 +84,12 @@ class WPSRequest(object):
         else:
             raise MissingParameterValue('service', 'service')
 
-        operation = _get_get_param(self.http_request, 'request')
+        self.operation = _get_get_param(self.http_request, 'request', self.operation)
+
+        language = _get_get_param(self.http_request, 'language')
+        self.check_and_set_language(language)
 
-        request_parser = self._get_request_parser(operation)
+        request_parser = self._get_request_parser(self.operation)
         request_parser(self.http_request)
 
     def _post_request(self):
@@ -84,16 +102,57 @@ class WPSRequest(object):
             raise FileSizeExceeded('File size for input exceeded.'
                                    ' Maximum request size allowed: {} megabytes'.format(maxsize / 1024 / 1024))
 
-        try:
-            doc = etree.fromstring(self.http_request.get_data())
-        except Exception as e:
-            raise NoApplicableCode(e.msg)
+        content_type = self.http_request.content_type or []  # or self.http_request.mimetype
+        json_input = 'json' in content_type
+        if not json_input:
+            try:
+                doc = etree.fromstring(self.http_request.get_data())
+            except Exception as e:
+                raise NoApplicableCode(e.msg)
+            operation = doc.tag
+            version = get_version_from_ns(doc.nsmap[doc.prefix])
+            self.set_version(version)
+
+            language = doc.attrib.get('language')
+            self.check_and_set_language(language)
+
+            request_parser = self._post_request_parser(operation)
+            request_parser(doc)
+        else:
+            try:
+                jdoc = json.loads(self.http_request.get_data())
+            except Exception as e:
+                raise NoApplicableCode(e.msg)
+            if self.identifier is not None:
+                jdoc = {'inputs': jdoc}
+            else:
+                self.identifier = jdoc.get('identifier', None)
 
-        operation = doc.tag
-        version = get_version_from_ns(doc.nsmap[doc.prefix])
-        self.set_version(version)
-        request_parser = self._post_request_parser(operation)
-        request_parser(doc)
+            self.operation = jdoc.get('operation', self.operation)
+
+            preprocessor_tuple = self.preprocessors.get(self.identifier, None)
+            if preprocessor_tuple:
+                self.identifier = preprocessor_tuple[0]
+                self.preprocess_request = preprocessor_tuple[1]
+                self.preprocess_response = preprocessor_tuple[2]
+
+            jdoc['operation'] = self.operation
+            jdoc['identifier'] = self.identifier
+            jdoc['api'] = self.api
+            jdoc['default_mimetype'] = self.default_mimetype
+
+            if self.preprocess_request is not None:
+                jdoc = self.preprocess_request(jdoc, http_request=self.http_request)
+            self.json = jdoc
+
+            version = jdoc.get('version')
+            self.set_version(version)
+
+            language = jdoc.get('language')
+            self.check_and_set_language(language)
+
+            request_parser = self._post_json_request_parser()
+            request_parser(jdoc)
 
     def _get_request_parser(self, operation):
         """Factory function returing propper parsing function
@@ -107,9 +166,7 @@ class WPSRequest(object):
 
             acceptedversions = _get_get_param(http_request, 'acceptversions')
             wpsrequest.check_accepted_versions(acceptedversions)
-
-            language = _get_get_param(http_request, 'language')
-            wpsrequest.check_and_set_language(language)
+            wpsrequest.default_mimetype = _get_get_param(http_request, 'f', wpsrequest.default_mimetype)
 
         def parse_get_describeprocess(http_request):
             """Parse GET DescribeProcess request
@@ -117,11 +174,11 @@ class WPSRequest(object):
             version = _get_get_param(http_request, 'version')
             wpsrequest.check_and_set_version(version)
 
-            language = _get_get_param(http_request, 'language')
-            wpsrequest.check_and_set_language(language)
-
             wpsrequest.identifiers = _get_get_param(
-                http_request, 'identifier', aslist=True)
+                http_request, 'identifier', wpsrequest.identifiers, aslist=True)
+            if wpsrequest.identifiers is None and self.identifier is not None:
+                wpsrequest.identifiers = [wpsrequest.identifier]
+            wpsrequest.default_mimetype = _get_get_param(http_request, 'f', wpsrequest.default_mimetype)
 
         def parse_get_execute(http_request):
             """Parse GET Execute request
@@ -129,10 +186,7 @@ class WPSRequest(object):
             version = _get_get_param(http_request, 'version')
             wpsrequest.check_and_set_version(version)
 
-            language = _get_get_param(http_request, 'language')
-            wpsrequest.check_and_set_language(language)
-
-            wpsrequest.identifier = _get_get_param(http_request, 'identifier')
+            wpsrequest.identifier = _get_get_param(http_request, 'identifier', wpsrequest.identifier)
             wpsrequest.store_execute = _get_get_param(
                 http_request, 'storeExecuteResponse', 'false')
             wpsrequest.status = _get_get_param(http_request, 'status', 'false')
@@ -143,28 +197,29 @@ class WPSRequest(object):
             if self.inputs is None:
                 self.inputs = {}
 
-            wpsrequest.outputs = {}
-
             # take responseDocument preferably
-            resp_outputs = get_data_from_kvp(
-                _get_get_param(http_request, 'ResponseDocument'))
-            raw_outputs = get_data_from_kvp(
-                _get_get_param(http_request, 'RawDataOutput'))
-            wpsrequest.raw = False
-            if resp_outputs:
-                wpsrequest.outputs = resp_outputs
-            elif raw_outputs:
-                wpsrequest.outputs = raw_outputs
-                wpsrequest.raw = True
+            raw, output_ids = False, _get_get_param(http_request, 'ResponseDocument')
+            if output_ids is None:
+                raw, output_ids = True, _get_get_param(http_request, 'RawDataOutput')
+            if output_ids is not None:
+                wpsrequest.raw, wpsrequest.output_ids = raw, output_ids
+            elif wpsrequest.raw is None:
+                wpsrequest.raw = wpsrequest.output_ids is not None
+
+            wpsrequest.default_mimetype = _get_get_param(http_request, 'f', wpsrequest.default_mimetype)
+            wpsrequest.outputs = get_data_from_kvp(wpsrequest.output_ids) or {}
+            if wpsrequest.raw:
                 # executeResponse XML will not be stored and no updating of
                 # status
                 wpsrequest.store_execute = 'false'
                 wpsrequest.status = 'false'
 
-        if not operation:
-            raise MissingParameterValue('Missing request value', 'request')
-        else:
+        if operation:
             self.operation = operation.lower()
+        else:
+            if wps_strict:
+                raise MissingParameterValue('Missing request value', 'request')
+            self.operation = 'execute'
 
         if self.operation == 'getcapabilities':
             return parse_get_getcapabilities
@@ -177,7 +232,8 @@ class WPSRequest(object):
                 'Unknown request {}'.format(self.operation), operation)
 
     def _post_request_parser(self, tagname):
-        """Factory function returing propper parsing function
+        """Factory function returning a proper parsing function
+        according to tagname and sets self.operation to the correct operation
         """
 
         wpsrequest = self
@@ -191,9 +247,6 @@ class WPSRequest(object):
                 [v.text for v in acceptedversions])
             wpsrequest.check_accepted_versions(acceptedversions)
 
-            language = doc.attrib.get('language')
-            wpsrequest.check_and_set_language(language)
-
         def parse_post_describeprocess(doc):
             """Parse POST DescribeProcess request
             """
@@ -201,9 +254,6 @@ class WPSRequest(object):
             version = doc.attrib.get('version')
             wpsrequest.check_and_set_version(version)
 
-            language = doc.attrib.get('language')
-            wpsrequest.check_and_set_language(language)
-
             wpsrequest.operation = 'describeprocess'
             wpsrequest.identifiers = [identifier_el.text for identifier_el in
                                       self.xpath_ns(doc, './ows:Identifier')]
@@ -214,9 +264,6 @@ class WPSRequest(object):
             version = doc.attrib.get('version')
             wpsrequest.check_and_set_version(version)
 
-            language = doc.attrib.get('language')
-            wpsrequest.check_and_set_language(language)
-
             wpsrequest.operation = 'execute'
 
             identifier = self.xpath_ns(doc, './ows:Identifier')
@@ -261,6 +308,69 @@ class WPSRequest(object):
             raise InvalidParameterValue(
                 'Unknown request {}'.format(tagname), 'request')
 
+    def _post_json_request_parser(self):
+        """
+        Factory function returning a proper parsing function
+        according to self.operation.
+        self.operation is modified to be lowercase
+            or the default 'execute' operation if self.operation is None
+        """
+
+        wpsrequest = self
+
+        def parse_json_post_getcapabilities(jdoc):
+            """Parse POST GetCapabilities request
+            """
+            acceptedversions = jdoc.get('acceptedversions')
+            wpsrequest.check_accepted_versions(acceptedversions)
+
+        def parse_json_post_describeprocess(jdoc):
+            """Parse POST DescribeProcess request
+            """
+
+            version = jdoc.get('version')
+            wpsrequest.check_and_set_version(version)
+            wpsrequest.identifiers = [identifier_el.text for identifier_el in
+                                      self.xpath_ns(jdoc, './ows:Identifier')]
+
+        def parse_json_post_execute(jdoc):
+            """Parse POST Execute request
+            """
+            version = jdoc.get('version')
+            wpsrequest.check_and_set_version(version)
+
+            wpsrequest.identifier = jdoc.get('identifier')
+            if wpsrequest.identifier is None:
+                raise MissingParameterValue(
+                    'Process identifier not set', 'Identifier')
+
+            wpsrequest.lineage = 'false'
+            wpsrequest.store_execute = 'false'
+            wpsrequest.status = 'false'
+            wpsrequest.inputs = get_inputs_from_json(jdoc)
+
+            if wpsrequest.output_ids is None:
+                wpsrequest.output_ids = jdoc.get('outputs', {})
+                wpsrequest.raw = jdoc.get('raw', False)
+            wpsrequest.raw, wpsrequest.outputs = get_output_from_dict(wpsrequest.output_ids, wpsrequest.raw)
+
+            if wpsrequest.raw:
+                # executeResponse XML will not be stored
+                wpsrequest.store_execute = 'false'
+
+            # todo: parse response_document like in the xml version?
+
+        self.operation = 'execute' if self.operation is None else self.operation.lower()
+        if self.operation == 'getcapabilities':
+            return parse_json_post_getcapabilities
+        elif self.operation == 'describeprocess':
+            return parse_json_post_describeprocess
+        elif self.operation == 'execute':
+            return parse_json_post_execute
+        else:
+            raise InvalidParameterValue(
+                'Unknown request {}'.format(self.operation), 'request')
+
     def set_version(self, version):
         self.version = version
         self.xpath_ns = get_xpath_ns(version)
@@ -287,13 +397,16 @@ class WPSRequest(object):
             raise VersionNegotiationFailed(
                 'The requested version "{}" is not supported by this server'.format(acceptedversions), 'version')
 
-    def check_and_set_version(self, version):
+    def check_and_set_version(self, version, allow_default=True):
         """set this.version
         """
 
         if not version:
-            raise MissingParameterValue('Missing version', 'version')
-        elif not _check_version(version):
+            if allow_default:
+                version = default_version
+            else:
+                raise MissingParameterValue('Missing version', 'version')
+        if not _check_version(version):
             raise VersionNegotiationFailed(
                 'The requested version "{}" is not supported by this server'.format(version), 'version')
         else:
@@ -332,6 +445,8 @@ class WPSRequest(object):
         obj = {
             'operation': self.operation,
             'version': self.version,
+            'api': self.api,
+            'default_mimetype': self.default_mimetype,
             'language': self.language,
             'identifier': self.identifier,
             'identifiers': self.identifiers,
@@ -352,28 +467,32 @@ class WPSRequest(object):
         :param value: the json (not string) representation
         """
 
-        self.operation = value['operation']
-        self.version = value['version']
-        self.language = value['language']
-        self.identifier = value['identifier']
-        self.identifiers = value['identifiers']
-        self.store_execute = value['store_execute']
-        self.status = value['status']
-        self.lineage = value['lineage']
-        self.outputs = value['outputs']
-        self.raw = value['raw']
+        self.operation = value.get('operation')
+        self.version = value.get('version')
+        self.api = value.get('api')
+        self.default_mimetype = value.get('default_mimetype')
+        self.language = value.get('language')
+        self.identifier = value.get('identifier')
+        self.identifiers = value.get('identifiers')
+        self.store_execute = value.get('store_execute')
+        self.status = value.get('status', False)
+        self.lineage = value.get('lineage', False)
+        self.outputs = value.get('outputs')
+        self.raw = value.get('raw', False)
         self.inputs = {}
 
-        for identifier in value['inputs']:
+        for identifier in value.get('inputs', []):
             inpt_defs = value['inputs'][identifier]
-
+            if not isinstance(inpt_defs, (list, tuple)):
+                inpt_defs = [inpt_defs]
+            self.inputs[identifier] = []
             for inpt_def in inpt_defs:
+                if not isinstance(inpt_def, dict):
+                    inpt_def = {"data": inpt_def}
+                if 'identifier' not in inpt_def:
+                    inpt_def['identifier'] = identifier
                 inpt = input_from_json(inpt_def)
-
-                if identifier in self.inputs:
-                    self.inputs[identifier].append(inpt)
-                else:
-                    self.inputs[identifier] = [inpt]
+                self.inputs[identifier].append(inpt)
 
 
 def get_inputs_from_xml(doc):
@@ -400,13 +519,11 @@ def get_inputs_from_xml(doc):
 
         complex_data = xpath_ns(input_el, './wps:Data/wps:ComplexData')
         if complex_data:
-
             complex_data_el = complex_data[0]
             inpt = {}
             inpt['identifier'] = identifier_el.text
-            inpt['mimeType'] = complex_data_el.attrib.get('mimeType', '')
-            inpt['encoding'] = complex_data_el.attrib.get(
-                'encoding', '').lower()
+            inpt['mimeType'] = complex_data_el.attrib.get('mimeType', None)
+            inpt['encoding'] = complex_data_el.attrib.get('encoding', '').lower()
             inpt['schema'] = complex_data_el.attrib.get('schema', '')
             inpt['method'] = complex_data_el.attrib.get('method', 'GET')
             if len(complex_data_el.getchildren()) > 0:
@@ -426,7 +543,7 @@ def get_inputs_from_xml(doc):
             inpt[identifier_el.text] = reference_data_el.text
             inpt['href'] = reference_data_el.attrib.get(
                 '{http://www.w3.org/1999/xlink}href', '')
-            inpt['mimeType'] = reference_data_el.attrib.get('mimeType', '')
+            inpt['mimeType'] = reference_data_el.attrib.get('mimeType', None)
             inpt['method'] = reference_data_el.attrib.get('method', 'GET')
             header_element = xpath_ns(reference_data_el, './wps:Header')
             if header_element:
@@ -470,7 +587,7 @@ def get_output_from_xml(doc):
             [identifier_el] = xpath_ns(output_el, './ows:Identifier')
             outpt = {}
             outpt[identifier_el.text] = ''
-            outpt['mimetype'] = output_el.attrib.get('mimeType', '')
+            outpt['mimetype'] = output_el.attrib.get('mimeType', None)
             outpt['encoding'] = output_el.attrib.get('encoding', '')
             outpt['schema'] = output_el.attrib.get('schema', '')
             outpt['uom'] = output_el.attrib.get('uom', '')
@@ -482,7 +599,7 @@ def get_output_from_xml(doc):
             [identifier_el] = xpath_ns(output_el, './ows:Identifier')
             outpt = {}
             outpt[identifier_el.text] = ''
-            outpt['mimetype'] = output_el.attrib.get('mimeType', '')
+            outpt['mimetype'] = output_el.attrib.get('mimeType', None)
             outpt['encoding'] = output_el.attrib.get('encoding', '')
             outpt['schema'] = output_el.attrib.get('schema', '')
             outpt['uom'] = output_el.attrib.get('uom', '')
@@ -491,6 +608,72 @@ def get_output_from_xml(doc):
     return the_output
 
 
+def get_inputs_from_json(jdoc):
+    the_inputs = {}
+    inputs_dict = jdoc.get('inputs', {})
+    for identifier, inpt_defs in inputs_dict.items():
+        if not isinstance(inpt_defs, (list, tuple)):
+            inpt_defs = [inpt_defs]
+        the_inputs[identifier] = []
+        for inpt_def in inpt_defs:
+            if not isinstance(inpt_def, dict):
+                inpt_def = {"data": inpt_def}
+            data_type = inpt_def.get('type', 'literal')
+            inpt = {'identifier': identifier}
+            if data_type == 'literal':
+                inpt['data'] = inpt_def.get('data')
+                inpt['uom'] = inpt_def.get('uom', '')
+                inpt['datatype'] = inpt_def.get('datatype', '')
+                the_inputs[identifier].append(inpt)
+            elif data_type == 'complex':
+                inpt['mimeType'] = inpt_def.get('mimeType', None)
+                inpt['encoding'] = inpt_def.get('encoding', '').lower()
+                inpt['schema'] = inpt_def.get('schema', '')
+                inpt['method'] = inpt_def.get('method', 'GET')
+                inpt['data'] = _get_rawvalue_value(inpt_def.get('data', ''), inpt['encoding'])
+                the_inputs[identifier].append(inpt)
+            elif data_type == 'reference':
+                inpt[identifier] = inpt_def
+                inpt['href'] = inpt_def.get('href', '')
+                inpt['mimeType'] = inpt_def.get('mimeType', None)
+                inpt['method'] = inpt_def.get('method', 'GET')
+                inpt['header'] = inpt_def.get('header', '')
+                inpt['body'] = inpt_def.get('body', '')
+                inpt['bodyreference'] = inpt_def.get('bodyreference', '')
+                the_inputs[identifier].append(inpt)
+            elif data_type == 'bbox':
+                inpt['data'] = inpt_def['bbox']
+                inpt['crs'] = inpt_def.get('crs', 'urn:ogc:def:crs:EPSG::4326')
+                inpt['dimensions'] = inpt_def.get('dimensions', 2)
+                the_inputs[identifier].append(inpt)
+    return the_inputs
+
+
+def get_output_from_dict(output_ids, raw):
+    the_output = {}
+    if isinstance(output_ids, dict):
+        pass
+    elif isinstance(output_ids, (tuple, list)):
+        output_ids = {x: {} for x in output_ids}
+    else:
+        output_ids = {output_ids: {}}
+        raw = True  # single non-dict output means raw output
+    for identifier, output_el in output_ids.items():
+        if isinstance(output_el, list):
+            output_el = output_el[0]
+        outpt = {}
+        outpt[identifier] = ''
+        outpt['mimetype'] = output_el.get('mimeType', None)
+        outpt['encoding'] = output_el.get('encoding', '')
+        outpt['schema'] = output_el.get('schema', '')
+        outpt['uom'] = output_el.get('uom', '')
+        if not raw:
+            outpt['asReference'] = output_el.get('asReference', 'false')
+        the_output[identifier] = outpt
+
+    return raw, the_output
+
+
 def get_data_from_kvp(data, part=None):
     """Get execute DataInputs and ResponseDocument from URL (key-value-pairs) encoding
     :param data: key:value pair list of the datainputs and responseDocument parameter


=====================================
pywps/app/basic.py
=====================================
@@ -7,8 +7,10 @@ XML tools
 """
 
 import logging
+from typing import Optional, Tuple
+
 from werkzeug.wrappers import Response
-from pywps import __version__
+import pywps.configuration as config
 
 LOGGER = logging.getLogger('PYWPS')
 
@@ -33,8 +35,113 @@ def get_xpath_ns(version):
     return xpath_ns
 
 
-def xml_response(doc):
-    """XML response serializer"""
-    response = Response(doc, content_type='text/xml')
+def make_response(doc, content_type):
+    """response serializer"""
+    if not content_type:
+        content_type = get_default_response_mimetype()
+    response = Response(doc, content_type=content_type)
     response.status_percentage = 100
     return response
+
+
+def get_default_response_mimetype():
+    default_mimetype = config.get_config_value('server', 'default_mimetype')
+    return default_mimetype
+
+
+def get_json_indent():
+    json_ident = int(config.get_config_value('server', 'json_indent'))
+    return json_ident if json_ident >= 0 else None
+
+
+def get_response_type(accept_mimetypes, default_mimetype) -> Tuple[bool, str]:
+    """
+    This function determinate if the response should be JSON or XML based on
+    the accepted mimetypes of the request and the default mimetype provided,
+    which will be used in case both are equally accepted.
+
+    :param accept_mimetypes: determinate which mimetypes are accepted
+    :param default_mimetype: "text/xml", "application/json"
+    :return: Tuple[bool, str] -
+        bool - True: The response type is JSON, False: Otherwise - XML
+        str - The output mimetype
+    """
+    accept_json = \
+        accept_mimetypes.accept_json or \
+        accept_mimetypes.best is None or \
+        'json' in accept_mimetypes.best.lower()
+    accept_xhtml = \
+        accept_mimetypes.accept_xhtml or \
+        accept_mimetypes.best is None or \
+        'xml' in accept_mimetypes.best.lower()
+    if not default_mimetype:
+        default_mimetype = get_default_response_mimetype()
+    json_is_default = 'json' in default_mimetype or '*' in default_mimetype
+    json_response = (accept_json and (not accept_xhtml or json_is_default)) or \
+                    (json_is_default and accept_json == accept_xhtml)
+    mimetype = 'application/json' if json_response else 'text/xml' if accept_xhtml else ''
+    return json_response, mimetype
+
+
+def parse_http_url(http_request) -> dict:
+    """
+    This function parses the request URL and extracts the following:
+        default operation, process identifier, output_ids, default mimetype
+        info that cannot be terminated from the URL will be None (default)
+
+        The url is expected to be in the following format, all the levels are optional.
+        [base_url]/[identifier]/[output_ids]
+
+    :param http_request: the request URL
+    :return: dict with the extracted info listed:
+        base_url - [wps|processes|jobs|api/api_level]
+        default_mimetype - determinate by the base_url part:
+            XML - if the base url == 'wps',
+            JSON - if the base URL in ['api'|'jobs'|'processes']
+        operation - also determinate by the base_url part:
+            ['api'|'jobs'] -> 'execute'
+            processes -> 'describeprocess' or 'getcapabilities'
+                'describeprocess' if identifier is present as the next item, 'getcapabilities' otherwise
+        api - api level, only expected if base_url=='api'
+        identifier - the process identifier
+        output_ids - if exist then it selects raw output with the name output_ids
+    """
+    operation = api = identifier = output_ids = default_mimetype = base_url = None
+    if http_request:
+        parts = str(http_request.path[1:]).split('/')
+        i = 0
+        if len(parts) > i:
+            base_url = parts[i].lower()
+            if base_url == 'wps':
+                default_mimetype = 'xml'
+            elif base_url in ['api', 'processes', 'jobs']:
+                default_mimetype = 'json'
+            i += 1
+            if base_url == 'api':
+                api = parts[i]
+                i += 1
+            if len(parts) > i:
+                identifier = parts[i]
+                i += 1
+                if len(parts) > i:
+                    output_ids = parts[i]
+                    if not output_ids:
+                        output_ids = None
+    if base_url in ['jobs', 'api']:
+        operation = 'execute'
+    elif base_url == 'processes':
+        operation = 'describeprocess' if identifier else 'getcapabilities'
+    d = {}
+    if operation:
+        d['operation'] = operation
+    if identifier:
+        d['identifier'] = identifier
+    if output_ids:
+        d['output_ids'] = output_ids
+    if default_mimetype:
+        d['default_mimetype'] = default_mimetype
+    if api:
+        d['api'] = api
+    if base_url:
+        d['base_url'] = base_url
+    return d


=====================================
pywps/configuration.py
=====================================
@@ -24,8 +24,10 @@ RAW_OPTIONS = [('logging', 'format'), ]
 CONFIG = None
 LOGGER = logging.getLogger("PYWPS")
 
+wps_strict = True
 
-def get_config_value(section, option):
+
+def get_config_value(section, option, default_value=''):
     """Get desired value from  configuration files
 
     :param section: section in configuration files
@@ -38,7 +40,7 @@ def get_config_value(section, option):
     if not CONFIG:
         load_configuration()
 
-    value = ''
+    value = default_value
 
     if CONFIG.has_section(section):
         if CONFIG.has_option(section, option):
@@ -96,6 +98,13 @@ def load_configuration(cfgfiles=None):
     # Allowed functions: "copy", "move", "link" (default "copy")
     CONFIG.set('server', 'storage_copy_function', 'copy')
 
+    # handles the default mimetype for requests.
+    # available options: "text/xml", "application/json"
+    CONFIG.set("server", "default_mimetype", "text/xml")
+
+    # default json indentation for responses.
+    CONFIG.set("server", "json_indent", "2")
+
     CONFIG.add_section('processing')
     CONFIG.set('processing', 'mode', 'default')
     CONFIG.set('processing', 'path', os.path.dirname(os.path.realpath(sys.argv[0])))


=====================================
pywps/exceptions.py
=====================================
@@ -11,7 +11,10 @@ Based on OGC OWS, WPS and
 http://lists.opengeospatial.org/pipermail/wps-dev/2013-October/000335.html
 """
 
+import json
 
+from werkzeug.datastructures import MIMEAccept
+from werkzeug.http import parse_accept_header
 from werkzeug.wrappers import Response
 from werkzeug.exceptions import HTTPException
 from markupsafe import escape
@@ -19,6 +22,7 @@ from markupsafe import escape
 import logging
 
 from pywps import __version__
+from pywps.app.basic import get_json_indent, get_response_type, parse_http_url
 
 __author__ = "Alex Morega & Calin Ciociu"
 
@@ -53,7 +57,7 @@ class NoApplicableCode(HTTPException):
     def get_description(self, environ=None):
         """Get the description."""
         if self.description:
-            return '''<ows:ExceptionText>{}</ows:ExceptionText>'''.format(escape(self.description))
+            return escape(self.description)
         else:
             return ''
 
@@ -65,17 +69,26 @@ class NoApplicableCode(HTTPException):
             'name': escape(self.name),
             'description': self.get_description(environ)
         }
-        doc = str((
-            '<?xml version="1.0" encoding="UTF-8"?>\n'
-            '<!-- PyWPS {version} -->\n'
-            '<ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd" version="1.0.0">\n'  # noqa
-            '  <ows:Exception exceptionCode="{name}" locator="{locator}" >\n'
-            '      {description}\n'
-            '  </ows:Exception>\n'
-            '</ows:ExceptionReport>'
-        ).format(**args))
-
-        return Response(doc, self.code, mimetype='text/xml')
+        accept_mimetypes = parse_accept_header(environ.get("HTTP_ACCEPT"), MIMEAccept)
+        request = environ.get('werkzeug.request', None)
+        default_mimetype = None if not request else request.args.get('f', None)
+        if default_mimetype is None:
+            default_mimetype = parse_http_url(request).get('default_mimetype')
+        json_response, mimetype = get_response_type(accept_mimetypes, default_mimetype)
+        if json_response:
+            doc = json.dumps(args, indent=get_json_indent())
+        else:
+            doc = str((
+                '<?xml version="1.0" encoding="UTF-8"?>\n'
+                '<!-- PyWPS {version} -->\n'
+                '<ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd" version="1.0.0">\n'  # noqa
+                '  <ows:Exception exceptionCode="{name}" locator="{locator}" >\n'
+                '      <ows:ExceptionText>{description}</ows:ExceptionText>\n'
+                '  </ows:Exception>\n'
+                '</ows:ExceptionReport>'
+            ).format(**args))
+
+        return Response(doc, self.code, mimetype=mimetype)
 
 
 class InvalidParameterValue(NoApplicableCode):


=====================================
pywps/inout/array_encode.py
=====================================
@@ -0,0 +1,14 @@
+##################################################################
+# Copyright 2018 Open Source Geospatial Foundation and others    #
+# licensed under MIT, Please consult LICENSE.txt for details     #
+##################################################################
+
+from json import JSONEncoder
+
+
+class ArrayEncoder(JSONEncoder):
+    def default(self, obj):
+        if hasattr(obj, 'tolist'):
+            # this will work for array.array and numpy.ndarray
+            return obj.tolist()
+        return JSONEncoder.default(self, obj)


=====================================
pywps/inout/basic.py
=====================================
@@ -2,8 +2,11 @@
 # Copyright 2018 Open Source Geospatial Foundation and others    #
 # licensed under MIT, Please consult LICENSE.txt for details     #
 ##################################################################
+import json
 from pathlib import PurePath
 
+from pywps.inout.formats import Supported_Formats
+from pywps.inout.types import Translations
 from pywps.translations import lower_case_dict
 from io import StringIO
 import os
@@ -203,11 +206,12 @@ class IOHandler(object):
         """
 
         validate = self.validator
-        _valid = validate(self, self.valid_mode)
-        if not _valid:
-            self.data_set = False
-            raise InvalidParameterValue('Input data not valid using '
-                                        'mode {}'.format(self.valid_mode))
+        if validate is not None:
+            _valid = validate(self, self.valid_mode)
+            if not _valid:
+                self.data_set = False
+                raise InvalidParameterValue('Input data not valid using '
+                                            'mode {}'.format(self.valid_mode))
         self.data_set = True
 
     @property
@@ -316,6 +320,14 @@ class IOHandler(object):
         WARNING: may be bytes or str"""
         return self._iohandler.data
 
+    def data_as_json(self):
+        # applies json.loads if needed
+        data = self._iohandler.data
+        if data and not isinstance(self._iohandler, DataHandler) and self.extension in ['.geojson', 'json']:
+            data = json.loads(data)
+            self.data = data  # switch to a DataHandler
+        return data
+
     @data.setter
     def data(self, value):
         self._iohandler = DataHandler(value, self)
@@ -460,7 +472,10 @@ class DataHandler(FileHandler):
             openmode = self._openmode(self.data)
             kwargs = {} if 'b' in openmode else {'encoding': 'utf8'}
             with open(self._file, openmode, **kwargs) as fh:
-                fh.write(self.data)
+                if isinstance(self.data, (bytes, str)):
+                    fh.write(self.data)
+                else:
+                    json.dump(self.data, fh)
 
         return self._file
 
@@ -643,8 +658,8 @@ class BasicIO:
         self.title = title
         self.abstract = abstract
         self.keywords = keywords
-        self.min_occurs = int(min_occurs)
-        self.max_occurs = int(max_occurs)
+        self.min_occurs = int(min_occurs) if min_occurs is not None else 0
+        self.max_occurs = int(max_occurs) if max_occurs is not None else None
         self.metadata = metadata
         self.translations = lower_case_dict(translations)
 
@@ -715,7 +730,7 @@ class BasicComplex(object):
     def validator(self):
         """Return the proper validator for given data_format
         """
-        return self.data_format.validate
+        return None if self.data_format is None else self.data_format.validate
 
     @property
     def supported_formats(self):
@@ -1071,8 +1086,8 @@ class ComplexOutput(BasicIO, BasicComplex, IOHandler):
     """
 
     def __init__(self, identifier, title=None, abstract=None, keywords=None,
-                 workdir=None, data_format=None, supported_formats=None,
-                 mode=MODE.NONE, translations=None):
+                 workdir=None, data_format=None, supported_formats: Supported_Formats = None,
+                 mode=MODE.NONE, translations: Translations = None):
         BasicIO.__init__(self, identifier, title, abstract, keywords, translations=translations)
         IOHandler.__init__(self, workdir=workdir, mode=mode)
         BasicComplex.__init__(self, data_format, supported_formats)


=====================================
pywps/inout/formats/__init__.py
=====================================
@@ -13,7 +13,7 @@
 
 from collections import namedtuple
 import mimetypes
-
+from typing import Optional, Sequence, Union
 
 _FORMATS = namedtuple('FORMATS', 'GEOJSON, JSON, SHP, GML, GPX, METALINK, META4, KML, KMZ, GEOTIFF,'
                                  'WCS, WCS100, WCS110, WCS20, WFS, WFS100,'
@@ -162,6 +162,9 @@ class Format(object):
         self.extension = jsonin['extension']
 
 
+Supported_Formats = Optional[Sequence[Union[str, Format]]]
+
+
 FORMATS = _FORMATS(
     Format('application/geo+json', extension='.geojson'),
     Format('application/json', extension='.json'),


=====================================
pywps/inout/inputs.py
=====================================
@@ -83,16 +83,16 @@ class BoundingBoxInput(basic.BBoxInput):
     def from_json(cls, json_input):
         instance = cls(
             identifier=json_input['identifier'],
-            title=json_input['title'],
-            abstract=json_input['abstract'],
-            crss=json_input['crss'],
-            keywords=json_input['keywords'],
+            title=json_input.get('title'),
+            abstract=json_input.get('abstract'),
+            crss=json_input.get('crss'),
+            keywords=json_input.get('keywords'),
             metadata=[Metadata.from_json(data) for data in json_input.get('metadata', [])],
-            dimensions=json_input['dimensions'],
-            workdir=json_input['workdir'],
-            mode=json_input['mode'],
-            min_occurs=json_input['min_occurs'],
-            max_occurs=json_input['max_occurs'],
+            dimensions=json_input.get('dimensions'),
+            workdir=json_input.get('workdir'),
+            mode=json_input.get('mode'),
+            min_occurs=json_input.get('min_occurs'),
+            max_occurs=json_input.get('max_occurs'),
             translations=json_input.get('translations'),
         )
         instance.data = json_input['bbox']
@@ -186,6 +186,14 @@ class ComplexInput(basic.ComplexInput):
 
     @classmethod
     def from_json(cls, json_input):
+        data_format = json_input.get('data_format')
+        if data_format is not None:
+            data_format = Format(
+                schema=data_format.get('schema'),
+                extension=data_format.get('extension'),
+                mime_type=data_format.get('mime_type', ""),
+                encoding=data_format.get('encoding')
+            )
         instance = cls(
             identifier=json_input['identifier'],
             title=json_input.get('title'),
@@ -193,19 +201,14 @@ class ComplexInput(basic.ComplexInput):
             keywords=json_input.get('keywords', []),
             workdir=json_input.get('workdir'),
             metadata=[Metadata.from_json(data) for data in json_input.get('metadata', [])],
-            data_format=Format(
-                schema=json_input['data_format'].get('schema'),
-                extension=json_input['data_format'].get('extension'),
-                mime_type=json_input['data_format']['mime_type'],
-                encoding=json_input['data_format'].get('encoding')
-            ),
+            data_format=data_format,
             supported_formats=[
                 Format(
                     schema=infrmt.get('schema'),
                     extension=infrmt.get('extension'),
-                    mime_type=infrmt['mime_type'],
+                    mime_type=infrmt.get('mime_type'),
                     encoding=infrmt.get('encoding')
-                ) for infrmt in json_input['supported_formats']
+                ) for infrmt in json_input.get('supported_formats', [])
             ],
             mode=json_input.get('mode', MODE.NONE),
             translations=json_input.get('translations'),
@@ -218,9 +221,10 @@ class ComplexInput(basic.ComplexInput):
         elif json_input.get('data'):
             data = json_input['data']
             # remove cdata tag if it exists (issue #553)
-            match = CDATA_PATTERN.match(data)
-            if match:
-                data = match.group(1)
+            if isinstance(data, str):
+                match = CDATA_PATTERN.match(data)
+                if match:
+                    data = match.group(1)
             instance.data = data
 
         return instance
@@ -331,7 +335,7 @@ class LiteralInput(basic.LiteralInput):
     @classmethod
     def from_json(cls, json_input):
         allowed_values = []
-        for allowed_value in json_input['allowed_values']:
+        for allowed_value in json_input.get('allowed_values', []):
             if allowed_value['type'] == 'anyvalue':
                 allowed_values.append(AnyValue())
             elif allowed_value['type'] == 'novalue':
@@ -351,7 +355,7 @@ class LiteralInput(basic.LiteralInput):
         data = json_input_copy.pop('data', None)
         uom = json_input_copy.pop('uom', None)
         metadata = json_input_copy.pop('metadata', [])
-        json_input_copy.pop('type')
+        json_input_copy.pop('type', None)
         json_input_copy.pop('any_value', None)
         json_input_copy.pop('values_reference', None)
 
@@ -371,8 +375,8 @@ class LiteralInput(basic.LiteralInput):
 
 
 def input_from_json(json_data):
-    data_type = json_data['type']
-    if data_type == 'complex':
+    data_type = json_data.get('type', 'literal')
+    if data_type in ['complex', 'reference']:
         inpt = ComplexInput.from_json(json_data)
     elif data_type == 'literal':
         inpt = LiteralInput.from_json(json_data)


=====================================
pywps/inout/literaltypes.py
=====================================
@@ -5,7 +5,6 @@
 
 """Literaltypes are used for LiteralInputs, to make sure, input data are OK
 """
-
 from urllib.parse import urlparse
 from dateutil.parser import parse as date_parser
 import datetime
@@ -19,7 +18,7 @@ LOGGER = logging.getLogger('PYWPS')
 LITERAL_DATA_TYPES = ('float', 'boolean', 'integer', 'string',
                       'positiveInteger', 'anyURI', 'time', 'date', 'dateTime',
                       'scale', 'angle',
-                      'nonNegativeInteger')
+                      'nonNegativeInteger', None)
 
 # currently we are supporting just ^^^ data types, feel free to add support for
 # more


=====================================
pywps/inout/outputs.py
=====================================
@@ -5,6 +5,7 @@
 """
 WPS Output classes
 """
+from typing import Optional, Sequence, Dict, Union
 
 from pywps import xml_util as etree
 import os
@@ -13,9 +14,10 @@ from pywps.app.Common import Metadata
 from pywps.exceptions import InvalidParameterValue
 from pywps.inout import basic
 from pywps.inout.storage.file import FileStorageBuilder
+from pywps.inout.types import Translations
 from pywps.validator.mode import MODE
 from pywps import configuration as config
-from pywps.inout.formats import Format
+from pywps.inout.formats import Format, Supported_Formats, FORMATS
 
 
 class BoundingBoxOutput(basic.BBoxOutput):
@@ -109,9 +111,10 @@ class ComplexOutput(basic.ComplexOutput):
         e.g. {"fr-CA": {"title": "Mon titre", "abstract": "Une description"}}
     """
 
-    def __init__(self, identifier, title, supported_formats=None,
-                 data_format=None, abstract='', keywords=[], workdir=None, metadata=None,
-                 as_reference=False, mode=MODE.NONE, translations=None):
+    def __init__(self, identifier: str, title: str, supported_formats: Supported_Formats = None,
+                 data_format=None, abstract: str = '', keywords=[], workdir=None,
+                 metadata: Optional[Sequence[Metadata]] = None,
+                 as_reference=False, mode: MODE = MODE.NONE, translations: Translations = None):
         if metadata is None:
             metadata = []
 
@@ -210,14 +213,12 @@ class ComplexOutput(basic.ComplexOutput):
     def _json_data(self, data):
         """Return Data node
         """
-        # Match only data that are safe CDATA pattern.
-        CDATA_PATTERN = re.compile(r'^<!\[CDATA\[((?!\]\]>).)*\]\]>$')
-
         data["type"] = "complex"
 
         if self.data:
-
-            if self.data_format.mime_type in ["application/xml", "application/gml+xml", "text/xml"]:
+            if self.data_format.mime_type in [FORMATS.GEOJSON.mime_type, FORMATS.JSON.mime_type]:
+                data["data"] = self.data
+            elif self.data_format.mime_type in ["application/xml", "application/gml+xml", "text/xml"]:
                 # Note that in a client-server round trip, the original and returned file will not be identical.
                 data_doc = etree.parse(self.file)
                 data["data"] = etree.tostring(data_doc, pretty_print=True).decode('utf-8')
@@ -226,6 +227,9 @@ class ComplexOutput(basic.ComplexOutput):
                 if self.data_format.encoding == 'base64':
                     data["data"] = self.base64.decode('utf-8')
                 else:
+                    # Match only data that are safe CDATA pattern.
+                    CDATA_PATTERN = re.compile(r'^<!\[CDATA\[((?!\]\]>).)*\]\]>$')
+
                     # Otherwise we assume all other formats are unsafe and need to be enclosed in a CDATA tag.
                     if isinstance(self.data, bytes):
                         # Try to inline data as text but if fail encode is in base64
@@ -539,13 +543,14 @@ class MetaLink4(MetaLink):
 
 
 def output_from_json(json_data):
-    if json_data['type'] == 'complex':
+    data_type = json_data.get('type', 'literal')
+    if data_type == 'complex':
         output = ComplexOutput.from_json(json_data)
-    elif json_data['type'] == 'literal':
+    elif data_type == 'literal':
         output = LiteralOutput.from_json(json_data)
-    elif json_data['type'] == 'bbox':
+    elif data_type == 'bbox':
         output = BoundingBoxOutput.from_json(json_data)
     else:
-        raise InvalidParameterValue("Output type not recognized: {}".format(json_data['type']))
+        raise InvalidParameterValue("Output type not recognized: {}".format(data_type))
 
     return output


=====================================
pywps/inout/types.py
=====================================
@@ -0,0 +1,8 @@
+##################################################################
+# Copyright 2018 Open Source Geospatial Foundation and others    #
+# licensed under MIT, Please consult LICENSE.txt for details     #
+##################################################################
+
+from typing import Optional, Dict
+
+Translations = Optional[Dict[str, Dict[str, str]]]


=====================================
pywps/response/__init__.py
=====================================
@@ -1,3 +1,8 @@
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+    from pywps import WPSRequest
+
 from pywps.dblog import store_status
 from pywps.response.status import WPS_STATUS
 from pywps.translations import get_translation
@@ -27,7 +32,7 @@ def get_response(operation):
 
 class WPSResponse(object):
 
-    def __init__(self, wps_request, uuid=None, version="1.0.0"):
+    def __init__(self, wps_request: 'WPSRequest', uuid=None, version="1.0.0"):
 
         self.wps_request = wps_request
         self.uuid = uuid
@@ -35,6 +40,7 @@ class WPSResponse(object):
         self.status = WPS_STATUS.ACCEPTED
         self.status_percentage = 0
         self.doc = None
+        self.content_type = None
         self.version = version
         self.template_env = RelEnvironment(
             loader=PackageLoader('pywps', 'templates'),
@@ -57,9 +63,13 @@ class WPSResponse(object):
         self.status_percentage = status_percentage
         store_status(self.uuid, self.status, self.message, self.status_percentage)
 
+    @abstractmethod
+    def _construct_doc(self):
+        ...
+
     def get_response_doc(self):
         try:
-            self.doc = self._construct_doc()
+            self.doc, self.content_type = self._construct_doc()
         except Exception as e:
             if hasattr(e, "description"):
                 msg = e.description
@@ -71,4 +81,4 @@ class WPSResponse(object):
         else:
             self._update_status(WPS_STATUS.SUCCEEDED, "Response generated", 100)
 
-            return self.doc
+            return self.doc, self.content_type


=====================================
pywps/response/capabilities.py
=====================================
@@ -1,6 +1,8 @@
+import json
+
 from werkzeug.wrappers import Request
 import pywps.configuration as config
-from pywps.app.basic import xml_response
+from pywps.app.basic import make_response, get_response_type, get_json_indent
 from pywps.response import WPSResponse
 from pywps import __version__
 from pywps.exceptions import NoApplicableCode
@@ -60,20 +62,27 @@ class CapabilitiesResponse(WPSResponse):
             'processes': processes
         }
 
-    def _construct_doc(self):
-
-        template = self.template_env.get_template(self.version + '/capabilities/main.xml')
+    @staticmethod
+    def _render_json_response(jdoc):
+        return jdoc
 
-        doc = template.render(**self.json)
-
-        return doc
+    def _construct_doc(self):
+        doc = self.json
+        json_response, mimetype = get_response_type(
+            self.wps_request.http_request.accept_mimetypes, self.wps_request.default_mimetype)
+        if json_response:
+            doc = json.dumps(self._render_json_response(doc), indent=get_json_indent())
+        else:
+            template = self.template_env.get_template(self.version + '/capabilities/main.xml')
+            doc = template.render(**doc)
+        return doc, mimetype
 
     @Request.application
     def __call__(self, request):
         # This function must return a valid response.
         try:
-            doc = self.get_response_doc()
-            return xml_response(doc)
+            doc, content_type = self.get_response_doc()
+            return make_response(doc, content_type=content_type)
         except NoApplicableCode as e:
             return e
         except Exception as e:


=====================================
pywps/response/describe.py
=====================================
@@ -1,6 +1,8 @@
+import json
+
 from werkzeug.wrappers import Request
 import pywps.configuration as config
-from pywps.app.basic import xml_response
+from pywps.app.basic import make_response, get_response_type, get_json_indent
 from pywps.exceptions import NoApplicableCode
 from pywps.exceptions import MissingParameterValue
 from pywps.exceptions import InvalidParameterValue
@@ -41,23 +43,31 @@ class DescribeResponse(WPSResponse):
             'language': self.wps_request.language,
         }
 
-    def _construct_doc(self):
+    @staticmethod
+    def _render_json_response(jdoc):
+        return jdoc
 
+    def _construct_doc(self):
         if not self.identifiers:
             raise MissingParameterValue('Missing parameter value "identifier"', 'identifier')
 
-        template = self.template_env.get_template(self.version + '/describe/main.xml')
-        max_size = int(config.get_size_mb(config.get_config_value('server', 'maxsingleinputsize')))
-        doc = template.render(max_size=max_size, **self.json)
-
-        return doc
+        doc = self.json
+        json_response, mimetype = get_response_type(
+            self.wps_request.http_request.accept_mimetypes, self.wps_request.default_mimetype)
+        if json_response:
+            doc = json.dumps(self._render_json_response(doc), indent=get_json_indent())
+        else:
+            template = self.template_env.get_template(self.version + '/describe/main.xml')
+            max_size = int(config.get_size_mb(config.get_config_value('server', 'maxsingleinputsize')))
+            doc = template.render(max_size=max_size, **doc)
+        return doc, mimetype
 
     @Request.application
     def __call__(self, request):
         # This function must return a valid response.
         try:
-            doc = self.get_response_doc()
-            return xml_response(doc)
+            doc, content_type = self.get_response_doc()
+            return make_response(doc, content_type=content_type)
         except NoApplicableCode as e:
             return e
         except Exception as e:


=====================================
pywps/response/execute.py
=====================================
@@ -3,14 +3,17 @@
 # licensed under MIT, Please consult LICENSE.txt for details     #
 ##################################################################
 
+import json
 import logging
 import time
 from werkzeug.wrappers import Request
 from pywps import get_ElementMakerForVersion
+from pywps.app.basic import get_response_type, get_json_indent, get_default_response_mimetype
 from pywps.exceptions import NoApplicableCode
 import pywps.configuration as config
 from werkzeug.wrappers import Response
 
+from pywps.inout.array_encode import ArrayEncoder
 from pywps.response.status import WPS_STATUS
 from pywps.response import WPSResponse
 from pywps.inout.formats import FORMATS
@@ -78,7 +81,7 @@ class ExecuteResponse(WPSResponse):
     def _update_status_doc(self):
         try:
             # rebuild the doc
-            self.doc = self._construct_doc()
+            self.doc, self.content_type = self._construct_doc()
         except Exception as e:
             raise NoApplicableCode('Building Response Document failed with : {}'.format(e))
 
@@ -188,30 +191,89 @@ class ExecuteResponse(WPSResponse):
                 data["output_definitions"] = [self.outputs[o].json for o in self.outputs]
         return data
 
+    @staticmethod
+    def _render_json_response(jdoc):
+        response = dict()
+        response['status'] = jdoc['status']
+        out = jdoc['process']['outputs']
+        d = {}
+        for val in out:
+            id = val.get('identifier')
+            if id is None:
+                continue
+            type = val.get('type')
+            key = 'bbox' if type == 'bbox' else 'data'
+            if key in val:
+                d[id] = val[key]
+        response['outputs'] = d
+        return response
+
     def _construct_doc(self):
-        template = self.template_env.get_template(self.version + '/execute/main.xml')
-        doc = template.render(**self.json)
-        return doc
+        if self.status == WPS_STATUS.SUCCEEDED and \
+                hasattr(self.wps_request, 'preprocess_response') and \
+                self.wps_request.preprocess_response:
+            self.outputs = self.wps_request.preprocess_response(self.outputs,
+                                                                request=self.wps_request,
+                                                                http_request=self.wps_request.http_request)
+        doc = self.json
+        try:
+            json_response, mimetype = get_response_type(
+                self.wps_request.http_request.accept_mimetypes, self.wps_request.default_mimetype)
+        except Exception:
+            mimetype = get_default_response_mimetype()
+            json_response = 'json' in mimetype
+        if json_response:
+            doc = json.dumps(self._render_json_response(doc), cls=ArrayEncoder, indent=get_json_indent())
+        else:
+            template = self.template_env.get_template(self.version + '/execute/main.xml')
+            doc = template.render(**doc)
+        return doc, mimetype
 
     @Request.application
     def __call__(self, request):
+        accept_json_response, accepted_mimetype = get_response_type(
+            self.wps_request.http_request.accept_mimetypes, self.wps_request.default_mimetype)
         if self.wps_request.raw:
             if self.status == WPS_STATUS.FAILED:
                 return NoApplicableCode(self.message)
             else:
                 wps_output_identifier = next(iter(self.wps_request.outputs))  # get the first key only
                 wps_output_value = self.outputs[wps_output_identifier]
-                if wps_output_value.source_type is None:
+                response = wps_output_value.data
+                if response is None:
                     return NoApplicableCode("Expected output was not generated")
                 suffix = ''
-                if isinstance(wps_output_value, ComplexOutput):
-                    if wps_output_value.data_format.extension is not None:
-                        suffix = wps_output_value.data_format.extension
-                return Response(wps_output_value.data,
-                                mimetype=wps_output_value.data_format.mime_type,
+                # if isinstance(wps_output_value, ComplexOutput):
+                data_format = None
+                if hasattr(wps_output_value, 'output_format'):
+                    # this is set in the response, thus should be more precise
+                    data_format = wps_output_value.output_format
+                elif hasattr(wps_output_value, 'data_format'):
+                    # this is set in the process' response _handler function, thus could have a few supported formats
+                    data_format = wps_output_value.data_format
+                if data_format is not None:
+                    mimetype = data_format.mime_type
+                    if data_format.extension is not None:
+                        suffix = data_format.extension
+                else:
+                    # like LitearlOutput
+                    mimetype = self.wps_request.outputs[wps_output_identifier].get('mimetype', None)
+                if not isinstance(response, (str, bytes, bytearray)):
+                    if not mimetype:
+                        mimetype = accepted_mimetype
+                    json_response = mimetype and 'json' in mimetype
+                    if json_response:
+                        mimetype = 'application/json'
+                        suffix = '.json'
+                        response = json.dumps(response, cls=ArrayEncoder, indent=get_json_indent())
+                    else:
+                        response = str(response)
+                if not mimetype:
+                    mimetype = None
+                return Response(response, mimetype=mimetype,
                                 headers={'Content-Disposition': 'attachment; filename="{}"'
                                          .format(wps_output_identifier + suffix)})
         else:
             if not self.doc:
                 return NoApplicableCode("Output was not generated")
-            return Response(self.doc, mimetype='text/xml')
+            return Response(self.doc, mimetype=accepted_mimetype)


=====================================
pywps/tests.py
=====================================
@@ -2,6 +2,7 @@
 # Copyright 2018 Open Source Geospatial Foundation and others    #
 # licensed under MIT, Please consult LICENSE.txt for details     #
 ##################################################################
+import json
 import tempfile
 from pathlib import Path
 
@@ -98,6 +99,16 @@ class WpsClient(Client):
         kwargs['data'] = data
         return self.post(*args, **kwargs)
 
+    def post_json(self, *args, **kwargs):
+        doc = kwargs.pop('doc')
+        # data = json.dumps(doc, indent=2)
+        # kwargs['data'] = data
+        kwargs['json'] = doc
+        # kwargs['content_type'] = 'application/json'  # input is json, redundant as it's deducted from the json kwarg
+        # kwargs['mimetype'] = 'application/json'  # output is json
+        kwargs['environ_base'] = {'HTTP_ACCEPT': 'application/json'}  # output is json
+        return self.post(*args, **kwargs)
+
 
 class WpsTestResponse(Response):
 
@@ -144,9 +155,30 @@ def assert_process_started(resp):
     assert success.split[0] == "processstarted"
 
 
+def assert_response_success_json(resp, expected_data):
+    assert resp.status_code == 200
+
+    content_type = resp.headers['Content-Type']
+    expected_contect_type = 'application/json'
+    re_content_type = rf'{expected_contect_type}(;\s*charset=.*)?'
+    assert re.match(re_content_type, content_type)
+
+    data = json.loads(resp.data)
+
+    success = data['status']['status']
+    assert success == 'succeeded'
+
+    if expected_data:
+        outputs = data['outputs']
+        assert outputs == expected_data
+
+
 def assert_response_success(resp):
     assert resp.status_code == 200
-    assert re.match(r'text/xml(;\s*charset=.*)?', resp.headers['Content-Type'])
+    content_type = resp.headers['Content-Type']
+    expected_contect_type = 'text/xml'
+    re_content_type = rf'{expected_contect_type}(;\s*charset=.*)?'
+    assert re.match(re_content_type, content_type)
     success = resp.xpath('/wps:ExecuteResponse/wps:Status/wps:ProcessSucceeded')
     assert len(success) == 1
 


=====================================
setup.cfg
=====================================
@@ -1,9 +1,9 @@
 [bumpversion]
-current_version = 4.4.5
+current_version = 4.5.0
 commit = False
 tag = False
 parse = (?P<major>\d+)\.(?P<minor>\d+).(?P<patch>\d+)
-serialize =
+serialize = 
 	{major}.{minor}.{patch}
 
 [flake8]


=====================================
tests/test_execute.py
=====================================
@@ -17,7 +17,7 @@ from pywps.exceptions import InvalidParameterValue
 from pywps import get_inputs_from_xml
 from pywps import E, get_ElementMakerForVersion
 from pywps.app.basic import get_xpath_ns
-from pywps.tests import client_for, assert_response_success
+from pywps.tests import client_for, assert_response_success, assert_response_success_json
 from pywps import configuration
 
 from io import StringIO
@@ -112,12 +112,17 @@ def create_bbox_process():
                    outputs=[BoundingBoxOutput('outbbox', 'Output message', ["EPSG:4326"])])
 
 
-def create_complex_proces():
+def create_complex_proces(mime_type: str = 'gml'):
     def complex_proces(request, response):
-        response.outputs['complex'].data = request.inputs['complex'][0].data
+        response.outputs['complex'].data = request.inputs['complex'][0].data_as_json()
         return response
 
-    frmt = Format(mime_type='application/gml', extension=".gml")  # this is unknown mimetype
+    if mime_type == 'gml':
+        frmt = Format(mime_type='application/gml', extension=".gml")  # this is unknown mimetype
+    elif mime_type == 'geojson':
+        frmt = FORMATS.GEOJSON
+    else:
+        raise Exception(f'Unknown mime type {mime_type}')
 
     return Process(handler=complex_proces,
                    identifier='my_complex_process',
@@ -420,31 +425,52 @@ class ExecuteTest(unittest.TestCase):
         assert get_output(resp.xml) == {'outvalue': '42'}
 
     def test_post_with_no_inputs(self):
+        request = {
+            'identifier': 'ultimate_question',
+            'version': '1.0.0'
+        }
+        result = {'outvalue': '42'}
+
         client = client_for(Service(processes=[create_ultimate_question()]))
         request_doc = WPS.Execute(
-            OWS.Identifier('ultimate_question'),
-            version='1.0.0'
+            OWS.Identifier(request['identifier']),
+            version=request['version']
         )
 
         resp = client.post_xml(doc=request_doc)
         assert_response_success(resp)
-        assert get_output(resp.xml) == {'outvalue': '42'}
+        assert get_output(resp.xml) == result
+
+        resp = client.post_json(doc=request)
+        assert_response_success_json(resp, result)
 
     def test_post_with_string_input(self):
+        request = {
+            'identifier': 'greeter',
+            'version': '1.0.0',
+            'inputs': {
+                'name': 'foo'
+            },
+        }
+        result = {'message': "Hello foo!"}
+
         client = client_for(Service(processes=[create_greeter()]))
         request_doc = WPS.Execute(
-            OWS.Identifier('greeter'),
+            OWS.Identifier(request['identifier']),
             WPS.DataInputs(
                 WPS.Input(
                     OWS.Identifier('name'),
-                    WPS.Data(WPS.LiteralData('foo'))
+                    WPS.Data(WPS.LiteralData(request['inputs']['name']))
                 )
             ),
-            version='1.0.0'
+            version=request['version']
         )
         resp = client.post_xml(doc=request_doc)
         assert_response_success(resp)
-        assert get_output(resp.xml) == {'message': "Hello foo!"}
+        assert get_output(resp.xml) == result
+
+        resp = client.post_json(doc=request)
+        assert_response_success_json(resp, result)
 
     def test_bbox(self):
         client = client_for(Service(processes=[create_bbox_process()]))
@@ -478,6 +504,47 @@ class ExecuteTest(unittest.TestCase):
         upper_corner = upper_corner.strip().replace('  ', ' ')
         self.assertEqual('16.0 51.0', upper_corner)
 
+    def test_bbox_rest(self):
+        client = client_for(Service(processes=[create_bbox_process()]))
+        bbox = [15.0, 50.0, 16.0, 51.0]
+        request = dict(
+            identifier='my_bbox_process',
+            version='1.0.0',
+            inputs={
+                'mybbox': {
+                    "type": "bbox",
+                    'bbox': bbox,
+                }
+            },
+        )
+        result = {'outbbox': bbox}
+
+        resp = client.post_json(doc=request)
+        assert_response_success_json(resp, result)
+
+    def test_geojson_input_rest(self):
+        geojson = os.path.join(DATA_DIR, 'json', 'point.geojson')
+        with open(geojson) as f:
+            p = json.load(f)
+
+        my_process = create_complex_proces('geojson')
+        client = client_for(Service(processes=[my_process]))
+
+        request = dict(
+            identifier='my_complex_process',
+            version='1.0.0',
+            inputs={
+                'complex': {
+                    "type": "complex",
+                    'data': p
+                }
+            },
+        )
+        result = {'complex': p}
+
+        resp = client.post_json(doc=request)
+        assert_response_success_json(resp, result)
+
     def test_output_response_dataType(self):
         client = client_for(Service(processes=[create_greeter()]))
         request_doc = WPS.Execute(
@@ -495,6 +562,39 @@ class ExecuteTest(unittest.TestCase):
         assert el.attrib['dataType'] == 'string'
 
 
+class AllowedInputReferenceExecuteTest(unittest.TestCase):
+
+    def setUp(self):
+        self.allowedinputpaths = configuration.get_config_value('server', 'allowedinputpaths')
+        configuration.CONFIG.set('server', 'allowedinputpaths', DATA_DIR)
+
+    def tearDown(self):
+        configuration.CONFIG.set('server', 'allowedinputpaths', self.allowedinputpaths)
+
+    def test_geojson_input_reference_rest(self):
+        geojson = os.path.join(DATA_DIR, 'json', 'point.geojson')
+        with open(geojson) as f:
+            p = json.load(f)
+
+        my_process = create_complex_proces('geojson')
+        client = client_for(Service(processes=[my_process]))
+
+        request = dict(
+            identifier='my_complex_process',
+            version='1.0.0',
+            inputs={
+                'complex': {
+                    "type": "reference",
+                    "href": f"file:{geojson}"
+                }
+            },
+        )
+        result = {'complex': p}
+
+        resp = client.post_json(doc=request)
+        assert_response_success_json(resp, result)
+
+
 class ExecuteTranslationsTest(unittest.TestCase):
 
     def setUp(self):


=====================================
tests/test_processing.py
=====================================
@@ -15,7 +15,6 @@ from pywps import configuration
 import pywps.processing
 from pywps.processing.job import Job
 from pywps.processing.basic import MultiProcessing
-from pywps import Process
 from pywps.app import WPSRequest
 from pywps.response.execute import ExecuteResponse
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/pywps/-/commit/25ad6aa4a526a96ab6a83c788d161fd9ae738c8f

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pywps/-/commit/25ad6aa4a526a96ab6a83c788d161fd9ae738c8f
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210812/89da626c/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list