Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

job execution results #713

Merged
merged 80 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
b3b74de
[wip] update docs about prefer header vs mode/transmissionMode/respon…
fmigneault Sep 10, 2024
bc3c924
[wip] update docs job exec and responses
fmigneault Sep 11, 2024
06fb1cb
[Wip] more docs updates and examples for job results
fmigneault Sep 12, 2024
a31d344
[wip] job requests outputs
fmigneault Sep 12, 2024
cce7cf4
Merge remote-tracking branch 'origin/master' into job-prefer
fmigneault Sep 12, 2024
1e87460
Merge remote-tracking branch 'origin/master' into job-prefer
fmigneault Sep 12, 2024
e42c0a7
[wip] more docs updates for job exec results
fmigneault Sep 13, 2024
f825cb3
add missing doc results combinations
fmigneault Sep 16, 2024
4d65b5c
doc note reword
fmigneault Sep 16, 2024
b5028c6
fix docs formatting/invalid references
fmigneault Sep 16, 2024
5c3cf1b
uniform casing of doc header
fmigneault Sep 16, 2024
8eeb1de
more docs updates + examples
fmigneault Sep 16, 2024
dbc6130
add doc details about content link headers
fmigneault Sep 16, 2024
a8cd906
setup tests for job exec mode/results
fmigneault Sep 16, 2024
3c1e4ca
fix docs invalid combinations of prefer/response + more docs precisio…
fmigneault Sep 17, 2024
2b57583
[wip] define test combinations for some prefer/return combinations
fmigneault Sep 17, 2024
a8ea2f3
more docs clarifications about job outputs/results
fmigneault Sep 18, 2024
c54f113
[wip] more test combinations for job results/outputs responses
fmigneault Sep 18, 2024
b25385c
[wip] job results representation negotiation
fmigneault Sep 19, 2024
46cccee
[wip] more job result tests + validate accept header on job submit + …
fmigneault Sep 20, 2024
8624a90
[wip] setup multipart function
fmigneault Sep 20, 2024
884abf1
[wip] multipart result + typing fixes
fmigneault Sep 20, 2024
fddb3b3
[wip] more setup of job results with multipart
fmigneault Sep 21, 2024
d5d3a21
[wip] generalize job result value/href resolution for single/multi/ar…
fmigneault Sep 23, 2024
bbafc39
[wip] fix invalid no-content multipart response content creation usin…
fmigneault Sep 24, 2024
1d87f58
use temmporary requests-toolbelt crim-ca/patch-multipart branch to fi…
fmigneault Sep 24, 2024
fc75d93
fix multipart generation - WIP tests results checks
fmigneault Sep 25, 2024
d050cd3
[wip] multipart result tests - add content-disposition and content-lo…
fmigneault Sep 28, 2024
26d399d
[wip] job document response with inline conversion of data/link outputs
fmigneault Oct 1, 2024
aadc2ed
job response for links-only with raw representation
fmigneault Oct 2, 2024
bf2b539
[wip] update tests and docs for expected handling of multi-output all…
fmigneault Oct 2, 2024
6506295
update tests
fmigneault Oct 2, 2024
2abe870
[wip] FIXME note job response for no-content multi-link handling
fmigneault Oct 2, 2024
4ea918a
[wip] job multi-results as links inline output transitionMode conversion
fmigneault Oct 2, 2024
2946791
job multi-results as links inline output transitionMode conversion
fmigneault Oct 2, 2024
8968c0b
setup more tests for special case of JSON for both output tranmission…
fmigneault Oct 2, 2024
28c9d77
[wip] more updates for multipart inline convert of data/link represen…
fmigneault Oct 3, 2024
521d634
fix lookup strategy of value/ref without using hardcoded key names
fmigneault Oct 3, 2024
eabca51
remove unused var
fmigneault Oct 3, 2024
dc749de
[wip] more test cases to handle for job results
fmigneault Oct 3, 2024
1ef76d3
[wip] refector job results to resolve representation after extracting…
fmigneault Oct 4, 2024
7705c9b
fix json multipart embedded contents without newline
fmigneault Oct 4, 2024
4d3a89a
job response refactor to handle single-output with similar strategy a…
fmigneault Oct 5, 2024
4e617c4
more job response handling/tests working
fmigneault Oct 5, 2024
262ebd9
all job return working
fmigneault Oct 8, 2024
55c555a
fix workflow tests + fix job results generation for directory referen…
fmigneault Oct 8, 2024
4a65359
fix parsing of prefer header with comma/semi-column variants
fmigneault Oct 8, 2024
9eac571
handle backward compatible output transmissionMode lookup
fmigneault Oct 8, 2024
545de42
[wip] update tests with new job return behaviors
fmigneault Oct 8, 2024
b0b61c9
fix resolution of S3 references from job response returns
fmigneault Oct 8, 2024
4ad2c76
fix WPS execute dispatch to job results response
fmigneault Oct 8, 2024
fb93ef5
fix OGC-API dispatch in workflow using literals
fmigneault Oct 8, 2024
166f321
fix CLI to align with job return behavior
fmigneault Oct 8, 2024
cf09163
fix output auto-ref workflow
fmigneault Oct 8, 2024
4c6c2b6
fix prefer header parsing
fmigneault Oct 8, 2024
8dda7cb
fix CLI with output from Link headers with explicit param
fmigneault Oct 8, 2024
192bf48
fix more tests assuming old behaviors
fmigneault Oct 8, 2024
8dbeb4f
fix more tests
fmigneault Oct 8, 2024
20fc826
linting fixes
fmigneault Oct 8, 2024
2292b14
Merge branch 'master' into job-prefer
fmigneault Oct 8, 2024
bfc4e2f
Merge branch 'master' into job-prefer
fmigneault Oct 9, 2024
a7e5792
fix docs lint
fmigneault Oct 9, 2024
1669b5b
fix doc links
fmigneault Oct 9, 2024
b7c0bc5
update conformance and changelog
fmigneault Oct 9, 2024
78e039d
update docs about transmissionMode and filtered outputs (relates to #…
fmigneault Oct 9, 2024
ef7670e
add --output-filter CLI option (relates to #380)
fmigneault Oct 9, 2024
cd05524
update Preference-Applied header with requested Prefer return if appl…
fmigneault Oct 9, 2024
f69406d
update missing docstring args
fmigneault Oct 9, 2024
ae9e7a3
fix linting
fmigneault Oct 9, 2024
cd0d474
ignore false-postivie doc8 D000 for indirect references (relates to h…
fmigneault Oct 9, 2024
b3a7ca1
update doc8>=1.1.2 to allow 'ignore-path-errors' option
fmigneault Oct 9, 2024
8078839
update sphinx>7 in docs requirements to avoid conflict resolution aga…
fmigneault Oct 9, 2024
3ae49ed
unpin sphinx_rtd_theme to allow sphinx/docutils compatibility
fmigneault Oct 9, 2024
2eca439
docs and linting fixes
fmigneault Oct 9, 2024
6f7aba7
fix CLI test
fmigneault Oct 10, 2024
2ce1c82
Merge branch 'master' into job-prefer
fmigneault Oct 11, 2024
d1ab8ba
fix output results S3 bucket missing output ID in path
fmigneault Oct 11, 2024
0fef2b6
fix missing test combinations/coverage for prefer header execution mo…
fmigneault Oct 11, 2024
a4544a3
fix output result locations for S3 storage
fmigneault Oct 11, 2024
ae469c7
remove fixme comment from review
fmigneault Oct 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,45 @@ Changes

Changes:
--------
- No change.

Fixes:
------
- Add support of ``response: raw`` execution request body parameter as alternative to ``response: document``,
which allows directly returning the result contents or ``Link`` headers rather then embedding them in a `JSON`
response (fixes `#376 <https://github.com/crim-ca/weaver/issues/376>`_).
- Add support of ``Prefer: return=minimal`` and ``Prefer: return=representation`` header as alternative method
to request the ``response: document`` and ``response: raw`` parameters
(fixes `#414 <https://github.com/crim-ca/weaver/issues/414>`_).
Minor differences exist according to supplied ``transmissionMode`` and the original data/link results.
See `Process Execution <file:///home/francis/dev/weaver/docs/build/html/processes.html#proc-op-execute>`_
documentation for details.
- Add support of ``outputs`` execution request body parameter to filter returned outputs from
the ``GET /jobs/{jobId}/results`` (async) or returned directly (sync) from ``POST /processes/{processId}/execution``
(fixes `#380 <https://github.com/crim-ca/weaver/issues/380>_`).
- Add support of ``Accept: multipart/*`` and ``Accept: multipart/mixed`` when submitting an execution to obtain
the results as multiple parts embedded within the response contents. Parts are represented with their default
data/link representation, unless overridden by corresponding ``transmissionMode`` per output ID.
- Add ``output_links``/``-oL``/``--output-link`` parameter to Python client and CLI to retrieve ``Link`` headers
as `Job` results. Due to the multiple ``Link`` headers returned by `Job` results, this cannot be performed
automatically without the assumption of which ``rel`` links correspond to actual output IDs to extract.
- Add ``output_filter``/``--oF``/``--output-filter`` parameter to Python client and CLI to indicate
any ``outputs`` to be filtered when submitting the `Process` execution.
- Update ``Preference-Applied`` header reported by execution responses to
include ``return=minimal`` or ``return=representation`` as applicable by the requested ``Prefer`` header.
- Update documentation with a mapping of *Process Execution Results* according to
submitted ``response`` body parameter (*OGC API - Processes v1.0*),
the ``Prefer: return`` header (*OGC API - Processes v2.0*), the requested ``Accept`` header,
and any relevant ``transmissionMode`` request body overrides per filtered ``outputs``.

Fixes:
------
- Fix ``GET /jobs/{jobId}/inputs`` contents to correctly return the submitted ``outputs`` definition
for `Process` execution (fixes `#715 <https://github.com/crim-ca/weaver/issues/715>`_).
- Fix missing ``Link`` header with ``rel: monitor`` relationship in the created `Job` responses
(fixes `#596 <https://github.com/crim-ca/weaver/issues/596>`_).
- Fix missing ``/rec/core/link-header`` definition in ``GET /conformance`` response reporting
that ``Link`` headers are returned for corresponding references of a given request
(fixes `#378 <https://github.com/crim-ca/weaver/issues/378>`_).
- Fix ``transmissionMode: value`` that was ignored for ``response: document`` if the output was represented by default
as a *complex* file URL, and ``transmissionMode: reference`` that was ignored if the output was *literal* data.
The ``transmissionMode`` will now return the appropriate inline data or URL as requested.
- Add missing conformance and requirement references for *OGC API - Processes - Part 2: DRU*
(fixes `##620 <https://github.com/crim-ca/weaver/issues/620>`_).
- Add the appropriate HTTP error type to respect ``/conf/dru/deploy/unsupported-content-type``
Expand Down
34 changes: 31 additions & 3 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
max-width: none;
}

/* force code-table to align their cells to top (align code line numbers between columns)
/* force table-code to align their cells to top (align code line numbers between columns)
note: class attribute must be applied to match this specific type of table
*/
.code-table tbody tr td {
.table-code tbody tr td {
vertical-align: top !important;

/* max-width: min-content; */
Expand All @@ -21,11 +21,39 @@
div[class^="highlight"] {
max-width: min-content;
}
.code-table tbody tr td div {
.table-code tbody tr td div {
max-width: none !important;
}
*/

.table-exec-results thead,
.table-exec-results tbody {
vertical-align: top !important;
}

.table-exec-results thead {
background-color: #CCCCCC;
}

.table-exec-results thead tr:nth-child(1) > th:nth-child(1),
.table-exec-results thead tr:nth-child(1) > th:nth-child(2) {
border-bottom-color: #777777 !important;
}

.table-exec-results tr:nth-child(1) > th:nth-child(2),
.table-exec-results tr:nth-child(1) > th:nth-child(3),
.table-exec-results tr:nth-child(2) > th:nth-child(2),
.table-exec-results td:nth-child(2),
.table-exec-results td:nth-child(4) {
border-left-color: #777777 !important;
border-left-width: medium !important;
}

/* avoid unnecessary spacing causing table to be massively longer than needed */
.table-exec-results ul {
margin-bottom: 0 !important;
}

/* override table width restrictions
avoids cells trying to fit all their text single line with a slider
instead, text will wrap according to specified :widths: specifications
Expand Down
4 changes: 1 addition & 3 deletions docs/_templates/autoapi/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ This page contains reference documentation of the source code.
.. toctree::
:titlesonly:

{% for page in pages %}
{% if page.top_level_object and page.display %}
{% for page in pages|selectattr("is_top_level_object") %}
{{ page.include_path }}
{% endif %}
{% endfor %}
8 changes: 8 additions & 0 deletions docs/examples/job_execute_outputs_transmission.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"inputs": {"<...>": "<...>"},
"outputs": {
"output-default": {},
"output-by-value": {"transmissionMode": "value"},
"output-by-ref": {"transmissionMode": "reference"}
}
}
77 changes: 77 additions & 0 deletions docs/examples/job_inputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"inputs": {
"calc": "4.26 * ((C / A) ** 3.94)",
"band_a": {
"href": "https://example.com/wpsoutputs/weaver/users/23/6f197568-38f5-42f4-851c-0c56d446094c/product/T29SPC_20190601T110621_B02_10m.jp2",
"type": "image/jp2"
},
"band_c": {
"href": "https://example.com/wpsoutputs/weaver/users/23/977799a0-bf63-4406-a419-6d686c9a8fc9/product/T29SPC_20190601T110621_B04_10m.jp2",
"type": "image/jp2"
},
"name": "output"
},
"outputs": {
"result": {
"transmissionMode": "reference"
}
},
"links": [
{
"title": "Job status.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96",
"type": "application/json",
"rel": "status"
},
{
"title": "Job status generic endpoint.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96",
"type": "application/json",
"rel": "alternate"
},
{
"title": "New job submission endpoint for the corresponding process.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/execution",
"type": "application/json",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/execute"
},
{
"title": "Submitted job inputs for process execution.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96/inputs",
"type": "application/json",
"rel": "inputs"
},
{
"title": "Job outputs of successful process execution (extended outputs with metadata).",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96/outputs",
"type": "application/json",
"rel": "outputs"
},
{
"title": "Job results of successful process execution (direct output values mapping).",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96/results",
"type": "application/json",
"rel": "http://www.opengis.net/def/rel/ogc/1.0/results"
},
{
"title": "Job statistics collected following process execution.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96/statistics",
"type": "application/json",
"rel": "statistics"
},
{
"title": "List of collected job logs during process execution.",
"hreflang": "en-CA",
"href": "https://example.com/weaver/processes/calculate-band/jobs/034151ec-a87e-41ed-8ab4-8afb22b48e96/logs",
"type": "application/json",
"rel": "logs"
}
]
}
13 changes: 13 additions & 0 deletions docs/examples/job_outputs_listing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"outputs": [
{
"id": "output-file",
"href": "https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output_netcdf.nc",
"type": "application/x-netcdf"
},
{
"id": "output-data",
"value": 3.1416
}
]
}
9 changes: 9 additions & 0 deletions docs/examples/job_outputs_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"outputs": {
"output-file": {
"href": "https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output_netcdf.nc",
"type": "application/x-netcdf"
},
"output-data": 3.1416
}
}
7 changes: 7 additions & 0 deletions docs/examples/job_results_document_minimal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"output-file": {
"href": "https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output_netcdf.nc",
"type": "application/x-netcdf"
},
"output-data": 3.1416
fmigneault marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions docs/examples/job_results_raw_multi.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
HTTP/1.1 200 OK
Host: weaver.example.com
Content-Type: multipart/mixed; boundary=43003e2f205a180ace9cd34d98f911ff

--43003e2f205a180ace9cd34d98f911ff
Content-Type: application/x-netcdf
Content-ID: <output-file@f93a15be-6e16-11ea-b667-08002752172a>
Content-Location: https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output-file/output_netcdf.nc

--43003e2f205a180ace9cd34d98f911ff
Content-Type: text/plain
Content-ID: <output-data@f93a15be-6e16-11ea-b667-08002752172a>

3.1416
--43003e2f205a180ace9cd34d98f911ff--
6 changes: 6 additions & 0 deletions docs/examples/job_results_raw_single_data.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HTTP/1.1 200 OK
Host: weaver.example.com
Content-ID: <output@f93a15be-6e16-11ea-b667-08002752172a>
Content-Type: application/x-netcdf

<netcdf data>
7 changes: 7 additions & 0 deletions docs/examples/job_results_raw_single_ref.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
HTTP/1.1 204 No Content
Host: weaver.example.com
Content-Length: 0
Content-Type: application/x-netcdf
Content-ID: <output@f93a15be-6e16-11ea-b667-08002752172a>
Content-Location: https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output/output_netcdf.nc
Link: <https://example.com/wpsoutputs/f93a15be-6e16-11ea-b667-08002752172a/output/output_netcdf.nc>; rel="output"; type="application/x-netcdf"
4 changes: 4 additions & 0 deletions docs/source/appendix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ Glossary
More recent `Media-Type` naming is employed for the general use of ``Content-Type`` data representation in
multiple situations and contexts.

.. seealso::
- |iana-link|_
- |edam-link|_

OAS
OpenAPI
OpenAPI Specification (`OAS`) defines a standard, programming language-agnostic interface description for
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def doc_redirect_include(file_path):

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
html_logo = "../_static/crim.png"

# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
Expand Down Expand Up @@ -313,7 +313,7 @@ def doc_redirect_include(file_path):
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
# html_search_language = 'en'
html_search_language = "en"

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
Expand Down
12 changes: 6 additions & 6 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ they are optional and which default value or operation is applied in each situat
default. Explicit response media-types can be requested in both cases using either an explicit ``Accept`` header
of the desired media-type, or their corresponding ``f`` query format.
|
| This option is Only applicable when |weaver-wps-restapi-html|_ is enabled. Otherwise, :term:`JSON` responses are
| This option is only applicable when |weaver-wps-restapi-html|_ is enabled. Otherwise, :term:`JSON` responses are
always employed by default.

.. versionadded:: 5.7.0
Expand Down Expand Up @@ -695,7 +695,7 @@ and all corresponding functionalities, including `API` endpoints, will be disabl

- | ``weaver.quotation_docker_image = <image-reference>`` [:class:`str`]
|
| Specifies the :term:`Docker` image used as |quote-estimator|_ to evaluate a :term:`Quote`
| Specifies the :term:`Docker` image used for :ref:`quote_estimation` to evaluate a :term:`Quote`
for the eventual :term:`Process` execution.
|
| Required if ``weaver.quotation`` is enabled.
Expand All @@ -708,7 +708,7 @@ and all corresponding functionalities, including `API` endpoints, will be disabl

- | ``weaver.quotation_docker_username = <username>`` [:class:`str`]
|
| Username to employ for authentication when retrieving the :term:`Docker` image used as |quote-estimator|_.
| Username to employ for authentication when retrieving the :term:`Docker` image used as :ref:`quote_estimation`.
|
| Only required if the :term:`Docker` image is not accessible publicly or already
provided through some other means when requested by the :term:`Docker` daemon.
Expand All @@ -722,7 +722,7 @@ and all corresponding functionalities, including `API` endpoints, will be disabl

- | ``weaver.quotation_docker_password = <username>`` [:class:`str`]
|
| Password to employ for authentication when retrieving the :term:`Docker` image used as |quote-estimator|_.
| Password to employ for authentication when retrieving the :term:`Docker` image used as :ref:`quote_estimation`.
|
| Only required if the :term:`Docker` image is not accessible publicly or already
provided through some other means when requested by the :term:`Docker` daemon.
Expand All @@ -739,7 +739,7 @@ and all corresponding functionalities, including `API` endpoints, will be disabl
|
| Currency code in `ISO-4217 <https://www.iso.org/iso-4217-currency-codes.html>`_ format used by default.
|
| It is up to the specified |quote-estimator|_ algorithm defined by ``weaver.quotation_docker_image`` and
| It is up to the specified :ref:`quote_estimation` algorithm defined by ``weaver.quotation_docker_image`` and
employed by the various :term:`Process` to ensure that the returned :ref:`quote_estimation` cost makes
sense according to the specified default currency.
|
Expand Down Expand Up @@ -803,7 +803,7 @@ and all corresponding functionalities, including `API` endpoints, will be disabl

- | ``weaver.quotation_currency_token = <API access token>`` [:class:`str`]
|
| Password to employ for authentication when retrieving the :term:`Docker` image used as |quote-estimator|_.
| Password to employ for authentication when retrieving the :term:`Docker` image used as :ref:`quote_estimation`.
|
| Only required if the :term:`Docker` image is not accessible publicly or already
provided through some other means when requested by the :term:`Docker` daemon.
Expand Down
Loading
Loading