Skip to content

Commit

Permalink
add conformance route (#53), frontpage links (#54) and update referen…
Browse files Browse the repository at this point in the history
…ce spec URL
  • Loading branch information
fmigneault committed Dec 20, 2019
1 parent 7d2b244 commit 5186291
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 58 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ New Features:
- Add support of ``label`` synonym as ``title`` for inputs and process description
(`CWL` specifying a ``label`` will set it in `WPS` process) (#31)
- Add support of input ``minOccurs`` and ``maxOccurs`` as ``int`` while maintaining ``str`` support (#14).
- Add conformance route with implementation links (#53).
- Add additional landing page link details (#54).

Changes:
-------------
Expand Down
3 changes: 2 additions & 1 deletion config/weaver.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ weaver.wps_metadata_provider_url=http://pavics-weaver.readthedocs.org/en/latest/
# WPS REST API
weaver.wps_restapi = true
weaver.wps_restapi_path = /
weaver.wps_restapi_ref = https://github.com/opengeospatial/D009-ADES_and_EMS_Results_and_Best_Practices_Engineering_Report/blob/master/code/ades_wpst.json
weaver.wps_restapi_ref = https://app.swaggerhub.com/apis/geoprocessing/WPS/
weaver.wps_restapi_doc = https://raw.githubusercontent.com/opengeospatial/wps-rest-binding/develop/docs/18-062.pdf
# email notification
weaver.wps_email_encrypt_salt = salty-email
weaver.wps_email_encrypt_rounds = 100000
Expand Down
53 changes: 26 additions & 27 deletions tests/wps_restapi/test_api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
from weaver.formats import CONTENT_TYPE_APP_JSON
from weaver.wps_restapi.swagger_definitions import (
api_frontpage_uri,
api_versions_uri,
api_swagger_ui_uri,
api_swagger_json_uri,
api_swagger_json_service,
API_TITLE,
FrontpageSchema,
VersionsSchema,
)
from weaver.wps_restapi import swagger_definitions as sd
from tests.utils import get_test_weaver_app, get_test_weaver_config, get_settings_from_testapp
from pyramid.httpexceptions import HTTPFound, HTTPUnauthorized, HTTPForbidden
import colander
Expand All @@ -25,27 +16,35 @@ def setUpClass(cls):
cls.json_headers = {"Accept": CONTENT_TYPE_APP_JSON, "Content-Type": CONTENT_TYPE_APP_JSON}

def test_frontpage_format(self):
resp = self.testapp.get(api_frontpage_uri, headers=self.json_headers)
resp = self.testapp.get(sd.api_frontpage_uri, headers=self.json_headers)
assert 200 == resp.status_code
try:
FrontpageSchema().deserialize(resp.json)
sd.FrontpageResponse().deserialize(resp.json)
except colander.Invalid as ex:
self.fail("expected valid response format as defined in schema [{!s}]".format(ex))

def test_version_format(self):
resp = self.testapp.get(api_versions_uri, headers=self.json_headers)
resp = self.testapp.get(sd.api_versions_uri, headers=self.json_headers)
assert 200 == resp.status_code
try:
VersionsSchema().deserialize(resp.json)
sd.VersionsResponse().deserialize(resp.json)
except colander.Invalid as ex:
self.fail("expected valid response format as defined in schema [{!s}]".format(ex))

def test_conformance_format(self):
resp = self.testapp.get(sd.api_conformance_uri, headers=self.json_headers)
assert 200 == resp.status_code
try:
sd.ConformanceSchema().deserialize(resp.json)
except colander.Invalid as ex:
self.fail("expected valid response format as defined in schema [{!s}]".format(ex))

def test_swagger_api_format(self):
resp = self.testapp.get(api_swagger_ui_uri)
resp = self.testapp.get(sd.api_swagger_ui_uri)
assert 200 == resp.status_code
assert "<title>{}</title>".format(API_TITLE) in resp.text
assert "<title>{}</title>".format(sd.API_TITLE) in resp.text

resp = self.testapp.get(api_swagger_json_uri, headers=self.json_headers)
resp = self.testapp.get(sd.api_swagger_json_uri, headers=self.json_headers)
assert 200 == resp.status_code
assert "tags" in resp.json
assert "info" in resp.json
Expand All @@ -57,18 +56,18 @@ def test_swagger_api_format(self):
def test_status_unauthorized_and_forbidden(self):
# methods should return corresponding status codes, shouldn't be the default '403' on both cases
with mock.patch("weaver.utils.get_weaver_url", side_effect=HTTPUnauthorized):
resp = self.testapp.get(api_frontpage_uri, headers=self.json_headers, expect_errors=True)
resp = self.testapp.get(sd.api_frontpage_uri, headers=self.json_headers, expect_errors=True)
assert 401 == resp.status_code
with mock.patch("weaver.utils.get_weaver_url", side_effect=HTTPForbidden):
resp = self.testapp.get(api_frontpage_uri, headers=self.json_headers, expect_errors=True)
resp = self.testapp.get(sd.api_frontpage_uri, headers=self.json_headers, expect_errors=True)
assert 403 == resp.status_code

def test_status_not_found_and_method_not_allowed(self):
resp = self.testapp.post("/random", headers=self.json_headers, expect_errors=True)
assert 404 == resp.status_code

# test an existing route with wrong method, shouldn't be the default '404' on both cases
resp = self.testapp.post(api_frontpage_uri, headers=self.json_headers, expect_errors=True)
resp = self.testapp.post(sd.api_frontpage_uri, headers=self.json_headers, expect_errors=True)
assert 405 == resp.status_code


Expand Down Expand Up @@ -108,18 +107,18 @@ def test_swagger_api_request_base_path_proxied(self):
assert resp.json["basePath"] == self.api_base_path

# validate that swagger UI still renders and has valid URL
resp = self.testapp.get(api_swagger_ui_uri)
resp = self.testapp.get(sd.api_swagger_ui_uri)
assert 200 == resp.status_code
assert "<title>{}</title>".format(API_TITLE) in resp.text
assert "<title>{}</title>".format(sd.API_TITLE) in resp.text

def test_swagger_api_request_base_path_original(self):
"""
Validates that Swagger JSON properly uses the original host/path to test live requests on Swagger UI
when the app's URI results direct route access.
"""
resp = self.testapp.get(api_swagger_ui_uri)
resp = self.testapp.get(sd.api_swagger_ui_uri)
assert 200 == resp.status_code
assert "<title>{}</title>".format(API_TITLE) in resp.text
assert "<title>{}</title>".format(sd.API_TITLE) in resp.text

# ensure that environment that would define the weaver location is not defined for local app
with mock.patch.dict("os.environ"):
Expand All @@ -128,9 +127,9 @@ def test_swagger_api_request_base_path_original(self):
resp = resp.follow()
assert 200 == resp.status_code
assert self.api_base_path not in resp.json["host"]
assert resp.json["basePath"] == api_frontpage_uri
assert resp.json["basePath"] == sd.api_frontpage_uri

# validate that swagger UI still renders and has valid URL
resp = self.testapp.get(api_swagger_ui_uri)
resp = self.testapp.get(sd.api_swagger_ui_uri)
assert 200 == resp.status_code
assert "<title>{}</title>".format(API_TITLE) in resp.text
assert "<title>{}</title>".format(sd.API_TITLE) in resp.text
1 change: 1 addition & 0 deletions weaver/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
CONTENT_TYPE_APP_ZIP = "application/zip"
CONTENT_TYPE_TEXT_HTML = "text/html"
CONTENT_TYPE_TEXT_PLAIN = "text/plain"
CONTENT_TYPE_APP_PDF = "application/pdf"
CONTENT_TYPE_APP_JSON = "application/json"
CONTENT_TYPE_APP_XML = "application/xml"
CONTENT_TYPE_TEXT_XML = "text/xml"
Expand Down
2 changes: 1 addition & 1 deletion weaver/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __nonzero__():
def get_weaver_url(container):
# type: (AnySettingsContainer) -> AnyStr
"""Retrieves the home URL of the `weaver` application."""
return get_settings(container).get("weaver.url").rstrip('/').strip()
return get_settings(container).get("weaver.url").rstrip("/").strip()


def get_any_id(info):
Expand Down
3 changes: 3 additions & 0 deletions weaver/wps_restapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def includeme(config):
config.add_route(**sd.service_api_route_info(sd.api_swagger_json_service, settings))
config.add_route(**sd.service_api_route_info(sd.api_swagger_ui_service, settings))
config.add_route(**sd.service_api_route_info(sd.api_versions_service, settings))
config.add_route(**sd.service_api_route_info(sd.api_conformance_service, settings))
config.add_view(api.api_frontpage, route_name=sd.api_frontpage_service.name,
request_method="GET", renderer=OUTPUT_FORMAT_JSON)
config.add_view(api.api_swagger_json, route_name=sd.api_swagger_json_service.name,
Expand All @@ -30,5 +31,7 @@ def includeme(config):
request_method="GET", renderer="templates/swagger_ui.mako")
config.add_view(api.api_versions, route_name=sd.api_versions_service.name,
request_method="GET", renderer=OUTPUT_FORMAT_JSON)
config.add_view(api.api_conformance, route_name=sd.api_conformance_service.name,
request_method="GET", renderer=OUTPUT_FORMAT_JSON)
config.add_notfound_view(api.not_found_or_method_not_allowed)
config.add_forbidden_view(api.unauthorized_or_forbidden)
59 changes: 51 additions & 8 deletions weaver/wps_restapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from weaver.wps_restapi import swagger_definitions as sd
from weaver.wps_restapi.colander_extras import CustomTypeConversionDispatcher
from weaver.wps_restapi.utils import get_wps_restapi_base_url, wps_restapi_base_path, OUTPUT_FORMAT_JSON
from weaver.formats import CONTENT_TYPE_APP_JSON
from weaver.formats import CONTENT_TYPE_APP_JSON, CONTENT_TYPE_TEXT_PLAIN
from weaver.owsexceptions import OWSException
from six.moves.urllib.parse import urlparse
from cornice_swagger import CorniceSwagger
Expand Down Expand Up @@ -50,10 +50,34 @@ def api_frontpage(request):

weaver_api = asbool(settings.get("weaver.wps_restapi"))
weaver_api_url = get_wps_restapi_base_url(settings) if weaver_api else None
weaver_api_doc = weaver_api_url + sd.api_swagger_ui_uri if weaver_api else None
weaver_api_def = weaver_api_url + sd.api_swagger_ui_uri if weaver_api else None
weaver_api_doc = settings.get("weaver.wps_restapi_doc", None) if weaver_api else None
weaver_api_ref = settings.get("weaver.wps_restapi_ref", None) if weaver_api else None
weaver_wps = asbool(settings.get("weaver.wps"))
weaver_wps_url = weaver_url + get_wps_path(settings) if weaver_wps else None
weaver_conform_url = weaver_url + sd.api_conformance_uri
weaver_process_url = weaver_url + sd.processes_uri
weaver_links = [
{"href": weaver_url, "rel": "self", "type": CONTENT_TYPE_APP_JSON, "title": "This document"},
{"href": weaver_conform_url, "rel": "conformance", "type": CONTENT_TYPE_APP_JSON,
"title": "WPS 2.0/3.0 REST-JSON Binding Extension conformance classes implemented by this service."},
]
if weaver_api_def:
weaver_links.append({"href": weaver_api_def, "rel": "service", "type": CONTENT_TYPE_APP_JSON,
"title": "API definition of this service."})
if isinstance(weaver_api_doc, six.string_types):
if "." in weaver_api_doc:
ext_type = weaver_api_doc.split(".")[-1]
doc_type = "application/{}".format(ext_type)
else:
doc_type = CONTENT_TYPE_TEXT_PLAIN # default most basic type
weaver_links.append({"href": weaver_api_doc, "rel": "documentation", "type": doc_type,
"title": "API documentation about this service."})
if weaver_api_ref:
weaver_links.append({"href": weaver_api_ref, "rel": "reference", "type": CONTENT_TYPE_APP_JSON,
"title": "API reference specification of this service."})
weaver_links.append({"href": weaver_process_url, "rel": "processes", "type": CONTENT_TYPE_APP_JSON,
"title": "Processes offered by this service."})

return {
"message": "Weaver Information",
Expand All @@ -62,21 +86,40 @@ def api_frontpage(request):
{"name": "api", "enabled": weaver_api,
"url": weaver_api_url,
"doc": weaver_api_doc,
"api": weaver_api_def,
"ref": weaver_api_ref},
{"name": "wps", "enabled": weaver_wps,
"url": weaver_wps_url},
]
],
"links": weaver_links,
}


# noinspection PyUnusedLocal
@sd.api_versions_service.get(tags=[sd.TAG_API], renderer=OUTPUT_FORMAT_JSON,
schema=sd.VersionsEndpoint(), response_schemas=sd.get_api_versions_responses)
def api_versions(request):
def api_versions(request): # noqa: F811
# type: (Request) -> HTTPException
"""weaver versions information."""
weaver_info = {'name': 'weaver', 'version': weaver_version, "type": "api"}
return HTTPOk(json={'versions': [weaver_info]})
"""Weaver versions information."""
weaver_info = {"name": "weaver", "version": weaver_version, "type": "api"}
return HTTPOk(json={"versions": [weaver_info]})


@sd.api_conformance_service.get(tags=[sd.TAG_API], renderer=OUTPUT_FORMAT_JSON,
schema=sd.ConformanceEndpoint(), response_schemas=sd.get_api_conformance_responses)
def api_conformance(request): # noqa: F811
# type: (Request) -> HTTPException
"""Weaver specification conformance information."""
# TODO: follow updates with https://github.com/geopython/pygeoapi/issues/198
conformance = {"conformsTo": [
"http://www.opengis.net/spec/wfs-1/3.0/req/core",
"http://www.opengis.net/spec/wfs-1/3.0/req/oas30",
# "http://www.opengis.net/spec/wfs-1/3.0/req/html",
"http://www.opengis.net/spec/wfs-1/3.0/req/geojson",
"http://www.opengis.net/spec/WPS/2.0/req/service/binding/rest-json/core",
"http://www.opengis.net/spec/WPS/2.0/req/service/binding/rest-json/oas30",
# "http://www.opengis.net/spec/WPS/2.0/req/service/binding/rest-json/html"
]}
return HTTPOk(json=conformance)


@sd.api_swagger_json_service.get(tags=[sd.TAG_API], renderer=OUTPUT_FORMAT_JSON,
Expand Down
Loading

0 comments on commit 5186291

Please sign in to comment.