Skip to content

Commit

Permalink
fix invalid API route resolutions from cornice service definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
fmigneault committed Mar 22, 2024
1 parent 5ca62c7 commit c4b1809
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 24 deletions.
10 changes: 8 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ Changes:
utility instead of ``Configurator.add_route`` and ``Configurator.add_view`` handlers that were causing a lot of
duplication between the ``cornice.Service`` parametrization and their corresponding view decorators. All metadata
is now embedded within the same decorator operation.

- Update Swagger-UI version for latest rendering fixes of OpenAPI definitions.
- Add missing documentation for ``weaver.wps_restapi_doc`` and ``weaver.wps_restapi_ref`` configuration settings.
- Modified the base path/URL resolution of the `OpenAPI` endpoint to be located at the application root instead of being
nested under ``weaver.wps_restapi_path`` or ``weaver.wps_restapi_url``, since the OpenAPI `JSON` and `HTML` responses
are employed for representing supported requests and responses both the `REST` and the `OWS` `WPS` interfaces.
- Update `Swagger-UI` version for latest rendering fixes of `OpenAPI` definitions.
- Add ``weaver.wps_client_headers_filter`` setting that allows filtering of specific `WPS` request headers from the
incoming request to be passed down to the `WPS` client employed to interact with the `WPS` provider
(fixes `#600 <https://github.com/crim-ca/weaver/issues/600>`_).
Expand All @@ -26,6 +29,9 @@ Changes:

Fixes:
------
- Fix invalid resolution of reported API endpoints in the `OpenAPI` and frontpage response when
``weaver.wps_restapi_path``, ``weaver.wps_restapi_url``, ``weaver.wps_path`` or ``weaver.wps_url``
were set to other prefix path values than the default root base URL.
- Fix ``moto>=5`` used in tests to mock AWS S3 operations that replaced ``mock_s3`` context manager by ``mock_aws``.

.. _changes_5.0.0:
Expand Down
19 changes: 19 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,25 @@ they are optional and which default value or operation is applied in each situat
| The *path* variant **SHOULD** start with ``/`` for appropriate concatenation with ``weaver.url``, although this is
not strictly enforced.
- | ``weaver.wps_restapi_doc = <full-url>``
| (default: ``None``)
|
| Location that will be displayed as reference specification document for the service.
|
| Typically, this value would be set to a reference similar
to :ref:`ogc-api-proc-part1-spec-html` or :ref:`ogc-api-proc-part1-spec-pdf`.
However, this value is left by default empty to let maintainers chose which specification document is more relevant
for their own deployment, considering that they might want to support different parts of the extended specification.
- | ``weaver.wps_restapi_ref = <full-url>``
| (default: ``None``)
|
| Location that will be displayed as reference specification :term:`JSON` schema for the service.
|
| Typically, this value would be set to a reference similar to :ref:`ogc-api-proc-part1-spec-json`.
However, this value is left by default empty to let maintainers chose which specification schema is more relevant
for their own deployment, considering that they might want to support different parts of the extended specification.
- | ``weaver.wps_metadata_[...]`` (multiple settings) [:class:`str`]
|
| Metadata fields that will be rendered by either or both the WPS-1/2 and WPS-REST endpoints
Expand Down
6 changes: 6 additions & 0 deletions docs/source/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
.. |ogc-api-proc| replace:: `OGC API - Processes`
.. _ogc-api-proc: https://github.com/opengeospatial/ogcapi-processes
.. |ogc-proc-long| replace:: |ogc-api-proc|_ (WPS-REST bindings)
.. |ogc-api-proc-part1-spec-html| replace:: OGC API - Processes - Part 1: Core Specification
.. _ogc-api-proc-part1-spec-html: https://docs.ogc.org/is/18-062r2/18-062r2.html
.. |ogc-api-proc-part1-spec-pdf| replace:: OGC API - Processes - Part 1: Core Specification
.. _ogc-api-proc-part1-spec-pdf: https://docs.ogc.org/is/18-062r2/18-062r2.pdf
.. |ogc-api-proc-part1-spec-json| replace:: OGC API - Processes - Part 1: Core JSON schema
.. _ogc-api-proc-part1-spec-json: https://raw.githubusercontent.com/opengeospatial/ogcapi-processes/master/openapi/ogcapi-processes.bundled.json
.. |ogc-exec-sync-responses| replace:: OGC API - Processes, Responses (sync)
.. _ogc-exec-sync-responses: https://docs.ogc.org/is/18-062r2/18-062r2.html#sc_execute_response
.. |ogc-exec-async-responses| replace:: OGC API - Processes, Responses (async)
Expand Down
9 changes: 7 additions & 2 deletions weaver/wps/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,10 @@ def includeme(config):
wps_service.add_view("POST", pywps_view, tags=wps_tags, renderer=OutputFormat.XML,
schema=sd.WPSEndpointPost(), response_schemas=sd.wps_responses)
LOGGER.debug("Applying WPS KVP/XML service schemas with views to application.")
with sd.cornice_route_prefix(config=config, route_prefix=wps_path):
config.add_cornice_service(wps_service)
# note:
# cannot use 'add_cornice_service' directive in this case
# it uses a decorator-wrapper that provides arguments in a different manner than what is expected by 'pywps_view'
config.add_route(wps_service.name, path=wps_path)
config.add_view(pywps_view, route_name=wps_service.name)
# provide the route name explicitly to resolve the correct path when generating the OpenAPI definition
wps_service.pyramid_route = wps_service.name
56 changes: 37 additions & 19 deletions weaver/wps_restapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
from weaver import __meta__
from weaver.formats import ContentType, OutputFormat
from weaver.owsexceptions import OWSException
from weaver.utils import get_header, get_settings, get_weaver_url
from weaver.utils import get_header, get_registry, get_settings, get_weaver_url
from weaver.wps.utils import get_wps_url
from weaver.wps_restapi import swagger_definitions as sd
from weaver.wps_restapi.colander_extras import CorniceOpenAPI
from weaver.wps_restapi.constants import ConformanceCategory
from weaver.wps_restapi.utils import get_wps_restapi_base_url, wps_restapi_base_path
from weaver.wps_restapi.utils import get_wps_restapi_base_url

if TYPE_CHECKING:
from typing import Any, Callable, List, Optional
Expand All @@ -38,7 +38,14 @@
from pyramid.config import Configurator
from pyramid.registry import Registry

from weaver.typedefs import AnyRequestType, JSON, OpenAPISpecification, OpenAPISpecInfo, SettingsType, ViewHandler
from weaver.typedefs import (
AnyRequestType,
AnySettingsContainer,
JSON,
OpenAPISpecification,
SettingsType,
ViewHandler
)
from weaver.wps_restapi.constants import AnyConformanceCategory

Conformance = TypedDict("Conformance", {
Expand Down Expand Up @@ -453,16 +460,16 @@ def api_frontpage_body(settings):

weaver_api = asbool(settings.get("weaver.wps_restapi"))
weaver_api_url = get_wps_restapi_base_url(settings) if weaver_api else None
weaver_api_oas_ui = weaver_api_url + sd.api_openapi_ui_service.path if weaver_api else None
weaver_api_swagger = weaver_api_url + sd.api_swagger_ui_service.path if weaver_api else None
weaver_api_oas_ui = sd.api_openapi_ui_service.path if weaver_api else None
weaver_api_swagger = sd.api_swagger_ui_service.path if weaver_api else None
weaver_api_spec = sd.openapi_json_service.path 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_api_spec = weaver_api_url + sd.openapi_json_service.path if weaver_api else None
weaver_wps = asbool(settings.get("weaver.wps"))
weaver_wps_url = get_wps_url(settings) if weaver_wps else None
weaver_conform_url = weaver_url + sd.api_conformance_service.path
weaver_process_url = weaver_url + sd.processes_service.path
weaver_jobs_url = weaver_url + sd.jobs_service.path
weaver_conform_url = sd.api_conformance_service.path
weaver_process_url = weaver_api_url + sd.processes_service.path
weaver_jobs_url = weaver_api_url + sd.jobs_service.path
weaver_vault = asbool(settings.get("weaver.vault"))
weaver_links = [
{"href": weaver_url, "rel": "self", "type": ContentType.APP_JSON, "title": "This landing page."},
Expand Down Expand Up @@ -525,7 +532,7 @@ def api_frontpage_body(settings):
# sample:
# https://raw.githubusercontent.com/opengeospatial/wps-rest-binding/develop/docs/18-062.pdf
if "." in weaver_api_doc: # pylint: disable=E1135,unsupported-membership-test
ext_type = weaver_api_doc.split(".")[-1]
ext_type = weaver_api_doc.rsplit(".", 1)[-1]
doc_type = f"application/{ext_type}"
else:
doc_type = ContentType.TEXT_PLAIN # default most basic type
Expand Down Expand Up @@ -562,7 +569,7 @@ def api_frontpage_body(settings):
"parameters": [
{"name": "api", "enabled": weaver_api, "url": weaver_api_url, "api": weaver_api_oas_ui},
{"name": "vault", "enabled": weaver_vault},
{"name": "wps", "enabled": weaver_wps, "url": weaver_wps_url},
{"name": "wps", "enabled": weaver_wps, "url": weaver_wps_url, "api": weaver_api_oas_ui},
],
"links": weaver_links,
}
Expand Down Expand Up @@ -594,8 +601,8 @@ def api_conformance(request): # noqa: F811


def get_openapi_json(http_scheme="http", http_host="localhost", base_url=None,
use_refs=True, use_docstring_summary=True, settings=None):
# type: (str, str, Optional[str], bool, bool, Optional[SettingsType]) -> OpenAPISpecification
use_refs=True, use_docstring_summary=True, container=None):
# type: (str, str, Optional[str], bool, bool, Optional[AnySettingsContainer]) -> OpenAPISpecification
"""
Obtains the JSON schema of Weaver OpenAPI from request and response views schemas.
Expand All @@ -604,13 +611,25 @@ def get_openapi_json(http_scheme="http", http_host="localhost", base_url=None,
:param base_url: Explicit base URL to employ of as API base instead of HTTP scheme/host parameters.
:param use_refs: Generate schemas with ``$ref`` definitions or expand every schema content.
:param use_docstring_summary: Extra function docstring to auto-generate the summary field of responses.
:param settings: Application settings to retrieve further metadata details to be added to the OpenAPI.
:param container:
Container with the :mod:`pyramid` registry and settings to retrieve
further metadata details to be added to the :term:`OpenAPI`.
.. seealso::
- :mod:`weaver.wps_restapi.swagger_definitions`
"""
depth = -1 if use_refs else 0
swagger = CorniceOpenAPI(get_services(), def_ref_depth=depth, param_ref=use_refs, resp_ref=use_refs)
registry = get_registry(container)
settings = get_settings(registry)
swagger = CorniceOpenAPI(
get_services(),
def_ref_depth=depth,
param_ref=use_refs,
resp_ref=use_refs,
# registry needed to map to the resolved paths using any relevant route prefix
# if unresolved, routes will use default endpoint paths without configured setting prefixes (if any)
pyramid_registry=registry,
)
swagger.ignore_methods = ["OPTIONS"] # don't ignore HEAD, used by vault
# function docstrings are used to create the route's summary in Swagger-UI
swagger.summary_docstrings = use_docstring_summary
Expand Down Expand Up @@ -677,17 +696,16 @@ def openapi_json(request): # noqa: F811
"""
# obtain 'server' host and api-base-path, which doesn't correspond necessarily to the app's host and path
# ex: 'server' adds '/weaver' with proxy redirect before API routes
settings = get_settings(request)
weaver_server_url = get_weaver_url(settings)
weaver_server_url = get_weaver_url(request)
LOGGER.debug("Request app URL: [%s]", request.url)
LOGGER.debug("Weaver config URL: [%s]", weaver_server_url)
spec = openapi_json_cached(base_url=weaver_server_url, use_docstring_summary=True, settings=settings)
spec = openapi_json_cached(base_url=weaver_server_url, use_docstring_summary=True, container=request)
return HTTPOk(json=spec, content_type=ContentType.APP_OAS_JSON)


@cache_region("doc", sd.api_swagger_ui_service.name)
def swagger_ui_cached(request):
json_path = wps_restapi_base_path(request) + sd.openapi_json_service.path
json_path = sd.openapi_json_service.path
json_path = json_path.lstrip("/") # if path starts by '/', swagger-ui doesn't find it on remote
data_mako = {"api_title": sd.API_TITLE, "openapi_json_path": json_path, "api_version": __meta__.__version__}
resp = render_to_response("templates/swagger_ui.mako", data_mako, request=request)
Expand Down
10 changes: 9 additions & 1 deletion weaver/wps_restapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
HeadersType,
JSON,
Params,
Return
Return,
SettingsType
)

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -86,20 +87,27 @@ def __init__(self, code=200, headers=None, **kwargs):
)

def prepare(self, environ):
# type: (SettingsType) -> None
"""
No contents for HEAD request.
"""


def wps_restapi_base_path(container):
# type: (AnySettingsContainer) -> str
"""
Obtain the REST :term:`API` base path.
"""
settings = get_settings(container)
restapi_path = settings.get("weaver.wps_restapi_path", "").rstrip("/").strip()
return restapi_path


def get_wps_restapi_base_url(container):
# type: (AnySettingsContainer) -> str
"""
Obtain the REST :term:`API` base URL.
"""
settings = get_settings(container)
weaver_rest_url = settings.get("weaver.wps_restapi_url")
if not weaver_rest_url:
Expand Down

0 comments on commit c4b1809

Please sign in to comment.