Skip to content

Commit 9660e78

Browse files
committed
tests:added tests for migration
Signed-off-by: Omswastik-11 <omswastikpanda11@gmail.com>
1 parent 468087d commit 9660e78

6 files changed

Lines changed: 77 additions & 93 deletions

File tree

openml/_api/resources/base/resources.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,3 @@ def list(
5454
tag: str | None = None,
5555
uploader: str | None = None,
5656
) -> pd.DataFrame: ...
57-
58-
@abstractmethod
59-
def publish(self, flow: OpenMLFlow) -> OpenMLFlow | tuple[OpenMLFlow, Response]: ... # type: ignore[override]
60-
61-
@abstractmethod
62-
def delete(self, flow_id: int) -> bool: ...

openml/_api/resources/flows.py

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from collections.abc import Mapping
34
from typing import Any
45

56
import pandas as pd
@@ -135,49 +136,21 @@ def list(
135136

136137
return pd.DataFrame.from_dict(flows, orient="index")
137138

138-
def publish(self, flow: OpenMLFlow) -> OpenMLFlow: # type: ignore[override]
139-
"""Create a new flow on the OpenML server.
140-
141-
under development , not fully functional yet
139+
def publish(self, path: str | None = None, files: Mapping[str, Any] | None = None) -> int:
140+
"""Publish a flow on the OpenML server.
142141
143142
Parameters
144143
----------
145-
flow : OpenMLFlow
146-
The flow object to upload to the server.
144+
files : Mapping[str, Any] | None
145+
Files to upload (including description).
147146
148147
Returns
149148
-------
150-
OpenMLFlow
151-
The updated flow object with the server-assigned flow_id.
149+
int
150+
The server-assigned flow id.
152151
"""
153-
from openml.extensions import Extension
154-
155-
# Check if flow is an OpenMLFlow or a compatible extension object
156-
if not isinstance(flow, OpenMLFlow) and not isinstance(flow, Extension):
157-
raise TypeError(f"Flow must be an OpenMLFlow or Extension instance, got {type(flow)}")
158-
159-
# Get file elements for upload (includes XML description if not provided)
160-
file_elements = flow._get_file_elements()
161-
if "description" not in file_elements:
162-
file_elements["description"] = flow._to_xml()
163-
164-
# POST to server (multipart/files). Ensure api_key is sent in the form data.
165-
files = file_elements
166-
data = {"api_key": self._http.api_key}
167-
response = self._http.post("flow", files=files, data=data)
168-
169-
parsed = xmltodict.parse(response.text)
170-
if "oml:error" in parsed:
171-
err = parsed["oml:error"]
172-
code = int(err.get("oml:code", 0)) if "oml:code" in err else None
173-
message = err.get("oml:message", "Server returned an error")
174-
raise OpenMLServerException(message=message, code=code)
175-
176-
# Parse response and update flow with server-assigned ID
177-
xml_response = xmltodict.parse(response.text)
178-
flow._parse_publish_response(xml_response)
179-
180-
return flow
152+
path = "flow"
153+
return super().publish(path, files)
181154

182155
def delete(self, flow_id: int) -> bool:
183156
"""Delete a flow from the OpenML server.
@@ -187,8 +160,7 @@ def delete(self, flow_id: int) -> bool:
187160
flow_id : int
188161
The ID of the flow to delete.
189162
"""
190-
self._http.delete(f"flow/{flow_id}")
191-
return True
163+
return super().delete(flow_id)
192164

193165

194166
class FlowsV2(ResourceV2, FlowsAPI):

openml/_api/runtime/core.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
)
1717

1818
if TYPE_CHECKING:
19-
from openml._api.resources.base import DatasetsAPI, FlowsAPI, ResourceAPI, TasksAPI
20-
from openml.base import OpenMLBase
19+
from openml._api.resources.base import DatasetsAPI, FlowsAPI, TasksAPI
2120

2221

2322
class APIBackend:
@@ -32,25 +31,6 @@ def __init__(
3231
self.tasks = tasks
3332
self.flows = flows
3433

35-
def get_resource_for_entity(self, entity: OpenMLBase) -> ResourceAPI:
36-
from openml.datasets.dataset import OpenMLDataset
37-
from openml.flows.flow import OpenMLFlow
38-
from openml.runs.run import OpenMLRun
39-
from openml.study.study import OpenMLStudy
40-
from openml.tasks.task import OpenMLTask
41-
42-
if isinstance(entity, OpenMLFlow):
43-
return self.flows # type: ignore
44-
if isinstance(entity, OpenMLRun):
45-
return self.runs # type: ignore
46-
if isinstance(entity, OpenMLDataset):
47-
return self.datasets # type: ignore
48-
if isinstance(entity, OpenMLTask):
49-
return self.tasks # type: ignore
50-
if isinstance(entity, OpenMLStudy):
51-
return self.studies # type: ignore
52-
raise ValueError(f"No resource manager available for entity type {type(entity)}")
53-
5434

5535
def build_backend(version: str, *, strict: bool) -> APIBackend:
5636
http_cache = HTTPCache(

openml/base.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import openml._api_calls
1212
import openml.config
1313

14-
from .utils import _tag_openml_base
14+
from .utils import _get_rest_api_type_alias, _tag_openml_base
1515

1616

1717
class OpenMLBase(ABC):
@@ -126,19 +126,20 @@ def _parse_publish_response(self, xml_response: dict[str, str]) -> None:
126126

127127
def publish(self) -> OpenMLBase:
128128
"""Publish the object on the OpenML server."""
129-
from openml._api import api_context
129+
file_elements = self._get_file_elements()
130130

131-
resource_manager = api_context.backend.get_resource_for_entity(self)
131+
if "description" not in file_elements:
132+
file_elements["description"] = self._to_xml()
132133

133-
published_entity = resource_manager.publish(self) # type: ignore
134-
135-
if (
136-
published_entity is not None
137-
and hasattr(published_entity, "id")
138-
and published_entity.id is not None
139-
):
140-
self.id = published_entity.id # type: ignore
134+
call = f"{_get_rest_api_type_alias(self)}/"
135+
response_text = openml._api_calls._perform_api_call(
136+
call,
137+
"post",
138+
file_elements=file_elements,
139+
)
140+
xml_response = xmltodict.parse(response_text)
141141

142+
self._parse_publish_response(xml_response)
142143
return self
143144

144145
def open_in_browser(self) -> None:

openml/flows/flow.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,16 +431,21 @@ def publish(self, raise_error_if_exists: bool = False) -> OpenMLFlow: # noqa: F
431431
# get_flow(), while functions.py tries to import flow.py in order to
432432
# instantiate an OpenMLFlow.
433433
import openml.flows.functions
434+
from openml._api import api_context
434435

435436
flow_id = openml.flows.functions.flow_exists(self.name, self.external_version)
436437
if not flow_id:
437438
if self.flow_id:
438439
raise openml.exceptions.PyOpenMLError(
439440
"Flow does not exist on the server, but 'flow.flow_id' is not None.",
440441
)
441-
super().publish()
442-
assert self.flow_id is not None # for mypy
443-
flow_id = self.flow_id
442+
443+
file_elements = self._get_file_elements()
444+
if "description" not in file_elements:
445+
file_elements["description"] = self._to_xml()
446+
447+
flow_id = api_context.backend.flows.publish(path="flow", files=file_elements)
448+
self.flow_id = flow_id
444449
elif raise_error_if_exists:
445450
error_message = f"This OpenMLFlow already exists with id: {flow_id}."
446451
raise openml.exceptions.PyOpenMLError(error_message)

tests/test_flows/test_flows_migration.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"""Tests for Flow V1 → V2 API Migration."""
33
from __future__ import annotations
44

5+
import uuid
6+
57
import pytest
68

79
from openml._api.resources import FallbackProxy, FlowsV1, FlowsV2
@@ -81,37 +83,67 @@ def test_list_with_tag_limit_offset(self):
8183
if len(flows_df) > 0:
8284
assert "id" in flows_df.columns
8385

84-
@pytest.mark.uses_test_server()
85-
def test_publish(self):
86-
"""Test publishing a sklearn flow using V1 API."""
87-
from openml_sklearn.extension import SklearnExtension
88-
from sklearn.tree import ExtraTreeRegressor
89-
clf = ExtraTreeRegressor()
90-
extension = SklearnExtension()
91-
dt_flow = extension.model_to_flow(clf)
92-
published_flow = self.resource.publish(dt_flow)
93-
assert isinstance(published_flow, OpenMLFlow)
94-
assert getattr(published_flow, "id", None) is not None
95-
9686
@pytest.mark.uses_test_server()
9787
def test_delete(self):
9888
"""Test deleting a flow using V1 API."""
9989
from openml_sklearn.extension import SklearnExtension
10090
from sklearn.tree import ExtraTreeRegressor
91+
10192
clf = ExtraTreeRegressor()
10293
extension = SklearnExtension()
10394
dt_flow = extension.model_to_flow(clf)
95+
96+
# Check if flow exists, if not publish it
10497
flow_id = self.resource.exists(
10598
name=dt_flow.name,
106-
external_version=dt_flow.external_version
99+
external_version=dt_flow.external_version,
107100
)
101+
102+
if not flow_id:
103+
# Publish the flow first
104+
file_elements = dt_flow._get_file_elements()
105+
if "description" not in file_elements:
106+
file_elements["description"] = dt_flow._to_xml()
107+
108+
flow_id = self.resource.publish(file_elements)
109+
110+
# Now delete it
108111
result = self.resource.delete(flow_id)
109112
assert result is True
113+
114+
# Verify it no longer exists
110115
exists = self.resource.exists(
111116
name=dt_flow.name,
112-
external_version=dt_flow.external_version
117+
external_version=dt_flow.external_version,
113118
)
114119
assert exists is False
120+
121+
@pytest.mark.uses_test_server()
122+
def test_publish(self):
123+
"""Test publishing a sklearn flow using V1 API."""
124+
from openml_sklearn.extension import SklearnExtension
125+
from sklearn.tree import ExtraTreeRegressor
126+
127+
clf = ExtraTreeRegressor()
128+
extension = SklearnExtension()
129+
dt_flow = extension.model_to_flow(clf)
130+
131+
# Check if flow already exists
132+
flow_id = self.resource.exists(
133+
name=dt_flow.name,
134+
external_version=dt_flow.external_version,
135+
)
136+
137+
if not flow_id:
138+
file_elements = dt_flow._get_file_elements()
139+
if "description" not in file_elements:
140+
print("Adding description to flow XML")
141+
file_elements["description"] = dt_flow._to_xml()
142+
143+
flow_id = self.resource.publish(file_elements)
144+
145+
assert isinstance(flow_id, int)
146+
assert flow_id > 0
115147

116148

117149

0 commit comments

Comments
 (0)