Repository navigation
[ENH] V1 → V2 API Migration #1575
Description
This issue tracks the migration of the openml-python SDK from the legacy OpenML API v1 (PHP-based) in openml/OpenML to the newer API v2 (Python-based) in openml/server-api, along with a structural refactor of the codebase to support both APIs during the transition.
The SDK currently relies on the v1 API, which should be progressively replaced by v2 while keeping backward compatibility and fallback where v2 endpoints are incomplete.
Goals
- Separate API interaction from SDK logic by introducing clear abstraction layers
- Modularize HTTP handling, API resources, and SDK-facing interfaces
- Allow easy switching between API v1 and v2
- Provide a transparent fallback to v1 for missing or unimplemented v2 endpoints
Proposed design (high level)
- A shared
HTTPClientresponsible for:- retries
- proxy configurations
- caching
- exception handling
- thread safety
- Abstract API "blueprints" (e.g.
DatasetsAPI,TasksAPI) defining SDK-facing methods - Version-specific implementations (
*V1,*V2) mapping those methods to concrete endpoints - A fallback proxy that routes calls to v1 if a v2 endpoint is unavailable (unless
strict=True) - A central backend/context that controls API version selection and exposes resources internally
A draft implementation sketch is included below to illustrate the architecture (not final API).
# handles HTTP requests, retries, and proxy configuration
class HTTPClient:
def __init__(self, base_url: str):
self.base_url = base_url
def get(self, path, params=None):
return requests.get(f"{self.base_url}{path}", params=params)
def post(self, path, params=None, files=None):
return requests.post(f"{self.base_url}{path}", params=params, files=files)
# abstract blueprints defining SDK-exposed API endpoints and methods
class DatasetsAPI(ABC):
@abstractmethod
def get(self, id: int) -> dict: ...
class TasksAPI(ABC):
@abstractmethod
def get(self, id: int) -> dict: ...
@abstractmethod
def list(self, id: int) -> dict: ...
# version-specific implementations built on top of the API blueprints
class DatasetsV1(DatasetsAPI):
def __init__(self, http: HTTPClient):
self._http = http
def get(self, id: int) -> dict:
r = self._http.get(f"/api/v1/json/data/{id}")
d = r.json()["data_set_description"]
return {
"id": d["data_set_description"]["id"],
"name": d["data_set_description"]["name"],
"version": d["data_set_description"]["version"],
}
class DatasetsV2(DatasetsAPI):
def __init__(self, http: HTTPClient):
self._http = http
def get(self, id: int) -> dict:
r = self._http.get(f"/datasets/{id}")
d = r.json()
return {
"id": d["id"],
"name": d["name"],
"version": d["version"],
}
class TasksV1(TasksAPI):
pass
class TasksV2(TasksAPI):
pass
# proxy that falls back to v1 when v2 endpoints are missing or unimplemented
class FallbackProxy:
def __init__(self, primary, fallback, *, strict: bool):
self._primary = primary
self._fallback = fallback
self._strict = strict
def __getattr__(self, name):
primary_attr = getattr(self._primary, name)
if not callable(primary_attr):
return primary_attr
def wrapper(*args, **kwargs):
try:
return primary_attr(*args, **kwargs)
except NotImplementedError:
if self._strict:
raise
return getattr(self._fallback, name)(*args, **kwargs)
return wrapper
# core backend holding API bindings, exposed internally and to the SDK
class APIBackend:
def __init__(self, *, datasets, tasks):
self.datasets = datasets
self.tasks = tasks
def build_backend(version: str, strict: bool) -> APIBackend:
v1_http = HTTPClient("https://www.openml.org")
v2_http = HTTPClient("http://127.0.0.1:8001")
v1 = APIBackend(
datasets=DatasetsV1(v1_http),
tasks=TasksV1(v1_http),
)
if version == "v1":
return v1
v2 = APIBackend(
datasets=DatasetsV2(v2_http),
tasks=TasksV2(v2_http),
)
if strict:
return v2
return APIBackend(
datasets=FallbackProxy(v2.datasets, v1.datasets),
tasks=FallbackProxy(v2.tasks, v1.tasks),
)
class APIContext:
def __init__(self):
self._backend = build_backend("v1", strict=False)
def set_version(self, version: str, strict: bool = False):
self._backend = build_backend(version, strict)
@property
def backend(self):
return self._backend
# SDK-facing entry points and version switching helpers
api_context = APIContext()
def set_api_version(version: str, strict=False):
api_context.set_version(version=version, strict=strict)
openml.set_api_version("v2", strict=True)
api_context.backend.data.get(31)
api_context.backend.tasks.list()Work Items / Refactor scope
Base Scaffolding
Foundation: Establish the initial folder and file structure along with minimal base implementations for core components (HTTP client, backend, resource interfaces, versioned stubs). This provides a stable scaffold that subsequent refactor and migration work can build on.
HTTPClient features: Extend the shared HTTP client to provide consistent, reusable infrastructure concerns across all API versions and resources.
Fallback policy: Define and implement the fallback behavior that transparently redirects calls from v2 to v1 when endpoints are missing or unimplemented, with strict mode disabling fallback and surfacing errors.
- Base Scaffolding [ENH] V1 → V2 API Migration - core structure #1576
- Folder/file structure and base scaffolding
- retries
- proxy configuration
- caching
- exception handling
- v2 → v1 fallback support
Resource implementations
Implement resource classes that define SDK-facing methods and map them to concrete API endpoints. Each resource should have a clear abstract interface and version-specific implementations (*V1, *V2).
- Datasets [ENH] V1 → V2 API Migration - datasets #1608
- Tasks [ENH] V1 → V2 API Migration - Tasks #1611
-
TaskTypes(not used in sdk) - EvaluationMeasures [ENH] V1 → V2 API Migration - evaluation measures #1603
- EstimationProcedures [ENH] V1 → V2 API Migration - estimation procedures #1604
- Evaluations [ENH] V1 → V2 API Migration - evaluations #1606
- Flows [ENH] V1 -> V2 Migration - Flows (module) #1609
- Studies [ENH] V1 → V2 API Migration - studies #1610
- Runs [ENH] V1 -> V2 Migration : Runs #1616
- Setups [ENH] V1 → V2 API Migration - setups #1619
-
Users(not used in sdk - though questionable, since spotted in evaluations)
Activity
JATAYU000 commentedon Jan 2, 2026
I will be working on Datasets Resource implementation
EmanAbdelhaleem commentedon Jan 2, 2026
Currently I am working on the EvaluationMeasures.
Afterwards I would like to work on Flows.
fkiraly commentedon Jan 2, 2026
@EmanAbdelhaleem, please comment on #1593 so you can be assigned.
@JATAYU000, please comment on #1592 so you can be assigned.
@rohansen856, please comment on #1594 so you can be assigned.
rohansen856 commentedon Jan 2, 2026
I would be workinng on
Studiesimplementation.Omswastik-11 commentedon Jan 5, 2026
Hi @geetu040 !! I would like to work on
Flows.Correct me if something I understood wrong .
flowthat define SDK-facing methods and map them to concrete API endpoints. Each resource should have a clear abstract interface and version-specific implementations (*V1, *V2).flows/functions.pyflowmoduleEmanAbdelhaleem commentedon Jan 6, 2026
Since
evaluations/functions.pycontains EvaluationMeasures, EstimationProcedures, Evaluations, I will be working on the three of them (I was actually doing so already, just realized now that they are separated here)28 remaining items
geetu040 commentedon Jan 29, 2026
Comment review/changes across all stacked PRs
use_cache, if the function gives the option to resert/override the existing cache, usereset_cacheuse_cacheandreset_cacheparameters can only be used withhttp.getsince other method don't make sense to have cachepostanddeletemethods in http client will use the api key automatically, but for get methods that need api_key for authentication should set this param to true when usinghttpobject:http.get(..., use_api_key=True)@openml.utils.thread_safe_if_oslo_installeddecorator on top of functions that had this in the sdkpublishmethod in all resource classesOpenML[Resource].publishmethod implemented in theOpenMLBaseclass.publishmethod should collect files and useapi_context.backend.[resource].publishwhich sends requests and parses for id internally.api_context.backend.[resource].publishwill lead toResourceV1.publishin_api/resources/base/versions.pydeleteanywhere in any of the resource classes, it's already implemented inResourceV1andResourceV2and should be used without overloading[resource]/functions.py, update the methoddelete_[resource]to useapi_context.backend.[resource].deleteinstead ofopenml.utils._delete_entitypush_tagmethod in all resource classesOpenML[Resource].push_tagmethod implemented in theOpenMLBaseclass.push_tagmethod should useapi_context.backend.[resource].tagfunction instead of_tag_openml_basewhich will entirely be removed from utilstagmethod is already implemented in the version classesResourceV1andResourceV2, so no need to implement this in the api resource classestag()remove_tagand useapi_context.backend.[resource].untagResourceAPI._not_supportedfor methods that are not implemented in v2OpenMLNotSupportedError) for all not implemented methodsself._not_supported(method="...")inside the method which is not implemented in the resource classes# noqa: ARG002comment for the params to avoid ruff failuresResourceV2RunsV1should inherit fromResourceV1andRunsAPIclass RunsV1(ResourceV1, RunsAPI): ...api_context.backend.[resource]s.[method](...)withopenml._backend.[resource].[method](....)api_context.backend.datasets.get_features(self.dataset_id)becomesopenml._backend.dataset.get_features(self.dataset_id)api_contextand simply importopenmlat top if to be addedfrom openml import _backend, we want to be consistent throughout the sdkDatasetsV1(ResourceV1, DatasetsAPI)toDatasetV1API(ResourceV1API, DatasetAPI)openml/_api/resourcesEmanAbdelhaleem commentedon Jan 29, 2026
@geetu040 If the v2 methods aren’t implemented because the endpoints don’t exist, then I don’t need to implement the combined class, right?
geetu040 commentedon Jan 29, 2026
the combined class checks if the output from both versions match and the fallback works as supposed to, so if v2 endpoints don't exist, you can skip the matching outputs but you would still need the combined class for fallback
EmanAbdelhaleem commentedon Feb 2, 2026
@geetu040
Should I implement the publish function for the evaluations resource?
geetu040 commentedon Feb 2, 2026
No, evaluations can't be published in the current state of sdk
geetu040 commentedon Feb 2, 2026
Added 2 more points (10, 11) in #1575 (comment), please update accordingly
CC: @EmanAbdelhaleem @rohansen856 @JATAYU000 @satvshr @Omswastik-11
EmanAbdelhaleem commentedon Feb 2, 2026
Setups are the same too, right?
satvshr commentedon Feb 4, 2026
An idea was to initialize:
inside
TestAPIBaseitself to prevent devs from having to initialize it and prevent code duplication. @geetu040 you mentioned not wanting to do this right now because other people may be using different ports (insettings.api.v2.server), but we should do something like this now, which can be used by all once all PRs tests are passing and are in a mergeable state (to prevent the differentsettings.api.v2.serverpaths).geetu040 commentedon Feb 6, 2026
Following up on #1616 (comment)
For tests that currently mock
delete, please update them as follows:requests.Session.requestinstead ofdelete.mock_delete.call_args.kwargsinstead of.args.'DELETE'.mock_delete.call_args.kwargsto inspect its structure and adjust any other test checks accordingly.Example diff:
Relevant for
[ENH] V1 → V2 API Migration - core structure (#1576)
[ENH] V1 → V2 API Migration - evaluations (#1606)
[ENH] V1 → V2 API Migration - evaluation measures (#1603)
[ENH] V1 → V2 API Migration - setups (#1619)
[ENH] V1 → V2 API Migration - estimation procedures (#1604)
[ENH] V1 → V2 API Migration - studies (#1610)