From b6cee6cc77f1e68b59c618e225ab9dbdcb3272ee Mon Sep 17 00:00:00 2001 From: Aayush Jha Date: Sat, 14 Mar 2026 11:08:18 +0530 Subject: [PATCH 1/2] Fix tasktype api_constraints parsing for malformed JSON and dict inputs --- src/routers/openml/tasktype.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/routers/openml/tasktype.py b/src/routers/openml/tasktype.py index 5355e45..cab7c89 100644 --- a/src/routers/openml/tasktype.py +++ b/src/routers/openml/tasktype.py @@ -59,6 +59,7 @@ async def get_task_type( creator.strip(' "') for creator in cast("str", contributors).split(",") ] task_type["creation_date"] = task_type.pop("creationDate") + task_type_inputs = await get_input_for_task_type(task_type_id, expdb) input_types = [] for task_type_input in task_type_inputs: @@ -66,10 +67,25 @@ async def get_task_type( if task_type_input.requirement == "required": input_["requirement"] = task_type_input.requirement input_["name"] = task_type_input.name - # api_constraints is for one input only in the test database (TODO: patch db) - if isinstance(task_type_input.api_constraints, str): - constraint = json.loads(task_type_input.api_constraints) - input_["data_type"] = constraint["data_type"] + + # Accept either legacy JSON string or already parsed dict + constraint = None + ac = task_type_input.api_constraints + if isinstance(ac, str): + try: + constraint = json.loads(ac) + except json.JSONDecodeError: + # Keep response stable for malformed legacy values + constraint = None + elif isinstance(ac, dict): + constraint = ac + + if isinstance(constraint, dict): + data_type = constraint.get("data_type") + if data_type is not None: + input_["data_type"] = data_type + input_types.append(input_) + task_type["input"] = input_types return {"task_type": task_type} From ac5dc6fddd745c116863a4dca1c982f95ffdd7c1 Mon Sep 17 00:00:00 2001 From: Aayush Jha Date: Sat, 14 Mar 2026 14:28:50 +0530 Subject: [PATCH 2/2] Refactor api_constraints parsing with logging and safer typing --- src/routers/openml/tasktype.py | 55 ++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/src/routers/openml/tasktype.py b/src/routers/openml/tasktype.py index cab7c89..44228fa 100644 --- a/src/routers/openml/tasktype.py +++ b/src/routers/openml/tasktype.py @@ -1,4 +1,6 @@ import json +import logging +from collections.abc import Mapping from typing import Annotated, Any, Literal, cast from fastapi import APIRouter, Depends @@ -11,6 +13,7 @@ from routers.dependencies import expdb_connection router = APIRouter(prefix="/tasktype", tags=["tasks"]) +logger = logging.getLogger(__name__) def _normalize_task_type(task_type: Row[Any]) -> dict[str, str | None | list[Any]]: @@ -26,6 +29,36 @@ def _normalize_task_type(task_type: Row[Any]) -> dict[str, str | None | list[Any return ttype +def _extract_data_type_from_api_constraints( + api_constraints: Mapping[str, Any] | str | None, + input_name: str, +) -> str | None: + """Extract string data_type from api_constraints safely.""" + constraint: Mapping[str, Any] | None = None + + if isinstance(api_constraints, str): + try: + loaded = json.loads(api_constraints) + except json.JSONDecodeError: + logger.warning( + "Failed to decode legacy api_constraints JSON for task_type_input '%s'; value=%r", + input_name, + api_constraints, + exc_info=True, + ) + return None + if isinstance(loaded, Mapping): + constraint = loaded + elif isinstance(api_constraints, Mapping): + constraint = api_constraints + + if not constraint: + return None + + data_type = constraint.get("data_type") + return data_type if isinstance(data_type, str) else None + + @router.get(path="/list") async def list_task_types( expdb: Annotated[AsyncConnection, Depends(expdb_connection)], @@ -68,22 +101,12 @@ async def get_task_type( input_["requirement"] = task_type_input.requirement input_["name"] = task_type_input.name - # Accept either legacy JSON string or already parsed dict - constraint = None - ac = task_type_input.api_constraints - if isinstance(ac, str): - try: - constraint = json.loads(ac) - except json.JSONDecodeError: - # Keep response stable for malformed legacy values - constraint = None - elif isinstance(ac, dict): - constraint = ac - - if isinstance(constraint, dict): - data_type = constraint.get("data_type") - if data_type is not None: - input_["data_type"] = data_type + data_type = _extract_data_type_from_api_constraints( + task_type_input.api_constraints, + task_type_input.name, + ) + if data_type is not None: + input_["data_type"] = data_type input_types.append(input_)