Skip to content

Commit 42302cc

Browse files
BSander Endpoint and Clients
1 parent c57a799 commit 42302cc

File tree

5 files changed

+326
-1
lines changed

5 files changed

+326
-1
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from http import HTTPStatus
2+
from typing import Any, Optional, Union, cast
3+
4+
import httpx
5+
6+
from ...client import AuthenticatedClient, Client
7+
from ...types import Response, UNSET
8+
from ... import errors
9+
10+
from ...models.body_analyze_simulation_omex import BodyAnalyzeSimulationOmex
11+
from ...models.http_validation_error import HTTPValidationError
12+
from typing import cast
13+
14+
15+
def _get_kwargs(
16+
*,
17+
body: BodyAnalyzeSimulationOmex,
18+
) -> dict[str, Any]:
19+
headers: dict[str, Any] = {}
20+
21+
_kwargs: dict[str, Any] = {
22+
"method": "post",
23+
"url": "/core/simulation/analyze",
24+
}
25+
26+
_kwargs["files"] = body.to_multipart()
27+
28+
_kwargs["headers"] = headers
29+
return _kwargs
30+
31+
32+
def _parse_response(
33+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
34+
) -> Optional[Union[HTTPValidationError, str]]:
35+
if response.status_code == 200:
36+
response_200 = response.text
37+
return response_200
38+
if response.status_code == 422:
39+
response_422 = HTTPValidationError.from_dict(response.json())
40+
41+
return response_422
42+
if client.raise_on_unexpected_status:
43+
raise errors.UnexpectedStatus(response.status_code, response.content)
44+
else:
45+
return None
46+
47+
48+
def _build_response(
49+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
50+
) -> Response[Union[HTTPValidationError, str]]:
51+
return Response(
52+
status_code=HTTPStatus(response.status_code),
53+
content=response.content,
54+
headers=response.headers,
55+
parsed=_parse_response(client=client, response=response),
56+
)
57+
58+
59+
def sync_detailed(
60+
*,
61+
client: Union[AuthenticatedClient, Client],
62+
body: BodyAnalyzeSimulationOmex,
63+
) -> Response[Union[HTTPValidationError, str]]:
64+
"""Analyze a simulation, and determine what containers should be built to execute it.
65+
66+
Resulting container definition file
67+
68+
Args:
69+
body (BodyAnalyzeSimulationOmex):
70+
71+
Raises:
72+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
73+
httpx.TimeoutException: If the request takes longer than Client.timeout.
74+
75+
Returns:
76+
Response[Union[HTTPValidationError, str]]
77+
"""
78+
79+
kwargs = _get_kwargs(
80+
body=body,
81+
)
82+
83+
response = client.get_httpx_client().request(
84+
**kwargs,
85+
)
86+
87+
return _build_response(client=client, response=response)
88+
89+
90+
def sync(
91+
*,
92+
client: Union[AuthenticatedClient, Client],
93+
body: BodyAnalyzeSimulationOmex,
94+
) -> Optional[Union[HTTPValidationError, str]]:
95+
"""Analyze a simulation, and determine what containers should be built to execute it.
96+
97+
Resulting container definition file
98+
99+
Args:
100+
body (BodyAnalyzeSimulationOmex):
101+
102+
Raises:
103+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
104+
httpx.TimeoutException: If the request takes longer than Client.timeout.
105+
106+
Returns:
107+
Union[HTTPValidationError, str]
108+
"""
109+
110+
return sync_detailed(
111+
client=client,
112+
body=body,
113+
).parsed
114+
115+
116+
async def asyncio_detailed(
117+
*,
118+
client: Union[AuthenticatedClient, Client],
119+
body: BodyAnalyzeSimulationOmex,
120+
) -> Response[Union[HTTPValidationError, str]]:
121+
"""Analyze a simulation, and determine what containers should be built to execute it.
122+
123+
Resulting container definition file
124+
125+
Args:
126+
body (BodyAnalyzeSimulationOmex):
127+
128+
Raises:
129+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
130+
httpx.TimeoutException: If the request takes longer than Client.timeout.
131+
132+
Returns:
133+
Response[Union[HTTPValidationError, str]]
134+
"""
135+
136+
kwargs = _get_kwargs(
137+
body=body,
138+
)
139+
140+
response = await client.get_async_httpx_client().request(**kwargs)
141+
142+
return _build_response(client=client, response=response)
143+
144+
145+
async def asyncio(
146+
*,
147+
client: Union[AuthenticatedClient, Client],
148+
body: BodyAnalyzeSimulationOmex,
149+
) -> Optional[Union[HTTPValidationError, str]]:
150+
"""Analyze a simulation, and determine what containers should be built to execute it.
151+
152+
Resulting container definition file
153+
154+
Args:
155+
body (BodyAnalyzeSimulationOmex):
156+
157+
Raises:
158+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
159+
httpx.TimeoutException: If the request takes longer than Client.timeout.
160+
161+
Returns:
162+
Union[HTTPValidationError, str]
163+
"""
164+
165+
return (
166+
await asyncio_detailed(
167+
client=client,
168+
body=body,
169+
)
170+
).parsed

compose_api/api/client/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Contains all the data models used in inputs/outputs"""
22

3+
from .body_analyze_simulation_omex import BodyAnalyzeSimulationOmex
34
from .body_run_simulation import BodyRunSimulation
45
from .check_health_health_get_response_check_health_health_get import CheckHealthHealthGetResponseCheckHealthHealthGet
56
from .hpc_run import HpcRun
@@ -11,6 +12,7 @@
1112
from .validation_error import ValidationError
1213

1314
__all__ = (
15+
"BodyAnalyzeSimulationOmex",
1416
"BodyRunSimulation",
1517
"CheckHealthHealthGetResponseCheckHealthHealthGet",
1618
"HpcRun",
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from collections.abc import Mapping
2+
from typing import Any, TypeVar, Optional, BinaryIO, TextIO, TYPE_CHECKING, Generator
3+
4+
from attrs import define as _attrs_define
5+
from attrs import field as _attrs_field
6+
import json
7+
from .. import types
8+
9+
from ..types import UNSET, Unset
10+
11+
from ..types import File, FileTypes
12+
from io import BytesIO
13+
14+
15+
T = TypeVar("T", bound="BodyAnalyzeSimulationOmex")
16+
17+
18+
@_attrs_define
19+
class BodyAnalyzeSimulationOmex:
20+
"""
21+
Attributes:
22+
uploaded_file (File):
23+
"""
24+
25+
uploaded_file: File
26+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
27+
28+
def to_dict(self) -> dict[str, Any]:
29+
uploaded_file = self.uploaded_file.to_tuple()
30+
31+
field_dict: dict[str, Any] = {}
32+
field_dict.update(self.additional_properties)
33+
field_dict.update({
34+
"uploaded_file": uploaded_file,
35+
})
36+
37+
return field_dict
38+
39+
def to_multipart(self) -> types.RequestFiles:
40+
files: types.RequestFiles = []
41+
42+
files.append(("uploaded_file", self.uploaded_file.to_tuple()))
43+
44+
for prop_name, prop in self.additional_properties.items():
45+
files.append((prop_name, (None, str(prop).encode(), "text/plain")))
46+
47+
return files
48+
49+
@classmethod
50+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
51+
d = dict(src_dict)
52+
uploaded_file = File(payload=BytesIO(d.pop("uploaded_file")))
53+
54+
body_analyze_simulation_omex = cls(
55+
uploaded_file=uploaded_file,
56+
)
57+
58+
body_analyze_simulation_omex.additional_properties = d
59+
return body_analyze_simulation_omex
60+
61+
@property
62+
def additional_keys(self) -> list[str]:
63+
return list(self.additional_properties.keys())
64+
65+
def __getitem__(self, key: str) -> Any:
66+
return self.additional_properties[key]
67+
68+
def __setitem__(self, key: str, value: Any) -> None:
69+
self.additional_properties[key] = value
70+
71+
def __delitem__(self, key: str) -> None:
72+
del self.additional_properties[key]
73+
74+
def __contains__(self, key: str) -> bool:
75+
return key in self.additional_properties

compose_api/api/routers/core.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
from pathlib import Path
44

55
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, UploadFile
6-
from starlette.responses import FileResponse
6+
from starlette.responses import FileResponse, PlainTextResponse
77

8+
from compose_api.btools.bsander.bsandr_utils.input_types import (
9+
ContainerizationEngine,
10+
ContainerizationTypes,
11+
ProgramArguments,
12+
)
13+
from compose_api.btools.bsander.execution import execute_bsander
814
from compose_api.common.gateway.models import Namespace, RouterConfig, ServerMode
915
from compose_api.common.ssh.ssh_service import get_ssh_service
1016
from compose_api.config import get_settings
@@ -143,6 +149,41 @@ async def submit_simulation(background_tasks: BackgroundTasks, uploaded_file: Up
143149
# raise HTTPException(status_code=500, detail=str(e)) from e
144150

145151

152+
@config.router.post(
153+
path="/simulation/analyze",
154+
response_class=PlainTextResponse,
155+
description="Resulting container definition file",
156+
operation_id="analyze-simulation-omex",
157+
tags=["Simulations"],
158+
summary="Analyze a simulation, and determine what containers should be built to execute it.",
159+
)
160+
async def analyze_simulation(uploaded_file: UploadFile) -> str:
161+
with tempfile.TemporaryDirectory() as tmp_dir:
162+
contents = await uploaded_file.read()
163+
uploaded_file_path = f"{tmp_dir}/{uploaded_file.filename}"
164+
with open(uploaded_file_path, "wb") as fh:
165+
fh.write(contents)
166+
singularity_rep, experiment_dep = execute_bsander(
167+
ProgramArguments(
168+
input_file_path=uploaded_file_path,
169+
output_dir=tmp_dir,
170+
containerization_type=ContainerizationTypes.SINGLE,
171+
containerization_engine=ContainerizationEngine.APPTAINER,
172+
passlist_entries=[
173+
"pypi::git+https://github.com/biosimulators/bspil-basico.git@initial_work",
174+
"pypi::cobra",
175+
"pypi::tellurium",
176+
"pypi::copasi-basico",
177+
"pypi::smoldyn",
178+
"pypi::numpy",
179+
"pypi::matplotlib",
180+
"pypi::scipy",
181+
],
182+
)
183+
)
184+
return singularity_rep.representation
185+
186+
146187
@config.router.get(
147188
path="/simulation/run/status",
148189
response_model=HpcRun,

compose_api/api/spec/openapi_3_1_0_generated.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ paths:
2828
application/json:
2929
schema:
3030
$ref: '#/components/schemas/HTTPValidationError'
31+
/core/simulation/analyze:
32+
post:
33+
tags:
34+
- Simulations
35+
summary: Analyze a simulation, and determine what containers should be built
36+
to execute it.
37+
description: Resulting container definition file
38+
operationId: analyze-simulation-omex
39+
requestBody:
40+
content:
41+
multipart/form-data:
42+
schema:
43+
$ref: '#/components/schemas/Body_analyze-simulation-omex'
44+
required: true
45+
responses:
46+
'200':
47+
description: Successful Response
48+
content:
49+
text/plain:
50+
schema:
51+
type: string
52+
'422':
53+
description: Validation Error
54+
content:
55+
application/json:
56+
schema:
57+
$ref: '#/components/schemas/HTTPValidationError'
3158
/core/simulation/run/status:
3259
get:
3360
tags:
@@ -138,6 +165,16 @@ paths:
138165
title: Response Get Version Version Get
139166
components:
140167
schemas:
168+
Body_analyze-simulation-omex:
169+
properties:
170+
uploaded_file:
171+
type: string
172+
format: binary
173+
title: Uploaded File
174+
type: object
175+
required:
176+
- uploaded_file
177+
title: Body_analyze-simulation-omex
141178
Body_run-simulation:
142179
properties:
143180
uploaded_file:

0 commit comments

Comments
 (0)