Skip to content

Commit f8847eb

Browse files
committed
adds missing references tutorials
1 parent 735d921 commit f8847eb

File tree

2 files changed

+747
-0
lines changed

2 files changed

+747
-0
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
# Basic Tutorial
2+
3+
4+
5+
## Installation
6+
Install the python client and check the installation as follows:
7+
8+
9+
```python
10+
import importlib
11+
if importlib.util.find_spec('osparc') is not None:
12+
! pip install osparc
13+
! python -c "import osparc; print(osparc.__version__)"
14+
```
15+
16+
## Setup
17+
18+
To setup the client, we need to provide a username and password to the configuration. These can be obtained in the UI under [Preferences > API Settings > API Keys](https://docs.osparc.io/#/docs/platform_introduction/user_setup/security_details?id=generating-o%c2%b2s%c2%b2parc-tokens). Use the *API key* as username and the *API secret* as password. For security reasons, you should not write these values in your script but instead set them up via environment variables or read them from a separate file. In this example, we use environment variables which will be referred to as "OSPARC_API_KEY" and "OSPARC_API_SECRET" for the rest of the tutorial.
19+
20+
21+
```python
22+
23+
import os
24+
from osparc import Configuration
25+
26+
cfg = Configuration(
27+
host=os.environ["OSPARC_API_HOST"],
28+
username=os.environ["OSPARC_API_KEY"],
29+
password=os.environ["OSPARC_API_SECRET"],
30+
)
31+
print(cfg.host)
32+
33+
```
34+
35+
The configuration can now be used to create an instance of the API client. The API client is responsible of the communication with the osparc platform
36+
37+
38+
The functions in the [osparc API](https://api.osparc.io/dev/doc#/) are grouped into sections such as *meta*, *users*, *files* or *solvers*. Each section address a different resource of the platform.
39+
40+
41+
42+
For example, the *users* section includes functions about the user (i.e. you) and can be accessed initializing a ``UsersApi``:
43+
44+
45+
```python
46+
from osparc import ApiClient, UsersApi
47+
48+
with ApiClient(cfg) as api_client:
49+
50+
users_api = UsersApi(api_client)
51+
52+
profile = users_api.get_my_profile()
53+
print(profile)
54+
55+
#
56+
# {'first_name': 'foo',
57+
# 'gravatar_id': 'aa33fssec77ea434c2ea4fb92d0fd379e',
58+
# 'groups': {'all': {'description': 'all users',
59+
# 'gid': '1',
60+
# 'label': 'Everyone'},
61+
# 'me': {'description': 'primary group',
62+
# 'gid': '2',
63+
# 'label': 'foo'},
64+
# 'organizations': []},
65+
# 'last_name': '',
66+
# 'login': 'foo@itis.swiss',
67+
# 'role': 'USER'}
68+
#
69+
```
70+
71+
## Solvers Workflow
72+
73+
The osparc API can be used to execute any computational service published in the platform. This means that any computational service listed in the UI under the [Services Tab](https://docs.osparc.io/#/docs/platform_introduction/services) is accessible from the API. Note that computational services are denoted as *solvers* in the API for convenience, but they refer to the same concept.
74+
75+
76+
Let's use the sleepers computational service to illustrate a typical workflow. The sleepers computational service is a very basic service that simply waits (i.e. *sleeps*) a given time before producing some outputs. It takes as input one natural number, an optional text file input that contains another natural number and a boolean in the form of a checkbox. It also provides two outputs: one natural number and a file containing a single natural number.
77+
78+
79+
```python
80+
import time
81+
from pathlib import Path
82+
from zipfile import ZipFile
83+
from tempfile import TemporaryDirectory
84+
85+
import osparc
86+
87+
Path("file_with_number.txt").write_text("3")
88+
89+
with osparc.ApiClient(cfg) as api_client:
90+
91+
files_api = osparc.FilesApi(api_client)
92+
input_file: osparc.File = files_api.upload_file(file="file_with_number.txt")
93+
94+
solvers_api = osparc.SolversApi(api_client)
95+
solver: osparc.Solver = solvers_api.get_solver_release(
96+
"simcore/services/comp/itis/sleeper", "2.1.6"
97+
)
98+
99+
job: osparc.Job = solvers_api.create_job(
100+
solver.id,
101+
solver.version,
102+
osparc.JobInputs(
103+
{
104+
"input_4": 2,
105+
"input_3": "false",
106+
"input_2": 3,
107+
"input_1": input_file,
108+
}
109+
),
110+
)
111+
112+
status: osparc.JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)
113+
while not status.stopped_at:
114+
time.sleep(3)
115+
status = solvers_api.inspect_job(solver.id, solver.version, job.id)
116+
print("Solver progress", f"{status.progress}/100", flush=True)
117+
assert status.state == "SUCCESS"
118+
#
119+
# Solver progress 0/100
120+
# Solver progress 100/100
121+
122+
outputs: osparc.JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)
123+
124+
print(f"Job {outputs.job_id} got these results:")
125+
for output_name, result in outputs.results.items():
126+
print(output_name, "=", result)
127+
128+
#
129+
# Job 19fc28f7-46fb-4e96-9129-5e924801f088 got these results:
130+
#
131+
# output_1 = {'checksum': '859fda0cb82fc4acb4686510a172d9a9-1',
132+
# 'content_type': 'text/plain',
133+
# 'filename': 'single_number.txt',
134+
# 'id': '9fb4f70e-3589-3e9e-991e-3059086c3aae'}
135+
# output_2 = 4.0
136+
137+
logfile_path: str = solvers_api.get_job_output_logfile(
138+
solver.id, solver.version, job.id
139+
)
140+
zip_path = Path(logfile_path)
141+
142+
with TemporaryDirectory() as tmp_dir:
143+
with ZipFile(f"{zip_path}") as fzip:
144+
fzip.extractall(tmp_dir)
145+
logfiles = list(Path(tmp_dir).glob("*.log*"))
146+
print("Unzipped", logfiles[0], "contains:\n", logfiles[0].read_text())
147+
#
148+
# Unzipped extracted/sleeper_2.0.2.logs contains:
149+
# 2022-06-01T18:15:00.405035847+02:00 Entrypoint for stage production ...
150+
# 2022-06-01T18:15:00.421279969+02:00 User : uid=0(root) gid=0(root) groups=0(root)
151+
# 2022-06-01T18:15:00.421560331+02:00 Workdir : /home/scu
152+
# ...
153+
# 2022-06-01T18:15:00.864550043+02:00
154+
# 2022-06-01T18:15:03.923876794+02:00 Will sleep for 3 seconds
155+
# 2022-06-01T18:15:03.924473521+02:00 [PROGRESS] 1/3...
156+
# 2022-06-01T18:15:03.925021846+02:00 Remaining sleep time 0.9999995231628418
157+
# 2022-06-01T18:15:03.925558026+02:00 [PROGRESS] 2/3...
158+
# 2022-06-01T18:15:03.926103062+02:00 Remaining sleep time 0.9999985694885254
159+
# 2022-06-01T18:15:03.926643184+02:00 [PROGRESS] 3/3...
160+
# 2022-06-01T18:15:03.933544384+02:00 Remaining sleep time 0.9999983310699463
161+
162+
download_path: str = files_api.download_file(file_id=outputs.results["output_1"].id)
163+
print(Path(download_path).read_text())
164+
#
165+
# 7
166+
167+
```
168+
169+
The script above
170+
171+
1. Uploads a file ``file_with_number.txt``
172+
2. Selects version ``2.0.2`` of the ``sleeper``
173+
3. Runs the ``sleeper`` and provides a reference to the uploaded file and other values as input parameters
174+
4. Monitors the status of the solver while it is running in the platform
175+
5. When the execution completes, it checks the outputs
176+
6. The logs are downloaded, unzipped and saved to a new ```extracted``` directory
177+
7. One of the outputs is a file and it is downloaded
178+
179+
180+
#### Files
181+
182+
Files used as input to solvers or produced by solvers in the platform are accessible in the **files** section and specifically with the ``FilesApi`` class.
183+
In order to use a file as input, it has to be uploaded first and the reference used in the corresponding solver's input.
184+
185+
```python
186+
files_api = FilesApi(api_client)
187+
input_file: File = files_api.upload_file(file="file_with_number.txt")
188+
189+
190+
# ...
191+
192+
193+
outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)
194+
results_file: File = outputs.results["output_1"]
195+
download_path: str = files_api.download_file(file_id=results_file.id)
196+
```
197+
198+
In the snippet above, ``input_file`` is a ``File`` reference to the uploaded file and that is passed as input to the solver. Analogously, ``results_file`` is a ``File`` produced by the solver and that can also be downloaded.
199+
200+
201+
#### Solvers, Inputs and Outputs
202+
203+
The inputs and outputs are specific for every solver. Every input/output has a name and an associated type that can be as simple as booleans, numbers, strings ... or more complex as files. You can find this information in the UI under Services Tab, selecting the service card > Information > Raw metadata. For instance, the ``sleeper`` version ``2.1.6`` has the following ``raw-metadata``:
204+
205+
```json
206+
{
207+
 "inputs": {
208+
  "input_1": {
209+
   "displayOrder": 1,
210+
   "label": "File with int number",
211+
   "description": "Pick a file containing only one integer",
212+
   "type": "data:text/plain",
213+
   "fileToKeyMap": {
214+
    "single_number.txt": "input_1"
215+
   },
216+
   "keyId": "input_1"
217+
  },
218+
  "input_2": {
219+
   "unitLong": "second",
220+
   "unitShort": "s",
221+
   "label": "Sleep interval",
222+
   "description": "Choose an amount of time to sleep in range [0-65]",
223+
   "keyId": "input_2",
224+
   "displayOrder": 2,
225+
   "type": "ref_contentSchema",
226+
   "contentSchema": {
227+
    "title": "Sleep interval",
228+
    "type": "integer",
229+
    "x_unit": "second",
230+
    "minimum": 0,
231+
    "maximum": 65
232+
   },
233+
   "defaultValue": 2
234+
  },
235+
  "input_3": {
236+
   "displayOrder": 3,
237+
   "label": "Fail after sleep",
238+
   "description": "If set to true will cause service to fail after it sleeps",
239+
   "type": "boolean",
240+
   "defaultValue": false,
241+
   "keyId": "input_3"
242+
  },
243+
  "input_4": {
244+
   "unitLong": "meter",
245+
   "unitShort": "m",
246+
   "label": "Distance to bed",
247+
   "description": "It will first walk the distance to bed",
248+
   "keyId": "input_4",
249+
   "displayOrder": 4,
250+
   "type": "ref_contentSchema",
251+
   "contentSchema": {
252+
    "title": "Distance to bed",
253+
    "type": "integer",
254+
    "x_unit": "meter"
255+
   },
256+
   "defaultValue": 0
257+
  }
258+
 }
259+
```
260+
261+
So, the inputs can be set as follows
262+
263+
```python
264+
# ...
265+
job: osparc.Job = solvers_api.create_job(
266+
solver.id,
267+
solver.version,
268+
osparc.JobInputs(
269+
{
270+
"input_4": 2,
271+
"input_3": "false",
272+
"input_2": 3,
273+
"input_1": input_file,
274+
}
275+
),
276+
)
277+
```
278+
279+
And the metadata for the outputs are
280+
281+
```json
282+
  "output_1": {
283+
   "displayOrder": 1,
284+
   "label": "File containing one random integer",
285+
   "description": "Integer is generated in range [1-9]",
286+
   "type": "data:text/plain",
287+
   "fileToKeyMap": {
288+
    "single_number.txt": "output_1"
289+
   },
290+
   "keyId": "output_1"
291+
  },
292+
  "output_2": {
293+
   "unitLong": "second",
294+
   "unitShort": "s",
295+
   "label": "Random sleep interval",
296+
   "description": "Interval is generated in range [1-9]",
297+
   "keyId": "output_2",
298+
   "displayOrder": 2,
299+
   "type": "ref_contentSchema",
300+
   "contentSchema": {
301+
    "title": "Random sleep interval",
302+
    "type": "integer",
303+
    "x_unit": "second"
304+
   }
305+
```
306+
307+
so this information determines which output corresponds to a number or a file in the following snippet
308+
309+
```python
310+
# ...
311+
312+
outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)
313+
314+
output_file = outputs.results["output_1"]
315+
number = outputs.results["output_2"]
316+
317+
assert status.state == "SUCCESS"
318+
319+
320+
assert isinstance(output_file, File)
321+
assert isinstance(number, float)
322+
323+
# output file exists
324+
assert files_api.get_file(output_file.id) == output_file
325+
326+
# can download and open
327+
download_path: str = files_api.download_file(file_id=output_file.id)
328+
assert float(Path(download_path).read_text()), "contains a random number"
329+
330+
```
331+
332+
#### Job Status
333+
334+
Once the client script triggers the solver, the solver runs in the platform and the script is freed. Sometimes, it is convenient to monitor the status of the run to see e.g. the progress of the execution or if the run was completed.
335+
336+
A solver runs in a plaforma starts a ``Job``. Using the ``solvers_api``, allows us to inspect the ``Job`` and get a ``JobStatus`` with information about its status. For instance
337+
338+
```python
339+
status: JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)
340+
while not status.stopped_at:
341+
time.sleep(3)
342+
status = solvers_api.inspect_job(solver.id, solver.version, job.id)
343+
print("Solver progress", f"{status.progress}/100", flush=True)
344+
```
345+
346+
#### Logs
347+
348+
When a solver runs, it will generate logs during execution which are then saved as .log files. Starting from the osparc Python Client version 0.5.0, The ``solvers_api`` also allows us to obtain the ``logfile_path`` associated with a particular ``Job``. This is a zip file that can then be extracted and saved. For instance
349+
350+
```python
351+
logfile_path: str = solvers_api.get_job_output_logfile(
352+
solver.id, solver.version, job.id
353+
)
354+
zip_path = Path(logfile_path)
355+
356+
extract_dir = Path("./extracted")
357+
extract_dir.mkdir()
358+
359+
with ZipFile(f"{zip_path}") as fzip:
360+
fzip.extractall(f"{extract_dir}")
361+
```
362+
363+
## References
364+
365+
- [osparc API python client] documentation
366+
- [osparc API] documentation
367+
- A full script with this tutorial: [``sleeper.py``](https://github.com/ITISFoundation/osparc-simcore/blob/master/tests/public-api/examples/sleeper.py)
368+
369+
[osparc API python client]:https://itisfoundation.github.io/osparc-simcore-clients/#/
370+
[osparc API]:https://api.osparc.io/dev/doc#/
371+
[Download as BasicTutorial_v0.5.0.ipynb](clients/python/docs/BasicTutorial_v0.5.0.ipynb ":ignore title")

0 commit comments

Comments
 (0)