Skip to content

Commit 269829a

Browse files
committed
add initial function workflow
1 parent 89f3caf commit 269829a

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import json
2+
import random
3+
from pathlib import Path
4+
from tempfile import TemporaryDirectory
5+
from typing import Final
6+
from uuid import UUID
7+
8+
from locust import HttpUser, task
9+
from pydantic import BaseModel, Field
10+
from pydantic_settings import BaseSettings, SettingsConfigDict
11+
from requests.auth import HTTPBasicAuth
12+
from urllib3 import PoolManager, Retry
13+
14+
_MAX_WAIT_SECONDS: Final[int] = 60
15+
16+
17+
# Perform the following setup in order to run this load test:
18+
# 1. Copy .env-devel to .env in this directory and add your osparc keys to .env.
19+
# 2. Construct a study **template** according to study_template.png. passer.py is the file next to this file.
20+
# 3. Setup the locust settings in the .env file (see https://docs.locust.io/en/stable/configuration.html#all-available-configuration-options)
21+
# run 'make test target=metamodeling/workflow.py' in your terminal and watch the magic happen 🤩
22+
23+
24+
class UserSettings(BaseSettings):
25+
model_config = SettingsConfigDict(extra="ignore")
26+
OSPARC_API_KEY: str = Field(default=...)
27+
OSPARC_API_SECRET: str = Field(default=...)
28+
29+
30+
_SOLVER_KEY = "simcore/services/comp/osparc-python-runner"
31+
_SOLVER_VERSION = "1.2.0"
32+
33+
_PYTHON_SCRIPT = """
34+
import numpy as np
35+
import pathlib as pl
36+
import json
37+
38+
def main():
39+
40+
input_json = pl.Path(os.environ["INPUT_FOLDER"]) / "function_inputs.json"
41+
object = json.load(input_json..read_text())
42+
x = object["x"]
43+
y = object["y"]
44+
45+
return np.sinc(x) * np.sinc(y)
46+
47+
48+
if __name__ == "__main__":
49+
main()
50+
"""
51+
52+
53+
class Schema(BaseModel):
54+
schema_content: dict = Field(default={})
55+
schema_class: str = Field(default="application/schema+json")
56+
57+
58+
class Function(BaseModel):
59+
function_class: str = Field(default="SOLVER")
60+
title: str
61+
description: str
62+
input_schema: Schema = Field(default=Schema())
63+
output_schema: Schema = Field(default=Schema())
64+
default_inputs: dict[str, str] = Field(default=dict())
65+
solver_key: str = Field(default=_SOLVER_KEY)
66+
solver_version: str = Field(default=_SOLVER_VERSION)
67+
68+
69+
class MetaModelingUser(HttpUser):
70+
def __init__(self, *args, **kwargs):
71+
self._user_settings = UserSettings()
72+
self._auth = HTTPBasicAuth(
73+
username=self._user_settings.OSPARC_API_KEY,
74+
password=self._user_settings.OSPARC_API_SECRET,
75+
)
76+
retry_strategy = Retry(
77+
total=4,
78+
backoff_factor=4.0,
79+
status_forcelist={429, 503, 504},
80+
allowed_methods={
81+
"DELETE",
82+
"GET",
83+
"HEAD",
84+
"OPTIONS",
85+
"PUT",
86+
"TRACE",
87+
"POST",
88+
"PATCH",
89+
"CONNECT",
90+
},
91+
respect_retry_after_header=True,
92+
raise_on_status=True,
93+
)
94+
self.pool_manager = PoolManager(retries=retry_strategy)
95+
96+
self._function_uid = None
97+
self._input_json_uuid = None
98+
self._job_uuid = None
99+
100+
super().__init__(*args, **kwargs)
101+
102+
def on_start(self) -> None:
103+
response = self.client.get("/v0/me", auth=self._auth) # fail fast
104+
response.raise_for_status()
105+
106+
@task
107+
def run_function(self):
108+
with TemporaryDirectory() as tmpdir:
109+
tmp_dir = Path(tmpdir)
110+
script = tmp_dir / "script.py"
111+
script.write_text(_PYTHON_SCRIPT)
112+
self._script_uuid = self.upload_file(script)
113+
114+
inputs = {"x": random.uniform(-10, 10), "y": random.uniform(-10, 10)}
115+
input_json = tmp_dir / "function_inputs.json"
116+
input_json.write_text(json.dumps(inputs))
117+
self._input_json_uuid = self.upload_file(input_json)
118+
119+
function = Function(
120+
title="Test function",
121+
description="Test function",
122+
default_inputs={},
123+
)
124+
response = self.client.post(
125+
"/v0/functions", json=function.model_dump(), auth=self._auth
126+
)
127+
response.raise_for_status()
128+
129+
def upload_file(self, file: Path) -> UUID:
130+
assert file.is_file()
131+
with open(f"{file.resolve()}", "rb") as f:
132+
files = {"file": f}
133+
response = self.client.put(
134+
"/v0/files/content", files=files, auth=self._auth
135+
)
136+
response.raise_for_status()
137+
file_uuid = response.json().get("id")
138+
assert file_uuid is not None
139+
return UUID(file_uuid)
140+
141+
142+
if __name__ == "__main__":
143+
function = Function(
144+
title="Test function",
145+
description="Test function",
146+
default_inputs={},
147+
)
148+
print(function.model_dump_json())

tests/performance/requirements/_base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ locust-plugins
22
parse
33
pydantic
44
pydantic-settings
5+
tenacity

0 commit comments

Comments
 (0)