Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
296 commits
Select commit Hold shift + click to select a range
fa3cd40
bug fixing
satvshr Jan 16, 2026
7e9bc1f
Merge branch 'main' into migration
geetu040 Jan 21, 2026
c603383
add tests directory
geetu040 Jan 21, 2026
ff6a8b0
use enum for delay method
geetu040 Jan 21, 2026
f01898f
implement cache
geetu040 Jan 21, 2026
5c4511e
refactor clients
geetu040 Jan 21, 2026
29fac2c
migrating v1 -> v2 flows
Omswastik-11 Jan 21, 2026
bdf53f3
migrating v1 -> v2 flows
Omswastik-11 Jan 21, 2026
9d0098f
Merge branch 'main' into issue1564
fkiraly Jan 21, 2026
a1a706c
Merge branch 'main' into flow-migration-stacked
Omswastik-11 Jan 22, 2026
43276d2
fix import in resources/base.py
geetu040 Jan 23, 2026
1206f69
refactor and add exception handling
geetu040 Jan 26, 2026
bde5942
Merge branch 'main' into issue1564
satvshr Jan 26, 2026
4948e99
refactor resources/base/
geetu040 Jan 26, 2026
a354167
implement delete
geetu040 Jan 26, 2026
c4e713f
Merge remote-tracking branch 'geetu040/migration' into flow-migration…
Omswastik-11 Jan 27, 2026
95fc75f
Merge branch 'main' into flow-migration-stacked
Omswastik-11 Jan 27, 2026
1fe7e3e
implement publish and minor refactoring
geetu040 Jan 27, 2026
54a3151
implement tag/untag
geetu040 Jan 27, 2026
2b6fe65
implement fallback
geetu040 Jan 27, 2026
685c19a
added tests
Omswastik-11 Jan 27, 2026
920ff21
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Jan 28, 2026
fa53f8d
add test_http.py
geetu040 Jan 28, 2026
2b2db96
add uses_test_server marker
geetu040 Jan 28, 2026
c7c24a3
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Jan 29, 2026
c9617f9
implement reset_cache
geetu040 Jan 29, 2026
92d88e4
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Jan 29, 2026
443ade9
tests:added tests for migration
Omswastik-11 Jan 29, 2026
468087d
tests:added tests for migration
Omswastik-11 Jan 29, 2026
5bc37b8
fixes with publish/delete
geetu040 Jan 29, 2026
08d9916
fix cache_key in tests
geetu040 Jan 29, 2026
9660e78
tests:added tests for migration
Omswastik-11 Jan 30, 2026
f60c78b
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Jan 30, 2026
f25c95b
tests:added tests for migration
Omswastik-11 Jan 30, 2026
8caba11
update _not_supported
geetu040 Jan 30, 2026
6e577c4
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Jan 30, 2026
1913c10
add 'get_api_config' skeleton method
SimonBlanke Jan 30, 2026
7681949
remove 'APISettings'
SimonBlanke Jan 30, 2026
01840a5
impl. 'get_api_config'
SimonBlanke Jan 30, 2026
26ed4c1
add singleton pattern for settings
SimonBlanke Jan 30, 2026
c588d0c
add 'reset_settings'
SimonBlanke Jan 30, 2026
b6ff720
remove unused code
SimonBlanke Jan 30, 2026
80d5afc
reimplement usage of v1 settings config
SimonBlanke Jan 30, 2026
f47112c
first try v2, fallback to v1 if not available
SimonBlanke Jan 30, 2026
d44cf3e
reimplement singelton without the use of 'global'
SimonBlanke Jan 30, 2026
ea7dda1
add explanations
SimonBlanke Jan 30, 2026
f0e5947
change usage of settings to new impl.
SimonBlanke Jan 30, 2026
edcd006
add explanations
SimonBlanke Jan 30, 2026
cde0aae
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 30, 2026
edc5772
added tag and untag methods
Omswastik-11 Jan 31, 2026
aa1e560
move to config: APIVersion, ResourceType
geetu040 Feb 1, 2026
06b8497
remove api_context entirely
geetu040 Feb 1, 2026
384da91
major refactor
geetu040 Feb 1, 2026
1878138
more refactoring with setup/
geetu040 Feb 2, 2026
dc26e01
implement APIBackend as controller
geetu040 Feb 2, 2026
e2d059b
move enums
geetu040 Feb 2, 2026
d156ad4
module level imports
geetu040 Feb 2, 2026
d7a3788
module level import for _backend
geetu040 Feb 2, 2026
b5b9ef6
module level import for tests
geetu040 Feb 2, 2026
68820fe
Merge branch 'main' into migration
geetu040 Feb 2, 2026
567eca4
add test: test_tag_and_untag
geetu040 Feb 2, 2026
a82a636
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Feb 2, 2026
d39d9d0
Apply suggestion from @geetu040
Omswastik-11 Feb 2, 2026
724c4ae
add suggestion
Omswastik-11 Feb 2, 2026
0251f49
change test_publish_error to use test server
Omswastik-11 Feb 2, 2026
e213873
changed test_publish_error to use test server
Omswastik-11 Feb 2, 2026
b6f38cd
changed test_list_flows_empty to use test_server instead
Omswastik-11 Feb 2, 2026
e06c538
move flow migration test to test_api dir
Omswastik-11 Feb 2, 2026
fccb772
remove old caching used by get_flow
Omswastik-11 Feb 2, 2026
3d61b27
increase timout time
Omswastik-11 Feb 2, 2026
b2287c3
implement get/set_config_values
geetu040 Feb 3, 2026
b7e285e
improve APIBackend.set_config_values
geetu040 Feb 3, 2026
fd43c48
use LegacyConfig
geetu040 Feb 3, 2026
f4aab6b
Revert "use LegacyConfig"
geetu040 Feb 3, 2026
d43cf86
implement _sync_api_config
geetu040 Feb 3, 2026
3e323ed
update tests with _sync_api_config
geetu040 Feb 3, 2026
9195fa6
rename config: timeout -> timeout_seconds
geetu040 Feb 3, 2026
5342eec
use timedelta for default ttl value
geetu040 Feb 3, 2026
adc0e74
update tests, adds v2/fallback
geetu040 Feb 3, 2026
bfb2d3e
add MinIOClient in TestBase
geetu040 Feb 3, 2026
cabaecf
fix linting for builder
geetu040 Feb 3, 2026
d1da932
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Feb 3, 2026
671f077
correct the list method
Omswastik-11 Feb 4, 2026
85c1113
fix unbound variables: "code", "message"
geetu040 Feb 4, 2026
39bf86a
use requests.Session()
geetu040 Feb 4, 2026
7b66677
remove "timeout_seconds" entirely
geetu040 Feb 4, 2026
d2224c4
update/refactor tests
geetu040 Feb 4, 2026
9608c36
remove unused current_api_version from TestAPIBase
geetu040 Feb 5, 2026
f6bc7f7
make TestAPIBase inherit TestBase
geetu040 Feb 5, 2026
baa3a38
nits: test classes
geetu040 Feb 5, 2026
c9e4b73
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Feb 5, 2026
acfa2bb
correct the tests
Omswastik-11 Feb 6, 2026
63fa0a0
correct the tests
Omswastik-11 Feb 6, 2026
aa9d486
correct the tests
Omswastik-11 Feb 6, 2026
8802b8a
correct the tests
Omswastik-11 Feb 6, 2026
52b93fe
minor fix in _sync_api_config
geetu040 Feb 6, 2026
ec9477f
chore: rerun CI
geetu040 Feb 6, 2026
0c480da
replace old delete method tests
Omswastik-11 Feb 9, 2026
c7d9fe5
correct the tests
Omswastik-11 Feb 9, 2026
935b64e
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Feb 9, 2026
b273193
correct the tests
Omswastik-11 Feb 9, 2026
911f44d
correct the tests
Omswastik-11 Feb 9, 2026
10d134a
remove duplicates in _api/resources/__init__.py
geetu040 Feb 10, 2026
935f0f4
implement HTTPClient.download and add tests
geetu040 Feb 10, 2026
9514df8
add docstrings
geetu040 Feb 11, 2026
53bee94
update minio
geetu040 Feb 12, 2026
0e05a16
merge
Omswastik-11 Feb 12, 2026
7413191
correct the tests
Omswastik-11 Feb 12, 2026
33b4ca0
make delay functions static
geetu040 Feb 13, 2026
a6b9a45
rename: retry_raise_e -> exception
geetu040 Feb 13, 2026
f924b32
use context-manager for requests.Session
geetu040 Feb 13, 2026
541b0f2
remove "assert response is not None"
geetu040 Feb 13, 2026
acb173f
verify checksum before caching
geetu040 Feb 13, 2026
3e8d1f0
update tests
geetu040 Feb 13, 2026
f83bdb5
minor fix in ResourceV1API.untag
geetu040 Feb 13, 2026
2a42712
remove cache.ttl
geetu040 Feb 16, 2026
001caad
replace config.cache.dir with config.cache_dir
geetu040 Feb 16, 2026
fb38a2d
make HTTPClient.cache compulsory
geetu040 Feb 17, 2026
03c4ca9
remove unused OpenMLCacheRequiredError
geetu040 Feb 17, 2026
8d708fd
implement and use TestAPIBase._create_resource
geetu040 Feb 17, 2026
4f75bba
make ResourceAPI.minio compulsory
geetu040 Feb 17, 2026
164f66f
Merge branch 'main' into migration
geetu040 Feb 17, 2026
c4dae43
rename: use_cache -> enable_cache; reset_cache -> refresh_cache
geetu040 Feb 17, 2026
36c20a2
use server config from TestBase
geetu040 Feb 17, 2026
ab3c1eb
tests: mock HTTP post calls to prevent race conditions
geetu040 Feb 17, 2026
ff0220e
modify the flow to use latest caching
Omswastik-11 Feb 17, 2026
f8144e2
Merge branch 'main' into flow-migration-stacked
Omswastik-11 Feb 17, 2026
2a488ca
Merge branch 'main' into migration
geetu040 Feb 18, 2026
599c7e1
remove hardcoded server in TestHTTPClient.test_cache
geetu040 Feb 18, 2026
2867862
fix docstring in _resolve_default_cache_dir
geetu040 Feb 18, 2026
f09f3cd
fix docstring in ResourceAPI
geetu040 Feb 18, 2026
5f731ce
remove duplicates in __all__
geetu040 Feb 18, 2026
bad7842
remove ttl related code/docs
geetu040 Feb 18, 2026
aefdb38
remove delay methods in HTTPClient
geetu040 Feb 18, 2026
0f40b02
minor fix in _resolve_default_cache_dir
geetu040 Feb 18, 2026
7ac1672
update FallbackProxy
geetu040 Feb 18, 2026
6ac1dfe
simplify _backend creation
geetu040 Feb 18, 2026
62924c9
Merge branch 'main' into migration
geetu040 Feb 18, 2026
27696bb
req changes
satvshr Feb 20, 2026
190face
resolve conflicts
satvshr Feb 20, 2026
95daaa6
remove old config file
satvshr Feb 20, 2026
7841ea8
added OPENML_TEST_SERVER_ADMIN_KEY_ENV_VAR
satvshr Feb 20, 2026
cc515aa
bug fixing
satvshr Feb 20, 2026
e6a92df
armagh fix
satvshr Feb 20, 2026
1b8c22a
update content_type check
geetu040 Feb 20, 2026
fc839a6
Revert "make delay functions static"
geetu040 Feb 20, 2026
1c922af
Revert "remove delay methods in HTTPClient"
geetu040 Feb 20, 2026
ffa9ce9
Merge branch 'main' into migration
geetu040 Feb 20, 2026
a7b2d21
allow api_key=None
geetu040 Feb 20, 2026
27fe790
add tests for api_key=None
geetu040 Feb 20, 2026
8965112
update cache not found message
geetu040 Feb 23, 2026
72ea1a4
update docs for path in HTTPCache
geetu040 Feb 23, 2026
a696c49
remove elapsed from cached meta
geetu040 Feb 23, 2026
755636d
move self.headers to _HEADERS
geetu040 Feb 23, 2026
d07af34
fix indentation in docstrings of _resolve_default_cache_dir
geetu040 Feb 23, 2026
2d9c8ec
Update openml/_api/clients/http.py
geetu040 Feb 23, 2026
002b989
Merge branch 'main' into migration
geetu040 Feb 23, 2026
045d896
move _handle_delete_exception and_get_endpoint_name, legal_resources
geetu040 Feb 23, 2026
c437966
set HTTPClient.headers
geetu040 Feb 23, 2026
e27470a
remove main_tag
geetu040 Feb 23, 2026
d04d956
remove and merge TestAPIBase into TestBase
geetu040 Feb 23, 2026
9263f7f
minor change in TestHTTPClient.test_cache
geetu040 Feb 23, 2026
79dea29
make HTTPClient.request private
geetu040 Feb 23, 2026
f6497c2
Revert "update FallbackProxy"
geetu040 Feb 23, 2026
dce7f54
use st_ctime instead of st_ctime for cache refresh test
geetu040 Feb 23, 2026
40dd460
Merge branch 'main' into issue1564
geetu040 Feb 24, 2026
0fc917c
majore config refactor
geetu040 Feb 24, 2026
3d86b18
Merge branch 'pr-1577' into migration
geetu040 Feb 24, 2026
aba3d3e
update _config.py
geetu040 Feb 24, 2026
d99d54d
update test_openml_cache_dir_env_var
geetu040 Feb 24, 2026
dc22e3a
fix mutable SERVERS_REGISTRY
geetu040 Feb 25, 2026
7318573
update set_api_version for fallback
geetu040 Feb 25, 2026
29ef187
minor fix
geetu040 Feb 25, 2026
cf94c89
fixes for test_config
geetu040 Feb 25, 2026
298fbda
fixes in conftest urls
geetu040 Feb 25, 2026
9870502
update test_http.py
geetu040 Feb 25, 2026
33065c2
undo changes with test_openml_cache_dir_env_var
geetu040 Feb 25, 2026
76b92bb
fix server mode in test_config.py
geetu040 Feb 25, 2026
419edcb
move _HEADERS to confing
geetu040 Feb 25, 2026
cb6d937
add fixtures for migration tests
geetu040 Feb 25, 2026
8544c8a
update test_http.py with fixtures
geetu040 Feb 25, 2026
d4c413b
update test_versions.py
geetu040 Feb 25, 2026
fab1a15
update test_versions.py
geetu040 Feb 25, 2026
4e75b92
merge
Omswastik-11 Feb 25, 2026
49c6d59
modify the tests
Omswastik-11 Feb 25, 2026
af9f5e6
Merge branch 'flow-migration-stacked' of https://github.com/Omswastik…
Omswastik-11 Feb 25, 2026
276324a
fix error message in HTTPClient.server
geetu040 Feb 26, 2026
73f7594
fixes in test_versions.py: use DummyTaskAPI instead of TaskAPI
geetu040 Feb 26, 2026
2ee7fa3
add clients in openml._backend
geetu040 Feb 26, 2026
1954640
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Feb 26, 2026
4be5bbd
fixes with openml.config.[server|apikey] leakage
geetu040 Feb 26, 2026
9027c01
remove unused fixtures: use_api_[v1|v2]
geetu040 Feb 26, 2026
e5461a9
add more config tests
geetu040 Feb 27, 2026
7d899a9
make SERVERS_REGISTRY private
geetu040 Feb 27, 2026
8587414
fix marker: uses_test_server->test_server
geetu040 Feb 27, 2026
23a3450
fix UserWarning
geetu040 Feb 27, 2026
ac28f82
update fixture: with_server
geetu040 Feb 27, 2026
097e27c
merge
Omswastik-11 Feb 27, 2026
c28c808
add a fallback in v2
Omswastik-11 Feb 27, 2026
2d698ed
Update tests/test_flows/test_flow.py
Omswastik-11 Feb 27, 2026
6559219
fix the test_api_key name in test
Omswastik-11 Feb 27, 2026
9f3ca32
Merge branch 'flow-migration-stacked' of https://github.com/Omswastik…
Omswastik-11 Feb 27, 2026
ffe596d
remove authentication require ness for list
Omswastik-11 Feb 27, 2026
4a66245
req changes
satvshr Mar 2, 2026
c762fb4
Merge branch 'issue1564' of https://github.com/satvshr/openml-python …
satvshr Mar 2, 2026
77c21f2
Update openml/_api/clients/http.py
geetu040 Mar 4, 2026
eac24fc
Update tests/test_api/test_http.py
geetu040 Mar 4, 2026
2ed65fe
update test_get_uses_cached_response
geetu040 Mar 4, 2026
f3b07de
test_get_with_api_key
geetu040 Mar 4, 2026
29db3f1
use .arff instead of .bin in tests
geetu040 Mar 4, 2026
3b4e538
update test_download_creates_file to use md5_checksum
geetu040 Mar 4, 2026
8ac886b
update test_download_is_cached_on_disk
geetu040 Mar 4, 2026
305f4f0
update APIBackendBuilder
geetu040 Mar 4, 2026
b2bf164
Merge branch 'main' into migration
geetu040 Mar 4, 2026
e97e6c2
Update openml/_api/clients/http.py
geetu040 Mar 4, 2026
c66d73c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2026
aa54e8e
pre-commit fixes
geetu040 Mar 4, 2026
2d452d3
Merge branch 'main' into issue1564
geetu040 Mar 6, 2026
c235812
Merge branch 'main' into issue1564
fkiraly Mar 6, 2026
39eb823
Trigger CI
satvshr Mar 6, 2026
50eed37
Merge branch 'main' into migration
geetu040 Mar 6, 2026
7a000eb
Merge branch 'main' into issue1564
geetu040 Mar 10, 2026
79f6187
Merge branch 'main' into issue1564
geetu040 Mar 10, 2026
56de184
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Mar 11, 2026
b1a9e7f
Merge branch 'pr-1577' into migration (merge conflicts)
geetu040 Mar 12, 2026
d716ecf
update server methods in config
geetu040 Mar 12, 2026
3c29e71
fix api-version leakage in tests
geetu040 Mar 12, 2026
b4ff0b2
remove unused migration code
geetu040 Mar 12, 2026
93155ee
debug ci: separate cache for each test-case
geetu040 Mar 12, 2026
d3cc9a7
update port for localhost
geetu040 Mar 12, 2026
a6b82f4
Revert "debug ci: separate cache for each test-case"
geetu040 Mar 12, 2026
3419973
rerun CI
geetu040 Mar 12, 2026
8de99b7
Merge branch 'main' into migration
geetu040 Mar 12, 2026
45c85d9
Merge branch 'migration' of https://github.com/geetu040/openml-python…
Omswastik-11 Mar 16, 2026
7d61107
create enum ServerMode
geetu040 Mar 16, 2026
1ecbbba
update config for ServerMode
geetu040 Mar 16, 2026
65472ed
update tests for ServerMode
geetu040 Mar 16, 2026
44b48b5
udpate apikey in _TEST_SERVERS_LOCAL
geetu040 Mar 17, 2026
04bc83b
fix: remove duplicate server name in cache path
geetu040 Mar 23, 2026
f926092
test: remove check for ":" since windows CI expects it
geetu040 Mar 23, 2026
309f136
merge
Omswastik-11 Mar 23, 2026
d274cb5
Merge branch 'main' into flow-migration-stacked
Omswastik-11 Mar 23, 2026
2896b41
updated the tests
Omswastik-11 Mar 23, 2026
df67df1
Revert "updated the tests"
Omswastik-11 Mar 23, 2026
2a7e9ce
updated the tests
Omswastik-11 Mar 23, 2026
d0dc185
Merge branch 'flow-migration-stacked' of https://github.com/Omswastik…
Omswastik-11 Mar 23, 2026
3eabc19
updated the test_flow
Omswastik-11 Mar 23, 2026
299cbda
merge the changes
Omswastik-11 Mar 24, 2026
a942d52
Merge branch 'main' into flow-migration-stacked
Omswastik-11 Mar 24, 2026
b89a084
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions openml/_api/resources/base/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
from .base import ResourceAPI

if TYPE_CHECKING:
import pandas as pd

from openml.evaluations import OpenMLEvaluation
from openml.flows.flow import OpenMLFlow


class DatasetAPI(ResourceAPI):
Expand Down Expand Up @@ -63,6 +66,21 @@ class FlowAPI(ResourceAPI):

resource_type: ResourceType = ResourceType.FLOW

@abstractmethod
def get(self, flow_id: int, *, reset_cache: bool = False) -> OpenMLFlow: ...

@abstractmethod
def list(
self,
limit: int | None = None,
offset: int | None = None,
tag: str | None = None,
uploader: str | None = None,
) -> pd.DataFrame: ...

@abstractmethod
def exists(self, name: str, external_version: str) -> int | bool: ...


class StudyAPI(ResourceAPI):
"""Abstract API interface for study resources."""
Expand Down
295 changes: 293 additions & 2 deletions openml/_api/resources/flow.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,302 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

import pandas as pd
import xmltodict

from openml.exceptions import OpenMLServerError, OpenMLServerException
from openml.flows.flow import OpenMLFlow

from .base import FlowAPI, ResourceV1API, ResourceV2API


class FlowV1API(ResourceV1API, FlowAPI):
"""Version 1 API implementation for flow resources."""
def get(
self,
flow_id: int,
*,
reset_cache: bool = False,
) -> OpenMLFlow:
"""Get a flow from the OpenML server.

Parameters
----------
flow_id : int
The ID of the flow to retrieve.
reset_cache : bool, optional (default=False)
Whether to reset the cache for this request.

Returns
-------
OpenMLFlow
The retrieved flow object.
"""
response = self._http.get(
f"flow/{flow_id}",
enable_cache=True,
refresh_cache=reset_cache,
)
flow_xml = response.text
return OpenMLFlow._from_dict(xmltodict.parse(flow_xml))

Comment on lines +42 to +43
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlowV1API.get() does not detect v1-style error payloads (<oml:error>...) that are returned with HTTP 200. HTTPClient only validates by status code, so this method can end up passing an error dict into OpenMLFlow._from_dict() and failing with a confusing parsing error. Please add an <oml:error> check similar to exists() / list() and raise OpenMLServerException with the server-provided code/message.

Suggested change
return OpenMLFlow._from_dict(xmltodict.parse(flow_xml))
result_dict = xmltodict.parse(flow_xml)
# Detect v1-style error payloads and raise a clear exception
if "oml:error" in result_dict:
err = result_dict["oml:error"]
code = int(err.get("oml:code", 0)) if "oml:code" in err else None
message = err.get("oml:message", "Server returned an error")
raise OpenMLServerException(message=message, code=code)
return OpenMLFlow._from_dict(result_dict)

Copilot uses AI. Check for mistakes.
def exists(self, name: str, external_version: str) -> int | bool:
"""Check if a flow exists on the OpenML server.

Parameters
----------
name : str
The name of the flow.
external_version : str
The external version of the flow.

Returns
-------
int | bool
The flow ID if the flow exists, False otherwise.
"""
if not (isinstance(name, str) and len(name) > 0):
raise ValueError("Argument 'name' should be a non-empty string")
if not (isinstance(external_version, str) and len(external_version) > 0):
raise ValueError("Argument 'version' should be a non-empty string")

data = {"name": name, "external_version": external_version, "api_key": self._http.api_key}
xml_response = self._http.post("flow/exists", data=data).text
Comment on lines +64 to +65
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlowV1API.exists() calls HTTPClient.post() with the default use_api_key=True, which raises OpenMLAuthenticationError when no API key is configured. The legacy flow/exists endpoint worked without an API key (it only included api_key when present), so this is a behavior regression for anonymous users. Consider calling self._http.post(..., use_api_key=False) and omit api_key from the payload (or make auth optional).

Suggested change
data = {"name": name, "external_version": external_version, "api_key": self._http.api_key}
xml_response = self._http.post("flow/exists", data=data).text
data = {"name": name, "external_version": external_version}
if self._http.api_key:
data["api_key"] = self._http.api_key
xml_response = self._http.post("flow/exists", data=data, use_api_key=False).text

Copilot uses AI. Check for mistakes.
result_dict = xmltodict.parse(xml_response)
# Detect error payloads and raise
if "oml:error" in result_dict:
err = result_dict["oml:error"]
code = int(err.get("oml:code", 0)) if "oml:code" in err else None
message = err.get("oml:message", "Server returned an error")
raise OpenMLServerException(message=message, code=code)

flow_id = int(result_dict["oml:flow_exists"]["oml:id"])
return flow_id if flow_id > 0 else False

def list(
self,
limit: int | None = None,
offset: int | None = None,
tag: str | None = None,
uploader: str | None = None,
) -> pd.DataFrame:
"""List flows on the OpenML server.

Parameters
----------
limit : int, optional
The maximum number of flows to return.
By default, all flows are returned.
offset : int, optional
The number of flows to skip before starting to collect the result set.
By default, no flows are skipped.
tag : str, optional
The tag to filter flows by.
By default, no tag filtering is applied.
uploader : str, optional
The user to filter flows by.
By default, no user filtering is applied.

Returns
-------
pd.DataFrame
A DataFrame containing the list of flows.
"""
api_call = "flow/list"
if limit is not None:
api_call += f"/limit/{limit}"
if offset is not None:
api_call += f"/offset/{offset}"
if tag is not None:
api_call += f"/tag/{tag}"
if uploader is not None:
api_call += f"/uploader/{uploader}"

response = self._http.get(api_call, enable_cache=True)
xml_string = response.text
flows_dict = xmltodict.parse(xml_string, force_list=("oml:flow",))

if "oml:error" in flows_dict:
err = flows_dict["oml:error"]
code = int(err.get("oml:code", 0)) if "oml:code" in err else None
message = err.get("oml:message", "Server returned an error")
raise OpenMLServerException(message=message, code=code)

assert isinstance(flows_dict["oml:flows"]["oml:flow"], list), type(flows_dict["oml:flows"])
assert flows_dict["oml:flows"]["@xmlns:oml"] == "http://openml.org/openml", flows_dict[
"oml:flows"
]["@xmlns:oml"]

flows: dict[int, dict[str, Any]] = {}
for flow_ in flows_dict["oml:flows"]["oml:flow"]:
fid = int(flow_["oml:id"])
flow_row = {
"id": fid,
"full_name": flow_["oml:full_name"],
"name": flow_["oml:name"],
"version": flow_["oml:version"],
"external_version": flow_["oml:external_version"],
"uploader": flow_["oml:uploader"],
}
flows[fid] = flow_row

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

def publish(self, path: str | None = None, files: Mapping[str, Any] | None = None) -> int:
"""Publish a flow on the OpenML server.

Parameters
----------
files : Mapping[str, Any] | None
Files to upload (including description).

Returns
-------
int
The server-assigned flow id.
"""
path = "flow"
return super().publish(path, files)
Comment on lines +146 to +160
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path parameter in the publish method is marked as optional (str | None), but on line 159 it's immediately overwritten with the hardcoded value "flow". This makes the parameter pointless. Either remove the path parameter from the method signature entirely, or remove the line that overwrites it if the parameter should actually be used.

Copilot uses AI. Check for mistakes.


class FlowV2API(ResourceV2API, FlowAPI):
"""Version 2 API implementation for flow resources."""
def get(
self,
flow_id: int,
*,
reset_cache: bool = False,
) -> OpenMLFlow:
"""Get a flow from the OpenML v2 server.

Parameters
----------
flow_id : int
The ID of the flow to retrieve.
reset_cache : bool, optional (default=False)
Whether to reset the cache for this request.

Returns
-------
OpenMLFlow
The retrieved flow object.
"""
response = self._http.get(
f"flows/{flow_id}/",
enable_cache=True,
refresh_cache=reset_cache,
)
flow_json = response.json()

# Convert v2 JSON to v1-compatible dict for OpenMLFlow._from_dict()
flow_dict = self._convert_v2_to_v1_format(flow_json)
return OpenMLFlow._from_dict(flow_dict)

def exists(self, name: str, external_version: str) -> int | bool:
"""Check if a flow exists on the OpenML v2 server.

Parameters
----------
name : str
The name of the flow.
external_version : str
The external version of the flow.

Returns
-------
int | bool
The flow ID if the flow exists, False otherwise.
"""
if not (isinstance(name, str) and len(name) > 0):
raise ValueError("Argument 'name' should be a non-empty string")
if not (isinstance(external_version, str) and len(external_version) > 0):
raise ValueError("Argument 'version' should be a non-empty string")

try:
response = self._http.get(f"flows/exists/{name}/{external_version}/")
result = response.json()
Comment on lines +210 to +217
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlowV2API.exists() interpolates name and external_version directly into the URL path. These values can contain characters that require URL escaping, which can break requests or cause ambiguous routing. Quote/escape each path segment (e.g., urllib.parse.quote) or send them as query parameters if supported by the v2 endpoint.

Copilot uses AI. Check for mistakes.
flow_id: int | bool = result.get("flow_id", False)
return flow_id
except (OpenMLServerError, KeyError):
# v2 returns 404 when flow doesn't exist, which raises OpenMLServerError
return False
Comment on lines +220 to +222
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlowV2API.exists() currently catches OpenMLServerError and returns False, which will also hide real failures (e.g., authentication/authorization problems, 500s) by reporting "flow does not exist". Only treat the specific "not found" case as non-existence (e.g., catch OpenMLServerNoResult / an OpenMLServerException with the v2 not-found code or 404) and re-raise all other exceptions.

Suggested change
except (OpenMLServerError, KeyError):
# v2 returns 404 when flow doesn't exist, which raises OpenMLServerError
return False
except OpenMLServerError as err:
# v2 returns HTTP 404 when the flow doesn't exist; only treat that case
# as "not found" and propagate all other server errors.
if getattr(err, "code", None) == 404:
return False
raise

Copilot uses AI. Check for mistakes.

def list(
self,
limit: int | None = None, # noqa: ARG002
offset: int | None = None, # noqa: ARG002
tag: str | None = None, # noqa: ARG002
uploader: str | None = None, # noqa: ARG002
) -> pd.DataFrame:
self._not_supported(method="list")
Comment on lines +224 to +231
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the publish method, the list method in FlowV2API should have -> NoReturn as its return type instead of -> pd.DataFrame since it only calls self._not_supported() which never returns.

Copilot uses AI. Check for mistakes.

def publish(self, path: str | None = None, files: Mapping[str, Any] | None = None) -> int: # type: ignore[override] # noqa: ARG002
self._not_supported(method="publish")
Comment on lines +233 to +234
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The publish method signature in FlowV2API has a # type: ignore[override] comment, but this is because the return type doesn't match the parent class ResourceV2API.publish. However, the parent ResourceV2API.publish raises OpenMLNotSupportedError, so it never actually returns. The return type annotation -> int is misleading since the method always raises an exception. Consider using -> NoReturn as the return type or removing the return type annotation entirely since the method only raises.

Copilot uses AI. Check for mistakes.

@staticmethod
def _convert_v2_to_v1_format(v2_json: dict[str, Any]) -> dict[str, dict]:
"""Convert v2 JSON response to v1 XML-dict format for OpenMLFlow._from_dict().

Parameters
----------
v2_json : dict
The v2 JSON response from the server.

Returns
-------
dict
A dictionary matching the v1 XML structure expected by OpenMLFlow._from_dict().
"""
# Map v2 JSON fields to v1 XML structure with oml: namespace
flow_dict = {
"oml:flow": {
"@xmlns:oml": "http://openml.org/openml",
"oml:id": str(v2_json.get("id", "0")),
"oml:uploader": str(v2_json.get("uploader", "")),
"oml:name": v2_json.get("name", ""),
"oml:version": str(v2_json.get("version", "")),
"oml:external_version": v2_json.get("external_version", ""),
"oml:description": v2_json.get("description", ""),
"oml:upload_date": (
v2_json.get("upload_date", "").replace("T", " ")
if v2_json.get("upload_date")
else ""
),
"oml:language": v2_json.get("language", ""),
"oml:dependencies": v2_json.get("dependencies", ""),
}
}

# Add optional fields
if "class_name" in v2_json:
flow_dict["oml:flow"]["oml:class_name"] = v2_json["class_name"]
if "custom_name" in v2_json:
flow_dict["oml:flow"]["oml:custom_name"] = v2_json["custom_name"]

# Convert parameters from v2 array to v1 format
if v2_json.get("parameter"):
flow_dict["oml:flow"]["oml:parameter"] = [
{
"oml:name": param.get("name", ""),
"oml:data_type": param.get("data_type", ""),
"oml:default_value": str(param.get("default_value", "")),
"oml:description": param.get("description", ""),
}
for param in v2_json["parameter"]
]

# Convert subflows from v2 to v1 components format
if v2_json.get("subflows"):
flow_dict["oml:flow"]["oml:component"] = [
{
"oml:identifier": subflow.get("identifier", ""),
"oml:flow": FlowV2API._convert_v2_to_v1_format(subflow["flow"])["oml:flow"],
}
for subflow in v2_json["subflows"]
]

# Convert tags from v2 array to v1 format
if v2_json.get("tag"):
flow_dict["oml:flow"]["oml:tag"] = v2_json["tag"]

return flow_dict
36 changes: 33 additions & 3 deletions openml/flows/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import xmltodict

import openml
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing the top-level openml package from within openml.flows.flow increases the risk of circular-import issues (the package openml/__init__.py imports openml.flows, which imports this module). Since the new code only needs the backend, consider importing _backend (or APIBackend) directly from openml._api lazily inside methods, rather than import openml at module import time.

Copilot uses AI. Check for mistakes.
from openml.base import OpenMLBase
from openml.extensions import Extension, get_extension_by_flow
from openml.utils import extract_xml_tags
Expand Down Expand Up @@ -438,9 +439,14 @@ def publish(self, raise_error_if_exists: bool = False) -> OpenMLFlow: # noqa: F
raise openml.exceptions.PyOpenMLError(
"Flow does not exist on the server, but 'flow.flow_id' is not None.",
)
super().publish()
assert self.flow_id is not None # for mypy
flow_id = self.flow_id

file_elements = self._get_file_elements()
if "description" not in file_elements:
file_elements["description"] = self._to_xml()

# Use openml._backend.flow.publish which internally calls ResourceV1.publish
flow_id = openml._backend.flow.publish(path="flow", files=file_elements)
self.flow_id = flow_id
elif raise_error_if_exists:
error_message = f"This OpenMLFlow already exists with id: {flow_id}."
raise openml.exceptions.PyOpenMLError(error_message)
Expand Down Expand Up @@ -468,6 +474,30 @@ def publish(self, raise_error_if_exists: bool = False) -> OpenMLFlow: # noqa: F
) from e
return self

def push_tag(self, tag: str) -> None:
"""Annotates this flow with a tag on the server.

Parameters
----------
tag : str
Tag to attach to the flow.
"""
if self.flow_id is None:
raise ValueError("Flow does not have an ID. Please publish the flow before tagging.")
openml._backend.flow.tag(self.flow_id, tag)

Comment on lines +485 to +488
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenMLBase.push_tag() raises ObjectNotPublishedError when the entity has no server id. This override raises ValueError instead, which is an observable API behavior change for flows. Consider preserving the exception type/message (e.g., raise openml.exceptions.ObjectNotPublishedError here, or delegate to the base implementation) so tagging an unpublished flow behaves consistently with other OpenML entities.

Copilot uses AI. Check for mistakes.
def remove_tag(self, tag: str) -> None:
"""Removes a tag from this flow on the server.

Parameters
----------
tag : str
Tag to remove from the flow.
"""
if self.flow_id is None:
raise ValueError("Flow does not have an ID. Please publish the flow before untagging.")
openml._backend.flow.untag(self.flow_id, tag)
Comment on lines +477 to +499
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenMLFlow already inherits push_tag / remove_tag from OpenMLBase. Re-defining them here creates duplicated API paths and potentially inconsistent behavior across resource types (some entities tag via openml.utils._tag_openml_base, flows via openml._backend). Consider removing these overrides and updating the shared implementation in OpenMLBase to use the backend for all resources instead.

Copilot uses AI. Check for mistakes.
Comment on lines +497 to +499
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as push_tag(): raising ValueError for an unpublished flow changes the public exception contract compared to OpenMLBase.remove_tag() (which raises ObjectNotPublishedError). Align the exception type/message (or delegate to the base implementation) for consistency across entities.

Copilot uses AI. Check for mistakes.

def get_structure(self, key_item: str) -> dict[str, list[str]]:
"""
Returns for each sub-component of the flow the path of identifiers
Expand Down
Loading
Loading