Skip to content

Commit 203ed7b

Browse files
authored
MRG: Merge pull request #632 from octue/better-support-asynchronous-questions
Switch to event-driven infrastructure and improve support for asynchronous questions
2 parents 4366d66 + cbe5eb6 commit 203ed7b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+3661
-3238
lines changed

.github/workflows/python-ci.yml

+6-10
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,10 @@ on:
1414
jobs:
1515
check-semantic-version:
1616
if: "!contains(github.event.head_commit.message, 'skipci')"
17-
runs-on: ubuntu-latest
18-
steps:
19-
- uses: actions/checkout@v3
20-
with:
21-
fetch-depth: 0
22-
- uses: octue/[email protected]
23-
with:
24-
path: pyproject.toml
25-
breaking_change_indicated_by: minor
17+
uses: octue/workflows/.github/workflows/check-semantic-version.yml@main
18+
with:
19+
path: pyproject.toml
20+
breaking_change_indicated_by: minor
2621

2722
run-tests:
2823
if: "!contains(github.event.head_commit.message, 'skipci')"
@@ -68,10 +63,11 @@ jobs:
6863
run: tox -vv -e py
6964

7065
- name: Upload coverage to Codecov
71-
uses: codecov/codecov-action@v3
66+
uses: codecov/codecov-action@v4
7267
with:
7368
files: coverage.xml
7469
fail_ci_if_error: true
70+
token: ${{ secrets.CODECOV_TOKEN }}
7571

7672
test-publish:
7773
if: "!contains(github.event.head_commit.message, 'skipci')"

.github/workflows/release.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ jobs:
5959
run: tox -vv -e py
6060

6161
- name: Upload coverage to Codecov
62-
uses: codecov/codecov-action@v3
62+
uses: codecov/codecov-action@v4
6363
with:
6464
files: coverage.xml
6565
fail_ci_if_error: false
66+
token: ${{ secrets.CODECOV_TOKEN }}
6667

6768
outputs:
6869
package_version: ${{ steps.get-package-version.outputs.PACKAGE_VERSION }}

.github/workflows/update-pull-request.yml

+7-15
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,13 @@
66

77
name: update-pull-request
88

9-
on: pull_request
9+
on: [pull_request]
1010

1111
jobs:
1212
description:
13-
if: "!contains(github.event.pull_request.body, '<!--- SKIP AUTOGENERATED NOTES --->')"
14-
runs-on: ubuntu-latest
15-
steps:
16-
- uses: octue/[email protected]
17-
id: pr-description
18-
with:
19-
pull_request_url: ${{ github.event.pull_request.url }}
20-
api_token: ${{ secrets.GITHUB_TOKEN }}
21-
22-
- name: Update pull request body
23-
uses: riskledger/update-pr-description@v2
24-
with:
25-
body: ${{ steps.pr-description.outputs.pull_request_description }}
26-
token: ${{ secrets.GITHUB_TOKEN }}
13+
uses: octue/workflows/.github/workflows/generate-pull-request-description.yml@main
14+
secrets:
15+
token: ${{ secrets.GITHUB_TOKEN }}
16+
permissions:
17+
contents: read
18+
pull-requests: write

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ octue-sdk-python Application SDK for python-based apps on the Octue platform
33

44
MIT License
55

6-
Copyright (c) 2017-2022 Octue Ltd
6+
Copyright (c) 2017-2024 Octue Ltd
77

88
Permission is hereby granted, free of charge, to any person obtaining a copy
99
of this software and associated documentation files (the "Software"), to deal

docs/source/asking_questions.rst

+140-10
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,27 @@
44
Asking services questions
55
=========================
66

7-
How to ask a question
8-
=====================
7+
What is a question?
8+
===================
9+
A question is a set of data (input values and/or an input manifest) sent to a child for processing/analysis. Questions
10+
can be:
11+
12+
- **Synchronous ("ask-and-wait"):** A question whose answer is waited for in real time
13+
14+
- **Asynchronous ("fire-and-forget"):** A question whose answer is not waited for and is instead retrieved later. There
15+
are two types:
16+
17+
- **Regular:** Responses to these questions are automatically stored in an event store where they can be :ref:`retrieved using the Octue SDK <retrieving_asynchronous_answers>`
18+
19+
- **Push endpoint:** Responses to these questions are pushed to an HTTP endpoint for asynchronous handling using Octue's
20+
`django-twined <https://django-twined.readthedocs.io/en/latest/>`_ or custom logic in your own webserver.
21+
922
Questions are always asked to a *revision* of a service. You can ask a service a question if you have its
10-
:ref:`SRUID <sruid_definition>`, project name, and the necessary permissions. The question is formed of input values
11-
and/or an input manifest.
23+
:ref:`SRUID <sruid_definition>`, project name, and the necessary permissions.
24+
25+
26+
Asking a question
27+
=================
1228

1329
.. code-block:: python
1430
@@ -47,19 +63,133 @@ You can also set the following options when you call :mod:`Child.ask <octue.reso
4763
- ``subscribe_to_logs`` - if true, the child will forward its logs to you
4864
- ``allow_local_files`` - if true, local files/datasets are allowed in any input manifest you supply
4965
- ``handle_monitor_message`` - if provided a function, it will be called on any monitor messages from the child
50-
- ``record_messages_to`` – if given a path to a JSON file, messages received from the parent while it processes the question are saved to it
66+
- ``record_events`` – if ``True``, events received from the parent while it processes the question are saved to the ``Child.received_events`` property
5167
- ``save_diagnostics`` – must be one of {"SAVE_DIAGNOSTICS_OFF", "SAVE_DIAGNOSTICS_ON_CRASH", "SAVE_DIAGNOSTICS_ON"}; if turned on, allow the input values and manifest (and its datasets) to be saved by the child either all the time or just if the analysis fails
5268
- ``question_uuid`` - if provided, the question will use this UUID instead of a generated one
69+
- ``push_endpoint`` - if provided, the result and other events produced during the processing of the question will be pushed to this HTTP endpoint (a URL)
70+
- ``asynchronous`` - if ``True``, don't wait for an answer to the question (the result and other events can be :ref:`retrieved from the event store later <retrieving_asynchronous_answers>`)
5371
- ``timeout`` - how long in seconds to wait for an answer (``None`` by default - i.e. don't time out)
5472

73+
Exceptions raised by a child
74+
----------------------------
5575
If a child raises an exception while processing your question, the exception will always be forwarded and re-raised in
5676
your local service or python session. You can handle exceptions in whatever way you like.
5777

58-
If setting a timeout, bear in mind that the question has to reach the child, the child has to run its analysis on
59-
the inputs sent to it (this most likely corresponds to the dominant part of the wait time), and the answer has to be
60-
sent back to the parent. If you're not sure how long a particular analysis might take, it's best to set the timeout to
61-
``None`` initially or ask the owner/maintainer of the child for an estimate.
78+
Timeouts
79+
--------
80+
If setting a timeout, bear in mind that the question has to reach the child, the child has to run its analysis on the
81+
inputs sent to it (this will most likely make up the dominant part of the wait time), and the answer has to be sent back
82+
to the parent. If you're not sure how long a particular analysis might take, it's best to set the timeout to ``None``
83+
initially or ask the owner/maintainer of the child for an estimate.
84+
85+
86+
.. _retrieving_asynchronous_answers:
87+
88+
Retrieving answers to asynchronous questions
89+
============================================
90+
To retrieve results and other events from the processing of a question later, make sure you have the permissions to
91+
access the event store and run:
92+
93+
.. code-block:: python
94+
95+
from octue.cloud.pub_sub.bigquery import get_events
96+
97+
events = get_events(
98+
table_id="your-project.your-dataset.your-table",
99+
sender="octue/test-service:1.0.0",
100+
question_uuid="53353901-0b47-44e7-9da3-a3ed59990a71",
101+
)
102+
103+
104+
**Options**
105+
106+
- ``kind`` - Only retrieve this kind of event if present (e.g. "result")
107+
- ``include_attributes`` - If ``True``, retrieve all the events' attributes as well
108+
- ``include_backend_metadata`` - If ``True``, retrieve information about the service backend that produced the event
109+
- ``limit`` - If set to a positive integer, limit the number of events returned to this
110+
111+
112+
.. collapse:: See an example output here...
113+
114+
.. code-block:: python
115+
116+
>>> events
117+
[
118+
{
119+
"event": {
120+
"datetime": "2024-03-06T15:44:18.156044",
121+
"kind": "delivery_acknowledgement"
122+
},
123+
},
124+
{
125+
"event": {
126+
"kind": "log_record",
127+
"log_record": {
128+
"args": null,
129+
"created": 1709739861.5949728,
130+
"exc_info": null,
131+
"exc_text": null,
132+
"filename": "app.py",
133+
"funcName": "run",
134+
"levelname": "INFO",
135+
"levelno": 20,
136+
"lineno": 28,
137+
"module": "app",
138+
"msecs": 594.9728488922119,
139+
"msg": "Finished example analysis.",
140+
"name": "app",
141+
"pathname": "/workspace/example_service_cloud_run/app.py",
142+
"process": 2,
143+
"processName": "MainProcess",
144+
"relativeCreated": 8560.13798713684,
145+
"stack_info": null,
146+
"thread": 68328473233152,
147+
"threadName": "ThreadPoolExecutor-0_2"
148+
}
149+
},
150+
},
151+
{
152+
"event": {
153+
"datetime": "2024-03-06T15:46:18.167424",
154+
"kind": "heartbeat"
155+
},
156+
"attributes": {
157+
"datetime": "2024-04-11T10:46:48.236064",
158+
"uuid": "a9de11b1-e88f-43fa-b3a4-40a590c3443f",
159+
"order": "7",
160+
"question_uuid": "d45c7e99-d610-413b-8130-dd6eef46dda6",
161+
"originator": "octue/test-service:1.0.0",
162+
"sender": "octue/test-service:1.0.0",
163+
"sender_type": "CHILD",
164+
"sender_sdk_version": "0.51.0",
165+
"recipient": "octue/another-service:3.2.1"
166+
}
167+
}
168+
{
169+
"event": {
170+
"kind": "result",
171+
"output_manifest": {
172+
"datasets": {
173+
"example_dataset": {
174+
"files": [
175+
"gs://octue-sdk-python-test-bucket/example_output_datasets/example_dataset/output.dat"
176+
],
177+
"id": "419bff6b-08c3-4c16-9eb1-5d1709168003",
178+
"labels": [],
179+
"name": "divergent-strange-gharial-of-pizza",
180+
"path": "https://storage.googleapis.com/octue-sdk-python-test-bucket/example_output_datasets/example_dataset/.signed_metadata_files/divergent-strange-gharial-of-pizza",
181+
"tags": {}
182+
}
183+
},
184+
"id": "a13713ae-f207-41c6-9e29-0a848ced6039",
185+
"name": null
186+
},
187+
"output_values": [1, 2, 3, 4, 5]
188+
},
189+
},
190+
]
62191
192+
----
63193

64194
Asking multiple questions in parallel
65195
=====================================
@@ -81,7 +211,7 @@ raised and no answers are returned.
81211
82212
This method uses multithreading, allowing all the questions to be asked at once instead of one after another.
83213

84-
Options:
214+
**Options**
85215

86216
- If ``raise_errors=False`` is provided, answers are returned for all successful questions while unraised errors are
87217
returned for unsuccessful ones

docs/source/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
# General information about the project.
5252
project = "Octue SDK (Python)"
5353
author = "Octue Ltd"
54-
copyright = "2022, Octue Ltd"
54+
copyright = "2024, Octue Ltd"
5555

5656
# The version info for the project you're documenting, acts as replacement for
5757
# |version| and |release|, also used in various other places throughout the

0 commit comments

Comments
 (0)