|
12 | 12 |
|
13 | 13 | import json |
14 | 14 | from gufe.tokenization import JSON_HANDLER, KeyedChain |
| 15 | +from pydantic import ValidationError |
15 | 16 |
|
16 | 17 | from ..base.api import ( |
17 | 18 | GufeJSONResponse, |
|
30 | 31 | from ..settings import get_base_api_settings |
31 | 32 | from ..storage.statestore import Neo4jStore |
32 | 33 | from ..storage.objectstore import S3ObjectStore |
33 | | -from ..storage.models import TaskStatusEnum |
| 34 | +from ..storage.models import TaskStatusEnum, StrategyState |
34 | 35 | from ..models import Scope, ScopedKey |
35 | 36 | from ..security.models import TokenData, CredentialedUserIdentity |
36 | 37 |
|
@@ -393,10 +394,6 @@ def get_chemicalsystem( |
393 | 394 | ### compute |
394 | 395 |
|
395 | 396 |
|
396 | | -@router.post("/networks/{scoped_key}/strategy") |
397 | | -def set_strategy(scoped_key: str, *, strategy: dict = Body(...), scope: Scope): ... |
398 | | - |
399 | | - |
400 | 397 | @router.post("/transformations/{transformation_scoped_key}/tasks") |
401 | 398 | def create_tasks( |
402 | 399 | transformation_scoped_key, |
@@ -1180,6 +1177,148 @@ def get_task_failures( |
1180 | 1177 | return [str(sk) for sk in n4js.get_task_failures(sk)] |
1181 | 1178 |
|
1182 | 1179 |
|
| 1180 | +### strategies |
| 1181 | + |
| 1182 | + |
| 1183 | +@router.post("/networks/{network_scoped_key}/strategy") |
| 1184 | +async def set_network_strategy( |
| 1185 | + network_scoped_key, |
| 1186 | + *, |
| 1187 | + request: Request, |
| 1188 | + n4js: Neo4jStore = Depends(get_n4js_depends), |
| 1189 | + token: TokenData = Depends(get_token_data_depends), |
| 1190 | +): |
| 1191 | + """Set a Strategy for the given AlchemicalNetwork. |
| 1192 | +
|
| 1193 | + Expected request body: |
| 1194 | + { |
| 1195 | + "strategy": {...}, // GUFE strategy object, or null to remove |
| 1196 | + "max_tasks_per_transformation": 3, |
| 1197 | + "task_scaling": "exponential", |
| 1198 | + "mode": "partial", |
| 1199 | + "sleep_interval": 3600 |
| 1200 | + } |
| 1201 | + """ |
| 1202 | + sk = ScopedKey.from_str(network_scoped_key) |
| 1203 | + validate_scopes(sk.scope, token) |
| 1204 | + |
| 1205 | + # Handle request body with custom JSON decoder for GUFE objects |
| 1206 | + body = await request.body() |
| 1207 | + body_ = json.loads(body.decode("utf-8"), cls=JSON_HANDLER.decoder) |
| 1208 | + |
| 1209 | + try: |
| 1210 | + strategy_keyed_chain = body_.pop("strategy") |
| 1211 | + |
| 1212 | + # Convert KeyedChain to GufeTokenizable if strategy is provided |
| 1213 | + if strategy_keyed_chain is not None: |
| 1214 | + strategy_kc = KeyedChain(strategy_keyed_chain) |
| 1215 | + strategy = strategy_kc.to_gufe() |
| 1216 | + else: |
| 1217 | + strategy = None |
| 1218 | + except Exception as e: |
| 1219 | + raise HTTPException( |
| 1220 | + status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY, |
| 1221 | + detail=str(e), |
| 1222 | + ) |
| 1223 | + |
| 1224 | + if strategy is not None: |
| 1225 | + # Create strategy state from body parameters |
| 1226 | + try: |
| 1227 | + strategy_state = StrategyState(**body_) |
| 1228 | + except ValidationError as e: |
| 1229 | + raise HTTPException( |
| 1230 | + status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY, |
| 1231 | + detail=str(e), |
| 1232 | + ) |
| 1233 | + |
| 1234 | + try: |
| 1235 | + strategy_sk = n4js.set_network_strategy(sk, strategy, strategy_state) |
| 1236 | + except ValueError: |
| 1237 | + raise HTTPException( |
| 1238 | + status_code=http_status.HTTP_400_BAD_REQUEST, |
| 1239 | + detail=str(e), |
| 1240 | + ) |
| 1241 | + |
| 1242 | + return str(strategy_sk) if strategy_sk is not None else None |
| 1243 | + else: |
| 1244 | + # Remove strategy |
| 1245 | + n4js.set_network_strategy(sk, None) |
| 1246 | + return None |
| 1247 | + |
| 1248 | + |
| 1249 | +@router.get("/networks/{network_scoped_key}/strategy") |
| 1250 | +def get_network_strategy( |
| 1251 | + network_scoped_key: str, |
| 1252 | + *, |
| 1253 | + n4js: Neo4jStore = Depends(get_n4js_depends), |
| 1254 | + token: TokenData = Depends(get_token_data_depends), |
| 1255 | +): |
| 1256 | + """Get the Strategy for the given AlchemicalNetwork.""" |
| 1257 | + sk = ScopedKey.from_str(network_scoped_key) |
| 1258 | + validate_scopes(sk.scope, token) |
| 1259 | + |
| 1260 | + strategy = n4js.get_network_strategy(sk) |
| 1261 | + return GufeJSONResponse(strategy) if strategy is not None else None |
| 1262 | + |
| 1263 | + |
| 1264 | +@router.get("/networks/{network_scoped_key}/strategy/state") |
| 1265 | +def get_network_strategy_state( |
| 1266 | + network_scoped_key: str, |
| 1267 | + *, |
| 1268 | + n4js: Neo4jStore = Depends(get_n4js_depends), |
| 1269 | + token: TokenData = Depends(get_token_data_depends), |
| 1270 | +): |
| 1271 | + """Get the StrategyState for the given AlchemicalNetwork.""" |
| 1272 | + sk = ScopedKey.from_str(network_scoped_key) |
| 1273 | + validate_scopes(sk.scope, token) |
| 1274 | + |
| 1275 | + strategy_state = n4js.get_network_strategy_state(sk) |
| 1276 | + |
| 1277 | + return strategy_state.to_dict() if strategy_state is not None else None |
| 1278 | + |
| 1279 | + |
| 1280 | +@router.get("/networks/{network_scoped_key}/strategy/status") |
| 1281 | +def get_network_strategy_status( |
| 1282 | + network_scoped_key: str, |
| 1283 | + *, |
| 1284 | + n4js: Neo4jStore = Depends(get_n4js_depends), |
| 1285 | + token: TokenData = Depends(get_token_data_depends), |
| 1286 | +): |
| 1287 | + """Get the status of the Strategy for the given AlchemicalNetwork.""" |
| 1288 | + sk = ScopedKey.from_str(network_scoped_key) |
| 1289 | + validate_scopes(sk.scope, token) |
| 1290 | + |
| 1291 | + strategy_state = n4js.get_network_strategy_state(sk) |
| 1292 | + |
| 1293 | + return strategy_state.status.value if strategy_state is not None else None |
| 1294 | + |
| 1295 | + |
| 1296 | +@router.post("/networks/{network_scoped_key}/strategy/awake") |
| 1297 | +def set_network_strategy_awake( |
| 1298 | + network_scoped_key: str, |
| 1299 | + *, |
| 1300 | + n4js: Neo4jStore = Depends(get_n4js_depends), |
| 1301 | + token: TokenData = Depends(get_token_data_depends), |
| 1302 | +): |
| 1303 | + """Set the Strategy status to 'awake' for the given AlchemicalNetwork.""" |
| 1304 | + sk = ScopedKey.from_str(network_scoped_key) |
| 1305 | + validate_scopes(sk.scope, token) |
| 1306 | + |
| 1307 | + strategy_state = n4js.get_network_strategy_state(sk) |
| 1308 | + |
| 1309 | + if strategy_state is None: |
| 1310 | + return |
| 1311 | + |
| 1312 | + # Update strategy state to awake and clear error info |
| 1313 | + strategy_state.status = "awake" |
| 1314 | + strategy_state.exception = None |
| 1315 | + strategy_state.traceback = None |
| 1316 | + |
| 1317 | + updated = n4js.update_strategy_state(sk, strategy_state) |
| 1318 | + |
| 1319 | + return str(updated) if updated is not None else None |
| 1320 | + |
| 1321 | + |
1183 | 1322 | ### add router |
1184 | 1323 |
|
1185 | 1324 | app.include_router(router) |
0 commit comments