Skip to content

Commit 7f98c61

Browse files
committed
merge
2 parents 4f8c9b3 + 53bee94 commit 7f98c61

21 files changed

Lines changed: 1194 additions & 26 deletions

openml/_api/clients/http.py

Lines changed: 436 additions & 3 deletions
Large diffs are not rendered by default.

openml/_api/clients/minio.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66

77

88
class MinIOClient:
9-
def __init__(self, path: Path | None = None) -> None:
9+
"""
10+
Lightweight client configuration for interacting with a MinIO-compatible
11+
object storage service.
12+
13+
This class stores basic configuration such as a base filesystem path and
14+
default HTTP headers. It is intended to be extended with actual request
15+
or storage logic elsewhere.
16+
17+
Parameters
18+
----------
19+
path : pathlib.Path or None, optional
20+
Base path used for local storage or downloads. If ``None``, no
21+
default path is configured.
22+
23+
Attributes
24+
----------
25+
path : pathlib.Path or None
26+
Configured base path for storage operations.
27+
headers : dict of str to str
28+
Default HTTP headers, including a user-agent identifying the
29+
OpenML Python client version.
30+
"""
31+
32+
def __init__(self, path: Path) -> None:
1033
self.path = path
1134
self.headers: dict[str, str] = {"user-agent": f"openml-python/{__version__}"}

openml/_api/resources/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@
4242
"EvaluationV1API",
4343
"EvaluationV2API",
4444
"FallbackProxy",
45-
"FallbackProxy",
4645
"FlowAPI",
4746
"FlowV1API",
4847
"FlowV2API",
4948
"ResourceAPI",
50-
"ResourceAPI",
5149
"ResourceV1API",
5250
"ResourceV2API",
5351
"RunAPI",

openml/_api/resources/base/base.py

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,33 @@
1414

1515

1616
class ResourceAPI(ABC):
17+
"""
18+
Abstract base class for OpenML resource APIs.
19+
20+
This class defines the common interface for interacting with OpenML
21+
resources (e.g., datasets, flows, runs) across different API versions.
22+
Concrete subclasses must implement the resource-specific operations
23+
such as publishing, deleting, and tagging.
24+
25+
Parameters
26+
----------
27+
http : HTTPClient
28+
Configured HTTP client used for communication with the OpenML API.
29+
minio : MinIOClient or None, optional
30+
Optional MinIO client used for object storage operations.
31+
32+
Attributes
33+
----------
34+
api_version : APIVersion
35+
API version implemented by the resource.
36+
resource_type : ResourceType
37+
Type of OpenML resource handled by the implementation.
38+
_http : HTTPClient
39+
Internal HTTP client instance.
40+
_minio : MinIOClient or None
41+
Internal MinIO client instance, if provided.
42+
"""
43+
1744
api_version: APIVersion
1845
resource_type: ResourceType
1946

@@ -22,18 +49,107 @@ def __init__(self, http: HTTPClient, minio: MinIOClient | None = None):
2249
self._minio = minio
2350

2451
@abstractmethod
25-
def delete(self, resource_id: int) -> bool: ...
52+
def delete(self, resource_id: int) -> bool:
53+
"""
54+
Delete a resource by its identifier.
55+
56+
Parameters
57+
----------
58+
resource_id : int
59+
Unique identifier of the resource to delete.
60+
61+
Returns
62+
-------
63+
bool
64+
``True`` if the deletion was successful.
65+
66+
Notes
67+
-----
68+
Concrete subclasses must implement this method.
69+
"""
2670

2771
@abstractmethod
28-
def publish(self, path: str, files: Mapping[str, Any] | None) -> int: ...
72+
def publish(self, path: str, files: Mapping[str, Any] | None) -> int:
73+
"""
74+
Publish a new resource to the OpenML server.
75+
76+
Parameters
77+
----------
78+
path : str
79+
API endpoint path used for publishing the resource.
80+
files : Mapping of str to Any or None
81+
Files or payload data required for publishing. The structure
82+
depends on the resource type.
83+
84+
Returns
85+
-------
86+
int
87+
Identifier of the newly created resource.
88+
89+
Notes
90+
-----
91+
Concrete subclasses must implement this method.
92+
"""
2993

3094
@abstractmethod
31-
def tag(self, resource_id: int, tag: str) -> list[str]: ...
95+
def tag(self, resource_id: int, tag: str) -> list[str]:
96+
"""
97+
Add a tag to a resource.
98+
99+
Parameters
100+
----------
101+
resource_id : int
102+
Identifier of the resource to tag.
103+
tag : str
104+
Tag to associate with the resource.
105+
106+
Returns
107+
-------
108+
list of str
109+
Updated list of tags assigned to the resource.
110+
111+
Notes
112+
-----
113+
Concrete subclasses must implement this method.
114+
"""
32115

33116
@abstractmethod
34-
def untag(self, resource_id: int, tag: str) -> list[str]: ...
117+
def untag(self, resource_id: int, tag: str) -> list[str]:
118+
"""
119+
Remove a tag from a resource.
120+
121+
Parameters
122+
----------
123+
resource_id : int
124+
Identifier of the resource to untag.
125+
tag : str
126+
Tag to remove from the resource.
127+
128+
Returns
129+
-------
130+
list of str
131+
Updated list of tags assigned to the resource.
132+
133+
Notes
134+
-----
135+
Concrete subclasses must implement this method.
136+
"""
35137

36138
def _not_supported(self, *, method: str) -> NoReturn:
139+
"""
140+
Raise an error indicating that a method is not supported.
141+
142+
Parameters
143+
----------
144+
method : str
145+
Name of the unsupported method.
146+
147+
Raises
148+
------
149+
OpenMLNotSupportedError
150+
If the current API version does not support the requested method
151+
for the given resource type.
152+
"""
37153
version = getattr(self.api_version, "value", "unknown")
38154
resource = getattr(self.resource_type, "value", "unknown")
39155

openml/_api/resources/base/fallback.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,82 @@
77

88

99
class FallbackProxy:
10+
"""
11+
Proxy object that provides transparent fallback across multiple API versions.
12+
13+
This class delegates attribute access to a sequence of API implementations.
14+
When a callable attribute is invoked and raises ``OpenMLNotSupportedError``,
15+
the proxy automatically attempts the same method on subsequent API instances
16+
until one succeeds.
17+
18+
Parameters
19+
----------
20+
*api_versions : Any
21+
One or more API implementation instances ordered by priority.
22+
The first API is treated as the primary implementation, and
23+
subsequent APIs are used as fallbacks.
24+
25+
Raises
26+
------
27+
ValueError
28+
If no API implementations are provided.
29+
30+
Notes
31+
-----
32+
Attribute lookup is performed dynamically via ``__getattr__``.
33+
Only methods that raise ``OpenMLNotSupportedError`` trigger fallback
34+
behavior. Other exceptions are propagated immediately.
35+
"""
36+
1037
def __init__(self, *api_versions: Any):
1138
if not api_versions:
1239
raise ValueError("At least one API version must be provided")
1340
self._apis = api_versions
1441

1542
def __getattr__(self, name: str) -> Any:
43+
"""
44+
Dynamically resolve attribute access across API implementations.
45+
46+
Parameters
47+
----------
48+
name : str
49+
Name of the attribute being accessed.
50+
51+
Returns
52+
-------
53+
Any
54+
The resolved attribute. If it is callable, a wrapped function
55+
providing fallback behavior is returned.
56+
57+
Raises
58+
------
59+
AttributeError
60+
If none of the API implementations define the attribute.
61+
"""
1662
api, attr = self._find_attr(name)
1763
if callable(attr):
1864
return self._wrap_callable(name, api, attr)
1965
return attr
2066

2167
def _find_attr(self, name: str) -> tuple[Any, Any]:
68+
"""
69+
Find the first API implementation that defines a given attribute.
70+
71+
Parameters
72+
----------
73+
name : str
74+
Name of the attribute to search for.
75+
76+
Returns
77+
-------
78+
tuple of (Any, Any)
79+
The API instance and the corresponding attribute.
80+
81+
Raises
82+
------
83+
AttributeError
84+
If no API implementation defines the attribute.
85+
"""
2286
for api in self._apis:
2387
attr = getattr(api, name, None)
2488
if attr is not None:
@@ -31,6 +95,25 @@ def _wrap_callable(
3195
primary_api: Any,
3296
primary_attr: Callable[..., Any],
3397
) -> Callable[..., Any]:
98+
"""
99+
Wrap a callable attribute to enable fallback behavior.
100+
101+
Parameters
102+
----------
103+
name : str
104+
Name of the method being wrapped.
105+
primary_api : Any
106+
Primary API instance providing the callable.
107+
primary_attr : Callable[..., Any]
108+
Callable attribute obtained from the primary API.
109+
110+
Returns
111+
-------
112+
Callable[..., Any]
113+
Wrapped function that attempts the primary call first and
114+
falls back to other APIs if ``OpenMLNotSupportedError`` is raised.
115+
"""
116+
34117
def wrapper(*args: Any, **kwargs: Any) -> Any:
35118
try:
36119
return primary_attr(*args, **kwargs)
@@ -46,6 +129,31 @@ def _call_fallbacks(
46129
*args: Any,
47130
**kwargs: Any,
48131
) -> Any:
132+
"""
133+
Attempt to call a method on fallback API implementations.
134+
135+
Parameters
136+
----------
137+
name : str
138+
Name of the method to invoke.
139+
skip_api : Any
140+
API instance to skip (typically the primary API that already failed).
141+
*args : Any
142+
Positional arguments passed to the method.
143+
**kwargs : Any
144+
Keyword arguments passed to the method.
145+
146+
Returns
147+
-------
148+
Any
149+
Result returned by the first successful fallback invocation.
150+
151+
Raises
152+
------
153+
OpenMLNotSupportedError
154+
If all API implementations either do not define the method
155+
or raise ``OpenMLNotSupportedError``.
156+
"""
49157
for api in self._apis:
50158
if api is skip_api:
51159
continue

openml/_api/resources/base/resources.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,50 @@
1616

1717

1818
class DatasetAPI(ResourceAPI):
19+
"""Abstract API interface for dataset resources."""
20+
1921
resource_type: ResourceType = ResourceType.DATASET
2022

2123

2224
class TaskAPI(ResourceAPI):
25+
"""Abstract API interface for task resources."""
26+
2327
resource_type: ResourceType = ResourceType.TASK
2428

2529

2630
class EvaluationMeasureAPI(ResourceAPI):
31+
"""Abstract API interface for evaluation measure resources."""
32+
2733
resource_type: ResourceType = ResourceType.EVALUATION_MEASURE
2834

2935

3036
class EstimationProcedureAPI(ResourceAPI):
37+
"""Abstract API interface for estimation procedure resources."""
38+
3139
resource_type: ResourceType = ResourceType.ESTIMATION_PROCEDURE
3240

3341

3442
class EvaluationAPI(ResourceAPI):
43+
"""Abstract API interface for evaluation resources."""
44+
3545
resource_type: ResourceType = ResourceType.EVALUATION
3646

3747

3848
class FlowAPI(ResourceAPI):
49+
"""Abstract API interface for flow resources."""
50+
3951
resource_type: ResourceType = ResourceType.FLOW
4052

4153

4254
class StudyAPI(ResourceAPI):
55+
"""Abstract API interface for study resources."""
56+
4357
resource_type: ResourceType = ResourceType.STUDY
4458

4559

4660
class RunAPI(ResourceAPI):
61+
"""Abstract API interface for run resources."""
62+
4763
resource_type: ResourceType = ResourceType.RUN
4864

4965
@abstractmethod
@@ -72,4 +88,6 @@ def list( # type: ignore[valid-type] # noqa: PLR0913
7288

7389

7490
class SetupAPI(ResourceAPI):
91+
"""Abstract API interface for setup resources."""
92+
7593
resource_type: ResourceType = ResourceType.SETUP

0 commit comments

Comments
 (0)