Skip to content

[ENH] V1 -> V2 Migration - Flows (module)#1609

Open
Omswastik-11 wants to merge 249 commits intoopenml:mainfrom
Omswastik-11:flow-migration-stacked
Open

[ENH] V1 -> V2 Migration - Flows (module)#1609
Omswastik-11 wants to merge 249 commits intoopenml:mainfrom
Omswastik-11:flow-migration-stacked

Conversation

@Omswastik-11
Copy link
Contributor

@Omswastik-11 Omswastik-11 commented Jan 8, 2026

Fixes #1601

added a Create method in FlowAPI for publishing flow but not refactored with old publish . (Needs discussion on this)
Added tests using fake_methods so that we can test without local V2 server . I have tested the FlowsV2 methods (get and exists ) and delete and list were not implemented in V2 server so I skipped them .

@Omswastik-11 Omswastik-11 changed the title [ENH] V1 -> V2 Migration [ENH] V1 -> V2 Migration - Flows (module) Jan 8, 2026
@Omswastik-11 Omswastik-11 marked this pull request as ready for review January 8, 2026 15:09
@geetu040 geetu040 mentioned this pull request Jan 9, 2026
25 tasks
@codecov-commenter
Copy link

codecov-commenter commented Jan 12, 2026

Codecov Report

❌ Patch coverage is 64.04078% with 388 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.34%. Comparing base (da993f7) to head (ffe596d).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
openml/_config.py 68.66% 89 Missing ⚠️
openml/_api/clients/http.py 61.16% 87 Missing ⚠️
openml/_api/resources/base/versions.py 31.16% 53 Missing ⚠️
openml/_api/resources/flow.py 46.23% 50 Missing ⚠️
openml/_api/resources/base/fallback.py 26.31% 28 Missing ⚠️
openml/cli.py 0.00% 14 Missing ⚠️
openml/_api/resources/base/base.py 62.85% 13 Missing ⚠️
openml/_api/setup/backend.py 80.70% 11 Missing ⚠️
openml/flows/flow.py 21.42% 11 Missing ⚠️
openml/_api/setup/builder.py 81.08% 7 Missing ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1609      +/-   ##
==========================================
+ Coverage   53.09%   54.34%   +1.25%     
==========================================
  Files          37       61      +24     
  Lines        4362     5073     +711     
==========================================
+ Hits         2316     2757     +441     
- Misses       2046     2316     +270     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI review requested due to automatic review settings February 26, 2026 10:48
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 54 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

from typing import Any, Iterator
from pathlib import Path
import platform
from urllib.parse import urlparse
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.

urlparse is imported but never used in this test module. Please remove the unused import to avoid lint failures.

Copilot uses AI. Check for mistakes.
# Example script which will appear in the upcoming OpenML-Python paper
# This test ensures that the example will keep running!
with overwrite_config_context(
with openml.config.overwrite_config_context( # noqa: F823
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.

overwrite_config_context is referenced via openml.config and should be resolvable here, so the # noqa: F823 suppression looks incorrect/unnecessary. Please remove it (or use the correct code if there is an actual linter error to suppress).

Copilot uses AI. Check for mistakes.
Comment on lines +454 to 457
@mock.patch.object(requests.Session, "request")
def test_delete_flow_not_owned(mock_request, test_files_directory, test_api_key):
openml.config.start_using_configuration_for_example()
content_file = test_files_directory / "mock_responses" / "flows" / "flow_delete_not_owned.xml"
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.

start_using_configuration_for_example() mutates global configuration state; these tests never call stop_using_configuration_for_example(), which can leak state into later tests and cause order-dependent failures. Please wrap this in a context/fixture that guarantees stop_... runs (e.g., try/finally or a dedicated pytest fixture).

Copilot uses AI. Check for mistakes.
"apikey": "normaluser",
},
APIVersion.V2: {
"server": "http://localhost:8002/api/v1/xml/",
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.

SERVERS_REGISTRY['local'][APIVersion.V2]['server'] points to a v1 XML base path (/api/v1/xml/). This looks inconsistent with the v2 clients (which call endpoints like flows/.../ under an /api/v2/ base). Please correct the local v2 base URL (and/or add a test to validate v2 server base URLs).

Suggested change
"server": "http://localhost:8002/api/v1/xml/",
"server": "http://localhost:8002/api/v2/",

Copilot uses AI. Check for mistakes.
Comment on lines +226 to +232
if isinstance(id_value, (str, int)):
return int(id_value)

# Fallback: check all values for numeric/string IDs
for v in root_value.values():
if isinstance(v, (str, int)):
return int(v)
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.

In _extract_id_from_upload, the fallback loop attempts int(v) for the first string value it finds. This can raise ValueError on non-numeric strings like the XML namespace (e.g. "http://openml.org/openml"). Please restrict the fallback to numeric strings (or explicitly look for known keys) before casting to int.

Suggested change
if isinstance(id_value, (str, int)):
return int(id_value)
# Fallback: check all values for numeric/string IDs
for v in root_value.values():
if isinstance(v, (str, int)):
return int(v)
if isinstance(id_value, int):
return id_value
if isinstance(id_value, str):
id_str = id_value.strip()
if id_str.isdigit():
return int(id_str)
# Fallback: check all values for numeric/string IDs
for v in root_value.values():
if isinstance(v, int):
return v
if isinstance(v, str):
v_str = v.strip()
if v_str.isdigit():
return int(v_str)

Copilot uses AI. Check for mistakes.
Comment on lines +419 to +428
self._config = replace(
self._config,
servers=config["servers"],
api_version=config["api_version"],
fallback_api_version=config["fallback_api_version"],
show_progress=config["show_progress"],
avoid_duplicate_runs=config["avoid_duplicate_runs"],
retry_policy=config["retry_policy"],
connection_n_retries=int(config["connection_n_retries"]),
)
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.

_setup() assigns api_version and fallback_api_version directly from the parsed config dict. If these values come from a config file/CLI they will be strings (e.g. "v2"), which will break later lookups like servers[self.api_version] (servers keys are APIVersion). Please coerce string values to APIVersion (and validate) when loading config, and consider similarly validating/normalizing servers.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +43
return OpenMLFlow._from_dict(xmltodict.parse(flow_xml))

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.
path_parts = parsed_url.path.strip("/").split("/")

filtered_params = {k: v for k, v in params.items() if k != "api_key"}
params_part = [urlencode(filtered_params)] if filtered_params else []
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.

HTTPCache.get_key() uses urlencode(filtered_params) over the raw dict, which makes the cache key depend on the insertion order of params. This can cause avoidable cache misses for semantically identical requests. Please sort parameters (e.g., by key) before encoding to make cache keys stable.

Suggested change
params_part = [urlencode(filtered_params)] if filtered_params else []
sorted_params = sorted(filtered_params.items())
params_part = [urlencode(sorted_params)] if sorted_params else []

Copilot uses AI. Check for mistakes.
Comment on lines +477 to +499
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)

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)
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 +28 to +34
def dummy_task_v2(http_client_v2, minio_client) -> DummyTaskV1API:
return DummyTaskV2API(http=http_client_v2, minio=minio_client)


@pytest.fixture
def dummy_task_fallback(dummy_task_v1, dummy_task_v2) -> DummyTaskV1API:
return FallbackProxy(dummy_task_v2, dummy_task_v1)
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.

The fixture return type annotations in this file look incorrect: dummy_task_v2 is annotated as DummyTaskV1API but returns DummyTaskV2API, and dummy_task_fallback is annotated as DummyTaskV1API but returns FallbackProxy. Please fix the annotations to match the actual returned objects to avoid type-checking confusion.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 27, 2026 13:00
Co-authored-by: Armaghan Shakir <raoarmaghanshakir040@gmail.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 57 out of 58 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +35
@pytest.fixture
def dummy_task_v2(http_client_v2, minio_client) -> DummyTaskV1API:
return DummyTaskV2API(http=http_client_v2, minio=minio_client)


@pytest.fixture
def dummy_task_fallback(dummy_task_v1, dummy_task_v2) -> DummyTaskV1API:
return FallbackProxy(dummy_task_v2, dummy_task_v1)

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.

Fixture return type annotations are inconsistent: dummy_task_v2 is annotated as returning DummyTaskV1API but returns DummyTaskV2API, and dummy_task_fallback is annotated as DummyTaskV1API but returns a FallbackProxy. Fix the annotations to match the actual return types to avoid confusing readers and type checkers.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to 34
import requests
from openml.testing import SimpleImputer, TestBase, create_request_response

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.

create_request_response is imported but unused in this file. Either remove the import or use it to build the mocked Response objects for consistency with other tests.

Copilot uses AI. Check for mistakes.
OpenMLHashException
If checksum verification fails.
"""
url = urljoin(self.server, path)
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.

urljoin(self.server, path) will generate incorrect URLs if self.server does not end with a trailing / (e.g. base .../api/v1/xml + task/1 becomes .../api/v1/task/1). Existing user config files and tests still set servers without the trailing slash, so requests can silently hit the wrong endpoint. Normalize server to always end with / (e.g. in config setup/server setter) or avoid urljoin here and manually ensure exactly one / separator.

Suggested change
url = urljoin(self.server, path)
if path.startswith(("https://", "https://")):
url = path
else:
base = self.server.rstrip("/")
url = f"{base}/{path.lstrip('/')}"

Copilot uses AI. Check for mistakes.
Comment on lines +136 to 137
flow_xml = openml._backend.http_client.get(f"flow/{flow_id}").text
flow_dict = xmltodict.parse(flow_xml)
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.

openml.config.get_backend() does not exist (config is now an OpenMLConfigManager instance). This will raise an AttributeError and break the test. Use the new backend entrypoint (openml._backend.http_client.get(...) or openml._backend.flow.get(...)) instead of calling a non-existent config method.

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +206
openml.config.server = "temp-server1"
openml.config.apikey = "temp-apikey1"
openml.config.get_servers(mode)["server"] = 'temp-server2'
openml.config.get_servers(mode)["apikey"] = 'temp-server2'

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.

get_servers() returns a dict keyed by APIVersion, but this test indexes it with string keys (["server"]/["apikey"]), which will raise KeyError. To test deepcopy/immutability, mutate openml.config.get_servers(mode)[api_version]["server"] (and apikey) instead.

Copilot uses AI. Check for mistakes.
Comment on lines 113 to 131
@pytest.mark.production_server()
def test_switch_to_example_configuration(self):
"""Verifies the test configuration is loaded properly."""
# Below is the default test key which would be used anyway, but just for clarity:
openml.config.apikey = "any-api-key"
openml.config.server = self.production_server
openml.config.set_servers("production")

openml.config.start_using_configuration_for_example()

assert openml.config.apikey == TestBase.user_key
assert openml.config.server == self.test_server
openml.config.servers = openml.config.get_servers("test")

@pytest.mark.production_server()
def test_switch_from_example_configuration(self):
"""Verifies the previous configuration is loaded after stopping."""
# Below is the default test key which would be used anyway, but just for clarity:
openml.config.apikey = TestBase.user_key
openml.config.server = self.production_server
openml.config.set_servers("production")

openml.config.start_using_configuration_for_example()
openml.config.stop_using_configuration_for_example()

assert openml.config.apikey == TestBase.user_key
assert openml.config.server == self.production_server
openml.config.servers = openml.config.get_servers("production")

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 example-configuration tests no longer assert that start_using_configuration_for_example() actually switched to the test config (or that stopping restores the previous config). They currently just overwrite openml.config.servers manually, which defeats the purpose of the test and could hide regressions. Add assertions comparing openml.config.servers (and/or server/apikey) before/after start/stop instead of mutating the config inside the test.

Copilot uses AI. Check for mistakes.
Comment on lines 23 to +59
@@ -33,6 +35,7 @@
utils,
)
from .__version__ import __version__
from ._api import _backend
from .datasets import OpenMLDataFeature, OpenMLDataset
from .evaluations import OpenMLEvaluation
from .flows import OpenMLFlow
@@ -49,6 +52,11 @@
OpenMLTask,
)

if TYPE_CHECKING:
from ._config import OpenMLConfigManager

config: OpenMLConfigManager = _config_module.__config

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 package-level change makes openml.config an instance attribute and removes the openml/config.py module. This is a breaking change for users who do import openml.config or from openml.config import .... If backward compatibility is desired, consider reintroducing a thin openml/config.py shim that re-exports the config manager (and legacy symbols) so existing imports keep working.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to 13
from urllib.parse import urlparse

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.

from urllib.parse import urlparse is imported but never used in this test module, which will fail linting in configurations that enforce unused-import checks. Remove the import or use it in the tests.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 27, 2026 13:48
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 57 out of 58 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +233 to +234
def publish(self, path: str | None = None, files: Mapping[str, Any] | None = None) -> int: # type: ignore[override] # noqa: ARG002
self._not_supported(method="publish")
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.
Comment on lines +224 to +231
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")
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.
Comment on lines +146 to +160
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)
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.
Comment on lines +48 to +49
Whether to ignore the cache. If ``true`` this will download and overwrite the flow xml
even if the requested flow is already cached.
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 docstring states "If true this will download..." but the parameter is ignore_cache, not true. It should say "If True, this will download..." or "If set to True, this will download..." for consistency with Python boolean conventions.

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +258
def publish(self, path: str, files: Mapping[str, Any] | None) -> int: # noqa: ARG002
self._not_supported(method="publish")

def delete(self, resource_id: int) -> bool: # noqa: ARG002
self._not_supported(method="delete")

def tag(self, resource_id: int, tag: str) -> list[str]: # noqa: ARG002
self._not_supported(method="tag")

def untag(self, resource_id: int, tag: str) -> list[str]: # noqa: ARG002
self._not_supported(method="untag")
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 return type annotation for these methods in ResourceV2API is incorrect. Since these methods call self._not_supported() which has a return type of NoReturn, these methods should also have -> NoReturn as their return type instead of -> int, -> bool, or -> list[str]. The # noqa: ARG002 comment suggests awareness that the arguments are unused, but the return type should also reflect that these methods never return.

Copilot uses AI. Check for mistakes.
"openml_logger",
"_examples",
"OPENML_CACHE_DIR_ENV_VAR",
"OPENML_SKIP_PARQUET_ENV_VAR",
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 attribute OPENML_TEST_SERVER_ADMIN_KEY_ENV_VAR is missing from the allowed set in __setattr__ on line 166-177. This could cause issues if code tries to set this attribute. It should be added to the set that includes OPENML_CACHE_DIR_ENV_VAR and OPENML_SKIP_PARQUET_ENV_VAR.

Suggested change
"OPENML_SKIP_PARQUET_ENV_VAR",
"OPENML_SKIP_PARQUET_ENV_VAR",
"OPENML_TEST_SERVER_ADMIN_KEY_ENV_VAR",

Copilot uses AI. Check for mistakes.
self._HEADERS: dict[str, str] = {"user-agent": f"openml-python/{__version__}"}

self._config: OpenMLConfig = OpenMLConfig()
# for legacy test `test_non_writable_home`
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.

In line 147, there's a comment "# for legacy test test_non_writable_home" but the _defaults attribute is also used in other methods like set_field_in_config_file (line 461). The comment is misleading as it suggests this is only for one specific test when it may have broader usage. Consider updating the comment or verifying if _defaults is truly only needed for that one test.

Suggested change
# for legacy test `test_non_writable_home`
# snapshot of default config (used for resets, e.g. in legacy tests like `test_non_writable_home`)

Copilot uses AI. Check for mistakes.

def _mocked_perform_api_call(call, request_method):
url = openml.config.server + "/" + call
url = openml.config.server + call
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 URL construction has a spacing issue. There's an extra space before the + operator which results in inconsistent concatenation. The line should be url = openml.config.server + call without the extra space before the + operator.

Copilot uses AI. Check for mistakes.
TestBase._mark_entity_for_removal("flow", flow.flow_id, flow.name)
TestBase.logger.info(f"collected from {__file__.split('/')[-1]}: {flow.flow_id}")


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.

There's a trailing whitespace at the end of line 301. Please remove the whitespace after the comment to maintain code cleanliness.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENH] V1 → V2 API Migration - flows

7 participants