Skip to content

Commit 9a752eb

Browse files
committed
first round of changes
2 parents 844faf5 + ec3ed63 commit 9a752eb

15 files changed

Lines changed: 622 additions & 288 deletions

File tree

openml/_api/clients/http.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ def _key_to_path(self, key: str) -> Path:
8989
"""
9090
return self.path.joinpath(key)
9191

92+
def _get_body_filename_from_response(self, response: Response) -> str:
93+
content_type = response.headers.get("Content-Type", "").lower()
94+
95+
if "application/json" in content_type:
96+
return "body.json"
97+
98+
if "text/xml" in content_type:
99+
return "body.xml"
100+
101+
return "body.txt"
102+
103+
def _get_body_filename_from_path(self, path: Path) -> str:
104+
if (path / "body.json").exists():
105+
return "body.json"
106+
107+
if (path / "body.xml").exists():
108+
return "body.xml"
109+
110+
return "body.txt"
111+
92112
def load(self, key: str) -> Response:
93113
"""
94114
Load a cached HTTP response from disk.
@@ -112,31 +132,26 @@ def load(self, key: str) -> Response:
112132
"""
113133
path = self._key_to_path(key)
114134

115-
if not path.exists():
116-
raise FileNotFoundError(f"Cache entry not found: {path}")
117-
118135
meta_path = path / "meta.json"
119-
headers_path = path / "headers.json"
120-
body_path = path / "body.bin"
136+
meta_raw = meta_path.read_bytes() if meta_path.exists() else "{}"
137+
meta = json.loads(meta_raw)
121138

122-
if not (meta_path.exists() and headers_path.exists() and body_path.exists()):
123-
raise FileNotFoundError(f"Incomplete cache at {path}")
124-
125-
with meta_path.open("r", encoding="utf-8") as f:
126-
meta = json.load(f)
127-
128-
with headers_path.open("r", encoding="utf-8") as f:
129-
headers = json.load(f)
139+
headers_path = path / "headers.json"
140+
headers_raw = headers_path.read_bytes() if headers_path.exists() else "{}"
141+
headers = json.loads(headers_raw)
130142

143+
body_path = path / self._get_body_filename_from_path(path)
144+
if not body_path.exists():
145+
raise FileNotFoundError(f"Incomplete cache at {body_path}")
131146
body = body_path.read_bytes()
132147

133148
response = Response()
134-
response.status_code = meta["status_code"]
135-
response.url = meta["url"]
136-
response.reason = meta["reason"]
137149
response.headers = headers
138150
response._content = body
139-
response.encoding = meta["encoding"]
151+
response.status_code = meta.get("status_code")
152+
response.url = meta.get("url")
153+
response.reason = meta.get("reason")
154+
response.encoding = meta.get("encoding")
140155

141156
return response
142157

@@ -160,7 +175,9 @@ def save(self, key: str, response: Response) -> None:
160175
path = self._key_to_path(key)
161176
path.mkdir(parents=True, exist_ok=True)
162177

163-
(path / "body.bin").write_bytes(response.content)
178+
body_filename = self._get_body_filename_from_response(response)
179+
with (path / body_filename).open("wb") as f:
180+
f.write(response.content)
164181

165182
with (path / "headers.json").open("w", encoding="utf-8") as f:
166183
json.dump(dict(response.headers), f)

openml/_api/resources/base/resources.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from __future__ import annotations
22

3+
import builtins
34
from abc import abstractmethod
4-
from collections.abc import Callable
5-
from pathlib import Path
5+
from collections.abc import Iterable
66
from typing import TYPE_CHECKING, Any
77

88
from openml.enums import ResourceType
@@ -11,9 +11,10 @@
1111

1212
if TYPE_CHECKING:
1313
import pandas as pd
14-
from requests import Response
1514

1615
from openml import OpenMLEvaluation
16+
from openml.flows.flow import OpenMLFlow
17+
from openml.setups.setup import OpenMLSetup
1718
from openml.tasks.task import OpenMLTask, TaskType
1819

1920

@@ -66,28 +67,15 @@ def list(
6667
"""
6768
...
6869

69-
def download(
70-
self,
71-
url: str,
72-
handler: Callable[[Response, Path, str], None] | None = None,
73-
encoding: str = "utf-8",
74-
file_name: str = "response.txt",
75-
md5_checksum: str | None = None,
76-
) -> Path:
77-
return self._http.download(
78-
url=url,
79-
handler=handler,
80-
encoding=encoding,
81-
file_name=file_name,
82-
md5_checksum=md5_checksum,
83-
)
84-
8570

8671
class EvaluationMeasureAPI(ResourceAPI):
8772
"""Abstract API interface for evaluation measure resources."""
8873

8974
resource_type: ResourceType = ResourceType.EVALUATION_MEASURE
9075

76+
@abstractmethod
77+
def list(self) -> list[str]: ...
78+
9179

9280
class EstimationProcedureAPI(ResourceAPI):
9381
"""Abstract API interface for estimation procedure resources."""
@@ -140,3 +128,24 @@ class SetupAPI(ResourceAPI):
140128
"""Abstract API interface for setup resources."""
141129

142130
resource_type: ResourceType = ResourceType.SETUP
131+
132+
@abstractmethod
133+
def list(
134+
self,
135+
limit: int,
136+
offset: int,
137+
*,
138+
setup: Iterable[int] | None = None,
139+
flow: int | None = None,
140+
tag: str | None = None,
141+
) -> list[OpenMLSetup]: ...
142+
143+
@abstractmethod
144+
def get(self, setup_id: int) -> OpenMLSetup: ...
145+
146+
@abstractmethod
147+
def exists(
148+
self,
149+
flow: OpenMLFlow,
150+
param_settings: builtins.list[dict[str, Any]],
151+
) -> int | bool: ...
Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
11
from __future__ import annotations
22

3+
import xmltodict
4+
35
from .base import EvaluationMeasureAPI, ResourceV1API, ResourceV2API
46

57

68
class EvaluationMeasureV1API(ResourceV1API, EvaluationMeasureAPI):
7-
"""Version 1 API implementation for evaluation measure resources."""
9+
"""V1 API implementation for evaluation measures.
10+
11+
Fetches evaluation measures from the v1 XML API endpoint.
12+
"""
13+
14+
def list(self) -> list[str]:
15+
"""List all evaluation measures available on OpenML.
16+
17+
Returns
18+
-------
19+
list[str]
20+
A list of evaluation measure names.
21+
"""
22+
path = "evaluationmeasure/list"
23+
response = self._http.get(path)
24+
xml_content = response.text
25+
26+
qualities = xmltodict.parse(xml_content, force_list=("oml:measures"))
27+
# Minimalistic check if the XML is useful
28+
if "oml:evaluation_measures" not in qualities:
29+
raise ValueError('Error in return XML, does not contain "oml:evaluation_measures"')
30+
31+
if not isinstance(
32+
qualities["oml:evaluation_measures"]["oml:measures"][0]["oml:measure"], list
33+
):
34+
raise TypeError('Error in return XML, does not contain "oml:measure" as a list')
35+
36+
return qualities["oml:evaluation_measures"]["oml:measures"][0]["oml:measure"]
837

938

1039
class EvaluationMeasureV2API(ResourceV2API, EvaluationMeasureAPI):
11-
"""Version 2 API implementation for evaluation measure resources."""
40+
"""V2 API implementation for evaluation measures.
41+
42+
Fetches evaluation measures from the v2 JSON API endpoint.
43+
"""
44+
45+
def list(self) -> list[str]:
46+
"""List all evaluation measures available on OpenML.
47+
48+
Returns
49+
-------
50+
list[str]
51+
A list of evaluation measure names.
52+
"""
53+
path = "evaluationmeasure/list"
54+
response = self._http.get(path)
55+
data = response.json()
56+
57+
if not isinstance(data, list):
58+
raise ValueError(f"Expected list, got {type(data)}")
59+
60+
return data

0 commit comments

Comments
 (0)