diff --git a/.pylintrc b/.pylintrc index 3ea9bfe32..ec0a5d9d0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -32,7 +32,8 @@ limit-inference-results=100 # usually to register additional checkers. # https://pylint.pycqa.org/en/latest/technical_reference/extensions.html load-plugins=pylint.extensions.docparams, - pylint.extensions.mccabe + pylint.extensions.mccabe, + pylint_per_file_ignores # https://pylint.pycqa.org/en/latest/technical_reference/extensions.html#design-checker-options max-complexity = 24 @@ -125,6 +126,9 @@ disable=C0111,missing-docstring, # note: (C0412, ungrouped-imports) is managed via isort tool, ignore false positives +per-file-ignores = + tests/*:R1729 + # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where diff --git a/CHANGES.rst b/CHANGES.rst index 13ccbfc34..b0e3d3039 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,11 +12,17 @@ Changes Changes: -------- -- No change. +- Add the official `CWL` `JSON` schema reference + (`common-workflow-language/cwl-v1.2#256 `_) + as ``$schema`` parameter returned in under the `OpenAPI` schema for the `CWL` component employed by `Weaver` + (fixes `#547 `_). +- Add ``$schema`` field auto-insertion into the generated `OpenAPI` schema definition by ``CorniceSwagger`` when + corresponding ``colander.SchemaNode`` definitions contain a ``_schema = ""`` attribute + (fixes `#157 `_). Fixes: ------ -- No change. +- Fix broken `OpenAPI` schema link references to `OGC API - Processes` repository. .. _changes_4.30.1: @@ -725,7 +731,7 @@ Changes: The previous schema for deployment with nested ``process`` field remains supported for backward compatibility. .. |ogc-app-pkg| replace:: OGC Application Package -.. _ogc-app-pkg: https://github.com/opengeospatial/ogcapi-processes/blob/master/extensions/deploy_replace_undeploy/standard/openapi/schemas/ogcapppkg.yaml +.. _ogc-app-pkg: https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-dru/ogcapppkg.yaml Fixes: ------ diff --git a/README.rst b/README.rst index 7c757ed37..45bc15087 100644 --- a/README.rst +++ b/README.rst @@ -331,7 +331,7 @@ It is part of `PAVICS`_ and `Birdhouse`_ ecosystems and is available within the .. |ogc-api-proc-part2| replace:: `OGC API - Processes - Part 2: Deploy, Replace, Undeploy`_ (DRU) extension .. _`OGC API - Processes - Part 2: Deploy, Replace, Undeploy`: https://github.com/opengeospatial/ogcapi-processes/tree/master/extensions/deploy_replace_undeploy .. |ogc-apppkg| replace:: `OGC Application Package` -.. _ogc-apppkg: https://github.com/opengeospatial/ogcapi-processes/blob/master/extensions/deploy_replace_undeploy/standard/openapi/schemas/ogcapppkg.yaml +.. _ogc-apppkg: https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-dru/ogcapppkg.yaml .. _PAVICS: https://ouranosinc.github.io/pavics-sdi/index.html .. _Birdhouse: http://bird-house.github.io/ .. _birdhouse-deploy: https://github.com/bird-house/birdhouse-deploy diff --git a/requirements-dev.txt b/requirements-dev.txt index 6ef229beb..067b1702f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,6 +25,7 @@ pytest<7 pydocstyle pylint>=2.11,!=2.12,<2.14; python_version <= "3.6" pylint>=2.15.4; python_version >= "3.7" +pylint-per-file-ignores; python_version >= "3.7" pylint_quotes responses safety diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index a80df5cd6..183a9a2ba 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -194,6 +194,7 @@ def test_processes_with_providers(self): providers = result.body["providers"] for prov in providers: prov.pop("$schema", None) + prov.pop("$id", None) assert providers == [ {"id": prov1.name, "processes": resources.TEST_EMU_WPS1_PROCESSES}, {"id": prov2.name, "processes": resources.TEST_HUMMINGBIRD_WPS1_PROCESSES}, @@ -379,6 +380,7 @@ def test_describe(self): output_formats = result.body["outputs"]["output"]["formats"] for out_fmt in output_formats: out_fmt.pop("$schema", None) + out_fmt.pop("$id", None) assert output_formats == [{"default": True, "mediaType": ContentType.TEXT_PLAIN}] assert "undefined" not in result.message, "CLI should not have confused process description as response detail." assert result.body["description"] == ( @@ -459,7 +461,7 @@ def test_execute_manual_monitor_status_and_download_results(self): Test a typical case of :term:`Job` execution, result retrieval and download, but with manual monitoring. Manual monitoring can be valid in cases where a *very* long :term:`Job` must be executed, and the user does - not intend to wait after it. This avoids leaving some shell/notebook/etc. open of a long time and provide a + not intend to wait for it. This avoids leaving some shell/notebook/etc. open of a long time and provide a massive ``timeout`` value. Instead, the user can simply re-call :meth:`WeaverClient.monitor` at a later time to resume monitoring. Other situation can be if the connection was dropped or script runner crashed, and the want to pick up monitoring again. @@ -1198,10 +1200,11 @@ def test_deploy_payload_process_info_merged(self): out_oas_fmt = {"default": True, "mediaType": ContentType.APP_JSON} out_any_fmt = [out_cwl_fmt, out_oas_fmt] # ignore schema specifications for comparison only of contents - in_schema.pop("$schema", None) - out_schema.pop("$schema", None) - for out_fmt in out_formats: - out_fmt.pop("$schema", None) + for field in ["$id", "$schema"]: + in_schema.pop(field, None) + out_schema.pop(field, None) + for out_fmt in out_formats: + out_fmt.pop(field, None) # if any of the below definitions don't include user-provided information, # CLI did not combine it as intended prior to sending deployment request assert in_schema == in_oas # injected by user provided process description diff --git a/tests/functional/test_docker_app.py b/tests/functional/test_docker_app.py index 665b80e3a..b25207e9c 100644 --- a/tests/functional/test_docker_app.py +++ b/tests/functional/test_docker_app.py @@ -13,6 +13,7 @@ from weaver.processes.wps_package import CWL_REQUIREMENT_APP_DOCKER from weaver.utils import fetch_file, get_any_value, load_file, str2bytes from weaver.wps.utils import get_wps_url +from weaver.wps_restapi.swagger_definitions import CWL_SCHEMA_URL from weaver.wps_restapi.utils import get_wps_restapi_base_url @@ -119,8 +120,17 @@ def test_deployed_process_schemas(self): # process already deployed by setUpClass body = self.get_deploy_body() process = self.process_store.fetch_by_id(self.process_id) - assert process.package == body["executionUnit"][0]["unit"] - assert process.payload == body + assert "$id" in process.package + assert process.package["$id"] == CWL_SCHEMA_URL + + payload = process.payload + payload.pop("$schema", None) + payload.pop("$id", None) + package = process.package + package.pop("$schema", None) + package.pop("$id", None) + assert package == body["executionUnit"][0]["unit"] + assert payload == body def test_execute_wps_rest_resp_json(self): """ diff --git a/tests/test_schemas.py b/tests/test_schemas.py index e91df429d..380ae482b 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -127,6 +127,7 @@ def test_format_variations(test_format, expect_format): try: result_format = format_schema.deserialize(test_format) result_format.pop("$schema", None) + result_format.pop("$id", None) assert result_format == expect_format except colander.Invalid: pytest.fail(f"Expected format to be valid: [{test_format}]") diff --git a/tests/test_utils.py b/tests/test_utils.py index c22432f1d..b8f89e5e0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -647,7 +647,7 @@ def mock_sleep(delay): # since backoff factor multiplies all incrementally increasing delays between requests, # proper detection of input backoff=0 makes all sleep calls equal to zero - assert all(backoff == 0 for backoff in sleep_counter["called_with"]) + assert all([backoff == 0 for backoff in sleep_counter["called_with"]]) assert sleep_counter["called_count"] == 3 # first direct call doesn't have any sleep from retry diff --git a/tests/wps_restapi/test_api.py b/tests/wps_restapi/test_api.py index a13a39a4a..d4fe92303 100644 --- a/tests/wps_restapi/test_api.py +++ b/tests/wps_restapi/test_api.py @@ -114,6 +114,13 @@ def test_swagger_api_format(self): assert "openapi" in resp.json assert "basePath" in resp.json + def test_openapi_includes_schema(self): + resp = self.testapp.get(sd.openapi_json_service.path, headers=self.json_headers) + assert resp.status_code == 200 + body = resp.json + assert "$id" in body["components"]["schemas"]["CWL"] + assert body["components"]["schemas"]["CWL"]["$id"] == sd.CWL_SCHEMA_URL + def test_status_unauthorized_and_forbidden(self): """ Validates that 401/403 status codes are correctly handled and that the appropriate one is returned. diff --git a/tests/wps_restapi/test_jobs.py b/tests/wps_restapi/test_jobs.py index bec77839f..ab8fce90b 100644 --- a/tests/wps_restapi/test_jobs.py +++ b/tests/wps_restapi/test_jobs.py @@ -1494,6 +1494,7 @@ def test_jobs_inputs_outputs_validations(self): job_none = sd.Execute().deserialize({}) job_none.pop("$schema", None) + job_none.pop("$id", None) assert job_none == { "inputs": {}, "outputs": {}, @@ -1503,6 +1504,7 @@ def test_jobs_inputs_outputs_validations(self): job_in_none = sd.Execute().deserialize({"outputs": {"random": default_trans_mode}}) job_in_none.pop("$schema", None) + job_in_none.pop("$id", None) assert job_in_none == { "inputs": {}, "outputs": {"random": default_trans_mode}, @@ -1512,6 +1514,7 @@ def test_jobs_inputs_outputs_validations(self): job_in_empty_dict = sd.Execute().deserialize({"inputs": {}, "outputs": {"random": default_trans_mode}}) job_in_empty_dict.pop("$schema", None) + job_in_empty_dict.pop("$id", None) assert job_in_empty_dict == { "inputs": {}, "outputs": {"random": default_trans_mode}, @@ -1521,6 +1524,7 @@ def test_jobs_inputs_outputs_validations(self): job_in_empty_list = sd.Execute().deserialize({"inputs": [], "outputs": {"random": default_trans_mode}}) job_in_empty_list.pop("$schema", None) + job_in_empty_list.pop("$id", None) assert job_in_empty_list == { "inputs": [], "outputs": {"random": default_trans_mode}, @@ -1530,6 +1534,7 @@ def test_jobs_inputs_outputs_validations(self): job_out_none = sd.Execute().deserialize({"inputs": {"random": "ok"}}) job_out_none.pop("$schema", None) + job_out_none.pop("$id", None) assert job_out_none == { "inputs": {"random": "ok"}, "outputs": {}, @@ -1539,6 +1544,7 @@ def test_jobs_inputs_outputs_validations(self): job_out_empty_dict = sd.Execute().deserialize({"inputs": {"random": "ok"}, "outputs": {}}) job_out_empty_dict.pop("$schema", None) + job_out_empty_dict.pop("$id", None) assert job_out_empty_dict == { "inputs": {"random": "ok"}, "outputs": {}, @@ -1548,6 +1554,7 @@ def test_jobs_inputs_outputs_validations(self): job_out_empty_list = sd.Execute().deserialize({"inputs": {"random": "ok"}, "outputs": []}) job_out_empty_list.pop("$schema", None) + job_out_empty_list.pop("$id", None) assert job_out_empty_list == { "inputs": {"random": "ok"}, "outputs": [], @@ -1560,6 +1567,7 @@ def test_jobs_inputs_outputs_validations(self): "outputs": {"random": {"transmissionMode": ExecuteTransmissionMode.REFERENCE}} }) job_out_defined.pop("$schema", None) + job_out_defined.pop("$id", None) assert job_out_defined == { "inputs": {"random": "ok"}, "outputs": {"random": {"transmissionMode": ExecuteTransmissionMode.REFERENCE}}, diff --git a/tests/wps_restapi/test_processes.py b/tests/wps_restapi/test_processes.py index 7246c6c23..7cd4f0788 100644 --- a/tests/wps_restapi/test_processes.py +++ b/tests/wps_restapi/test_processes.py @@ -977,6 +977,10 @@ def test_deploy_process_CWL_DockerRequirement_executionUnit(self): cwl_out = cwl["outputs"]["output"] cwl_out["id"] = "output" cwl["outputs"] = [cwl_out] + cwl.pop("$schema", None) + cwl.pop("$id", None) + pkg.pop("$schema", None) + pkg.pop("$id", None) assert pkg == cwl # process description should have been generated with relevant I/O diff --git a/weaver/datatype.py b/weaver/datatype.py index 28dbb1b4c..cbeea073a 100644 --- a/weaver/datatype.py +++ b/weaver/datatype.py @@ -2510,9 +2510,9 @@ def offering(self, schema=ProcessSchema.OGC, request=None): for io_type in ["inputs", "outputs"]: process[io_type] = normalize_ordered_io(process[io_type], io_hints[io_type]) process.update({"process": dict(process)}) - return sd.ProcessDescriptionOLD().deserialize(process) + return sd.ProcessDescriptionOLD(schema_meta_include=True).deserialize(process) # process fields directly at root + I/O as mappings - return sd.ProcessDescriptionOGC().deserialize(process) + return sd.ProcessDescriptionOGC(schema_meta_include=True).deserialize(process) def summary(self, revision=False, links=True, container=None): # type: (bool, bool, Optional[AnySettingsContainer]) -> JSON diff --git a/weaver/processes/utils.py b/weaver/processes/utils.py index 81ddff96e..c6dc9c0b4 100644 --- a/weaver/processes/utils.py +++ b/weaver/processes/utils.py @@ -195,7 +195,7 @@ def _check_deploy(payload): """ message = "Process deployment definition is invalid." try: - results = sd.Deploy().deserialize(payload) + results = sd.Deploy(schema_meta_include=False, schema_include=False).deserialize(payload) # Because many fields are optional during deployment to allow flexibility between compatible WPS/CWL # definitions, any invalid field at lower-level could make a full higher-level definition to be dropped. # Verify the result to ensure this was not the case for known cases to attempt early detection. @@ -239,7 +239,10 @@ def _check_deploy(payload): r_exec_unit = results.get("executionUnit", [{}]) if p_exec_unit and p_exec_unit != r_exec_unit: message = "Process deployment execution unit is invalid." - d_exec_unit = sd.ExecutionUnitList().deserialize(p_exec_unit) # raises directly if caused by invalid schema + d_exec_unit = sd.ExecutionUnitList( + schema_meta_include=False, + schema_include=False, + ).deserialize(p_exec_unit) # raises directly if caused by invalid schema if r_exec_unit != d_exec_unit: # otherwise raise a generic error, don't allow differing definitions message = ( "Process deployment execution unit resolved as valid definition but differs from submitted " @@ -352,6 +355,7 @@ def deploy_process_from_payload(payload, container, overwrite=False): # pylint: payload_copy = deepcopy(payload) payload = _check_deploy(payload) payload.pop("$schema", None) + payload.pop("$id", None) # validate identifier naming for unsupported characters process_desc = payload.get("processDescription", {}) # empty possible if CWL directly passed @@ -459,6 +463,7 @@ def deploy_process_from_payload(payload, container, overwrite=False): # pylint: # remove schema to avoid later deserialization error if different, but remaining content is valid # also, avoid storing this field in the process object, regenerate it as needed during responses process_info.pop("$schema", None) + process_info.pop("$id", None) try: process = Process(process_info) # if 'version' was provided in deploy info, it will be added as hint here diff --git a/weaver/wps_restapi/colander_extras.py b/weaver/wps_restapi/colander_extras.py index 548577802..1dc201d6d 100644 --- a/weaver/wps_restapi/colander_extras.py +++ b/weaver/wps_restapi/colander_extras.py @@ -80,6 +80,7 @@ convert_regex_validator ) from cornice_swagger.swagger import CorniceSwagger, DefinitionHandler, ParameterHandler, ResponseHandler +from jsonschema.validators import Draft7Validator if TYPE_CHECKING: from typing import Any, Dict, Iterable, List, Optional, Sequence, Type, Union @@ -1179,31 +1180,36 @@ class SchemaRefMappingSchema(ExtendedNodeInterface, ExtendedSchemaBase): """ Mapping schema that supports auto-insertion of JSON-schema references provided in the definition. - When the :class:`colander.MappingSchema` defines ``_schema = ""`` or ``_id = ""`` with a valid URL, - all validations will automatically insert the corresponding ``$schema`` or ``$id`` field with this URL to the - deserialized JSON result. + When the :class:`colander.MappingSchema` defines ``_schema = ""`` with a valid URL, + all validations will automatically insert the corresponding ``$schema`` or ``$id`` field with this URL to + the deserialized :term:`OpenAPI` schema using :class:`SchemaRefConverter`, and to the deserialized :term:`JSON` + content, respectively. When injecting the ``$id`` reference into the :term:`JSON` object, the ``$schema`` will + instead refer to the ``schema_meta`` attribute that default to the :term:`JSON` meta-schema. - Alternatively, the parameters ``schema`` and ``id`` can be passed as keyword arguments when instantiating the - schema node. + Alternatively, the parameters ``schema`` and ``schema_meta`` can be passed as keyword arguments when instantiating + the schema node. The references injection can be disabled with ``schema_meta_include`` and ``schema_include``. """ _extension = "_ext_schema_ref" - _ext_schema_fields = ["_schema", "_id"] + _ext_schema_options = ["_schema_meta", "_schema_meta_include", "_schema", "_schema_include"] + _ext_schema_fields = ["_id", "_schema"] # typings and attributes to help IDEs flag that the field is available/overridable - _schema = None # type: str - _id = None # type: str + + _schema_meta = Draft7Validator.META_SCHEMA["$schema"] # type: str + _schema_meta_include = False # type: bool + _schema = None # type: str + _schema_include = True # type: bool def __init__(self, *args, **kwargs): - schema_fields = self._schema_fields - for schema_key in schema_fields: + for schema_key in self._schema_options: schema_field = schema_key[1:] - schema_ref = kwargs.pop(schema_field, None) - if self._is_schema_ref(schema_ref): - setattr(self, schema_key, schema_ref) + schema_value = kwargs.pop(schema_field, None) + if schema_value not in ["", None]: + setattr(self, schema_key, schema_value) super(SchemaRefMappingSchema, self).__init__(*args, **kwargs) setattr(self, SchemaRefMappingSchema._extension, True) - for schema_key in schema_fields: + for schema_key in self._schema_fields: schema_field = f"${schema_key[1:]}" sort_first = getattr(self, "_sort_first", []) sort_after = getattr(self, "_sort_after", []) @@ -1215,22 +1221,26 @@ def _is_schema_ref(schema_ref): # type: (Any) -> bool return isinstance(schema_ref, str) and URL.match_object.match(schema_ref) + @property + def _schema_options(self): + return getattr(self, "_ext_schema_options", SchemaRefMappingSchema._ext_schema_options) + @property def _schema_fields(self): return getattr(self, "_ext_schema_fields", SchemaRefMappingSchema._ext_schema_fields) - def _deserialize_impl(self, cstruct): # pylint: disable=W0222,signature-differs + def _schema_deserialize(self, cstruct, schema_meta, schema_id): + # type: (OpenAPISchema, Optional[str], Optional[str]) -> OpenAPISchema if not isinstance(cstruct, dict): return cstruct if not getattr(self, SchemaRefMappingSchema._extension, False): return cstruct schema_result = {} - schema_fields = self._schema_fields - for schema_key in schema_fields: - schema_ref = getattr(self, schema_key, None) + schema_fields = [("schema", schema_meta), ("id", schema_id)] + for schema_key, schema_ref in schema_fields: if self._is_schema_ref(schema_ref): - schema_field = f"${schema_key[1:]}" + schema_field = f"${schema_key}" schema = ExtendedSchemaNode( colander.String(), name=schema_field, @@ -1244,6 +1254,21 @@ def _deserialize_impl(self, cstruct): # pylint: disable=W0222,signature-differs schema_result.update(cstruct) return schema_result + def _deserialize_impl(self, cstruct): # pylint: disable=W0222,signature-differs + schema_id = schema_meta = None + schema_id_include = getattr(self, "_schema_include", False) + schema_meta_include = getattr(self, "_schema_meta_include", False) + if schema_meta_include: + schema_meta = getattr(self, "_schema_meta", None) + if schema_id_include: + schema_id = getattr(self, "_schema", None) + if schema_id or schema_meta: + return self._schema_deserialize(cstruct, schema_meta, schema_id) + return cstruct + + def convert_type(self, cstruct): # pylint: disable=W0222,signature-differs + return SchemaRefMappingSchema._deserialize_impl(self, cstruct) + @staticmethod @abstractmethod def schema_type(): @@ -2233,7 +2258,25 @@ def _deserialize_keyword(self, cstruct): return ExtendedMappingSchema.deserialize(self, cstruct) -class ExtendedTypeConverter(TypeConverter): +class SchemaRefConverter(TypeConverter): + """ + Converter that will add :term:`OpenAPI` ``$schema`` and ``$id`` references if they are provided in the schema node. + """ + def convert_type(self, schema_node): + # type: (colander.SchemaNode) -> OpenAPISchema + result = super(SchemaRefConverter, self).convert_type(schema_node) + if isinstance(schema_node, SchemaRefMappingSchema): + # apply any resolved schema references at the top of the definition + result_ref = SchemaRefMappingSchema.convert_type(schema_node, {}) + result_ref.update(result) + result = result_ref + return result + + +class ExtendedTypeConverter(SchemaRefConverter): + """ + Base converter with support of `Extended` schema type definitions. + """ def convert_type(self, schema_node): # type: (colander.SchemaNode) -> OpenAPISchema # base type converters expect raw pattern string @@ -2250,7 +2293,7 @@ def convert_type(self, schema_node): return result -class KeywordTypeConverter(TypeConverter): +class KeywordTypeConverter(SchemaRefConverter): """ Generic keyword converter that builds schema with a list of sub-schemas under the keyword. """ diff --git a/weaver/wps_restapi/swagger_definitions.py b/weaver/wps_restapi/swagger_definitions.py index f846a706a..11ba8097f 100644 --- a/weaver/wps_restapi/swagger_definitions.py +++ b/weaver/wps_restapi/swagger_definitions.py @@ -121,6 +121,10 @@ CWL_VERSION = "v1.2" CWL_REPO_URL = "https://github.com/common-workflow-language" +CWL_SCHEMA_BRANCH = "1.2.1_proposed" +CWL_SCHEMA_PATH = "json-schema/cwl.yaml" +CWL_SCHEMA_REPO = f"https://raw.githubusercontent.com/common-workflow-language/cwl-{CWL_VERSION}" +CWL_SCHEMA_URL = f"{CWL_SCHEMA_REPO}/{CWL_SCHEMA_BRANCH}/{CWL_SCHEMA_PATH}" CWL_BASE_URL = "https://www.commonwl.org" CWL_SPEC_URL = f"{CWL_BASE_URL}/#Specification" CWL_USER_GUIDE_URL = f"{CWL_BASE_URL}/user_guide" @@ -146,12 +150,12 @@ OGC_API_EXAMPLES_CORE = f"{OGC_API_SCHEMA_BASE}/core/examples" # FIXME: OGC OpenAPI schema restructure (https://github.com/opengeospatial/ogcapi-processes/issues/319) # OGC_API_SCHEMA_EXT_DEPLOY = f"{OGC_API_SCHEMA_BASE}/openapi/schemas/processes-dru" -OGC_API_SCHEMA_EXT_DEPLOY = f"{OGC_API_SCHEMA_BASE}/extensions/deploy_replace_undeploy/standard/openapi/schemas" +OGC_API_SCHEMA_EXT_DEPLOY = f"{OGC_API_SCHEMA_BASE}/openapi/schemas/processes-dru" OGC_API_EXAMPLES_EXT_DEPLOY = f"{OGC_API_SCHEMA_BASE}/extensions/deploy_replace_undeploy/examples" # not available yet: -OGC_API_SCHEMA_EXT_BILL = f"{OGC_API_SCHEMA_BASE}/extensions/billing/standard/openapi/schemas" -OGC_API_SCHEMA_EXT_QUOTE = f"{OGC_API_SCHEMA_BASE}/extensions/quotation/standard/openapi/schemas" -OGC_API_SCHEMA_EXT_WORKFLOW = f"{OGC_API_SCHEMA_BASE}/extensions/workflows/standard/openapi/schemas" +OGC_API_SCHEMA_EXT_BILL = f"{OGC_API_SCHEMA_BASE}/openapi/schemas/processes-billing" +OGC_API_SCHEMA_EXT_QUOTE = f"{OGC_API_SCHEMA_BASE}/openapi/schemas/processes-quotation" +OGC_API_SCHEMA_EXT_WORKFLOW = f"{OGC_API_SCHEMA_BASE}/openapi/schemas/processes-workflows" # official/published references OGC_API_PROC_PART1 = "https://schemas.opengis.net/ogcapi/processes/part1/1.0" @@ -820,8 +824,8 @@ class Format(ExtendedMappingSchema): """ Used to respect ``mediaType`` field as suggested per `OGC-API`. """ + _schema_include = False # exclude "$id" added on each sub-deserialize (too verbose, only for reference) _schema = f"{OGC_API_PROC_PART1_SCHEMAS}/format.yaml" - _ext_schema_fields = [] # exclude "$schema" added on each sub-deserialize (too verbose, only for reference) mediaType = MediaType(default=ContentType.TEXT_PLAIN, example=ContentType.APP_JSON) encoding = ExtendedSchemaNode(String(), missing=drop) @@ -4807,7 +4811,8 @@ class CWLApp(PermissiveMappingSchema): class CWL(CWLBase, CWLApp): - _sort_first = ["cwlVersion", "id", "class"] + _schema = CWL_SCHEMA_URL + _sort_first = ["$schema", "cwlVersion", "id", "class"] class Unit(ExtendedMappingSchema): @@ -4936,7 +4941,7 @@ class ResultReferenceList(ExtendedSequenceSchema): class ResultData(OneOfKeywordSchema): - _schema = f"{OGC_API_PROC_PART1_SCHEMAS}/result.yaml" + _schema = f"{OGC_API_PROC_PART1_SCHEMAS}/inlineOrRefData.yaml" _one_of = [ # must place formatted value first since both value/format fields are simultaneously required # other classes require only one of the two, and therefore are more permissive during schema validation @@ -4953,7 +4958,7 @@ class Result(ExtendedMappingSchema): """ Result outputs obtained from a successful process job execution. """ - example_ref = f"{OGC_API_PROC_PART1_SCHEMAS}/result.yaml" + _schema = f"{OGC_API_PROC_PART1_SCHEMAS}/results.yaml" output_id = ResultData( variable="{output-id}", title="ResultData", description=( @@ -5214,9 +5219,9 @@ class CWLGraphList(ExtendedSequenceSchema): class CWLGraphBase(ExtendedMappingSchema): graph = CWLGraphList( name="$graph", description=( - "Graph definition that defines *exactly one* CWL application package represented as list. " + "Graph definition that defines *exactly one* CWL Application Package represented as list. " "Multiple definitions simultaneously deployed is NOT supported currently." - # "Graph definition that combines one or many CWL application packages within a single payload. " + # "Graph definition that combines one or many CWL Application Packages within a single payload. " # "If a single application is given (list of one item), it will be deployed as normal CWL by itself. " # "If multiple applications are defined, the first MUST be the top-most Workflow process. " # "Deployment of other items will be performed, and the full deployment will be persisted only if all are " @@ -5945,7 +5950,7 @@ class ProcessesListing(ProcessCollection, ProcessListingLinks): class MultiProcessesListing(DescriptionSchema, ProcessesListing, ProvidersProcessesCollection, ProcessListingMetadata): - pass + _schema_meta_include = True class OkGetProcessesListResponse(ExtendedMappingSchema): @@ -6005,7 +6010,7 @@ class OkGetProcessInfoResponse(ExtendedMappingSchema): class OkGetProcessPackageSchema(ExtendedMappingSchema): header = ResponseHeaders() - body = NoContent() + body = CWL() class OkGetProcessPayloadSchema(ExtendedMappingSchema):