Python client migration guide
This guide compares the H2O MLOps Python client across versions. Each table shows how to perform an operation in the earlier version (left column) and in the later version (right column). Use these comparisons to update your code.
From v1.5.x to v1.6.x
uid removed as a list() and count() selector
uid identifies a single entity, so it's no longer a list() or count() selector. Call get() instead. count(uid=...) has no replacement; to check existence, call get() and catch errors.MLOpsNotFoundError.
| v1.5.x | v1.6.x |
|---|
entities.list(uid=...)[0]
| |
entities.count(uid=...) > 0
| try:
entities.get(uid=...)
except errors.MLOpsNotFoundError:
...
|
Not affected: artifacts, experiments, datasets, and monitoring_aggregates, where uid still works.
Removed list() and count() support for some selectors
v1.6.x no longer accepts a few selector keys and raises a ValueError for each. Selector-based filtering by supported fields, such as name, state, and created_time, still works.
| v1.5.x | v1.6.x |
|---|
workspace.runtimes.list(runtime_image=...)
workspace.runtime_images.list(docker_image=...)
workspace.runtime_images.versions.list(version=...)
workspace.batch_scoring_jobs.list(
monitoring_state=...,
)
deployment.monitoring.baseline_aggregates.list(
is_model_output=...,
)
| No replacement. |
| v1.5.x | v1.6.x |
|---|
workspace.experiments.list(tags=...)
| workspace.experiments.list(tag=...)
|
workspace.datasets.list(tags=...)
| workspace.datasets.list(tag=...)
|
opts is now the first positional argument of experiments.list() and datasets.list(); pass additional_metadata by keyword instead.
| v1.5.x | v1.6.x |
|---|
workspace.experiments.list(
["metadata_key"],
)
| workspace.experiments.list(
additional_metadata=["metadata_key"],
)
|
workspace.datasets.list(
["metadata_key"],
)
| workspace.datasets.list(
additional_metadata=["metadata_key"],
)
|
artifact.model_info removed
If the artifact has an associated experiment, use it instead.
| v1.5.x | v1.6.x |
|---|
artifact.model_info["artifact_uid"]
| |
artifact.model_info["artifact_type"]
| |
artifact.model_info["model_metadata"]
| |
artifact.model_info["model_parameters"]
| |
workspace.events removed
| v1.5.x | v1.6.x |
|---|
| No replacement. |
options.BatchKubernetesOptions.min_replicas removed
| v1.5.x | v1.6.x |
|---|
options.BatchKubernetesOptions(
...,
replicas=...,
min_replicas=...,
)
| options.BatchKubernetesOptions(
...,
replicas=...,
)
|
experiment.parameters return type changed
| v1.5.x | v1.6.x |
|---|
params = experiment.parameters
| params = experiment.parameters
|
isinstance(
params, dict,
)
| isinstance(
params, options.ExperimentParameters | None,
)
|
| |
exclude_deleted removed from artifacts.list()
The default still excludes deleted artifacts. To fetch deleted artifacts, filter explicitly.
| v1.5.x | v1.6.x |
|---|
workspace.artifacts.list(
exclude_deleted=False,
)
| workspace.artifacts.list(
state="DELETED",
)
|
Tag join separator changed in list() results
Applies to experiments.list() and datasets.list().
options.Column.is_model_output default changed
is_model_output now defaults to None, and MonitoringOptions infers it from the column's placement in input_columns or output_columns. An explicit value that disagrees with the placement raises a ValueError.
| v1.5.x | v1.6.x |
|---|
options.MonitoringOptions(
...,
input_columns=[
...,
options.Column(
name=...,
is_model_output=False,
),
],
output_columns=[
...,
options.Column(
name=...,
is_model_output=True,
),
],
)
| options.MonitoringOptions(
...,
input_columns=[
...,
options.Column(
name=...,
),
],
output_columns=[
...,
options.Column(
name=...,
),
],
)
|
deployment.security_options deprecated
v1.6.x deprecates security_options in favor of securities_options, which returns a list. update() and create() now accept a single SecurityOptions or a list: replace the whole configuration, or add, edit, or delete individual entries with action. A delete needs only uid and action, no security_type.
| v1.5.x | v1.6.x |
|---|
deployment.security_options
| deployment.securities_options
|
deployment.update(
...,
security_options=options.SecurityOptions(...),
)
| deployment.update(
...,
security_options=[
...,
options.SecurityOptions(
uid=...,
action=types.SecurityAction.DELETE,
),
],
)
|
workspace.deployments.create(
...,
security_options=options.SecurityOptions(...),
)
| workspace.deployments.create(
...,
security_options=[
...,
options.SecurityOptions(...),
],
)
|
deployment.is_paused semantics changed
v1.6.x narrows is_paused to the explicit pause flag set by pause() and cleared by resume(). A deployment at zero replicas for any other reason, such as a scale-down update, a schedule, or on-demand mode, no longer reports is_paused as True; check state instead.
| v1.5.x | v1.6.x |
|---|
| |
| deployment.state == "STOPPED"
|
id_field no longer requires output_fields_type=INCLUDE_ID
In v1.5.x, setting id_field on options.ModelRequestParameters without output_fields_type=INCLUDE_ID raised a ValueError. v1.6.x drops that restriction: id_field alone or with INCLUDE_ID works; it only conflicts with output_fields_type=INCLUDE_ALL_INPUT_FEATURES.
| v1.5.x | v1.6.x |
|---|
options.ModelRequestParameters(
id_field="row_id",
output_fields_type=types.OutputFieldsType.INCLUDE_ID,
)
| options.ModelRequestParameters(
id_field="row_id",
)
|
New types.SecurityType.API_KEY member
An exhaustive if, elif, or match chain over SecurityType without a default branch does not handle the new member.
| v1.5.x | v1.6.x |
|---|
Not available in v1.5.x. | types.SecurityType.API_KEY
|
New exception hierarchy
wait_for_healthy() and wait_for_ready() now raise errors.MLOpsTimeoutError, which subclasses both MLOpsError and TimeoutError, so except TimeoutError still works, though the message text changed. Also new: MLOpsError as the base, MLOpsApiError and its subclasses MLOpsAuthError, MLOpsNotFoundError, MLOpsConflictError, MLOpsRateLimitError, and MLOpsServerError, plus MLOpsScoringError and MLOpsModelIngestionError.
| v1.5.x | v1.6.x |
|---|
try:
deployment.wait_for_healthy(
timeout=60,
)
except TimeoutError:
...
| try:
deployment.wait_for_healthy(
timeout=60,
)
except errors.MLOpsTimeoutError as e:
print(
f"timed out after {e.elapsed:.0f}s, "
f"last state: {e.last_state}"
)
|
workspace.experiments.create() may raise ingestion errors
A model ingestion failure now raises errors.MLOpsModelIngestionError, and a timeout raises errors.MLOpsTimeoutError. The new ingestion_timeout and ingestion_poll_interval keyword arguments control the wait.
| v1.5.x | v1.6.x |
|---|
workspace.experiments.create(...)
| workspace.experiments.create(
...,
ingestion_timeout=...,
ingestion_poll_interval=...,
)
|
Monitoring helpers log instead of warn
The helpers now log skipped-column conditions to the h2o_mlops.monitoring logger instead of raising a UserWarning that callers can catch.
| v1.5.x | v1.6.x |
|---|
with warnings.catch_warnings(record=True) as w:
utils.monitoring.prepare_monitoring_options_from_data_frame(...)
| h2o_mlops.enable_logging(level="WARNING")
utils.monitoring.prepare_monitoring_options_from_data_frame(...)
|
FileExistsError message changed in artifact.download()
The new message is Artifact destination already exists; pass overwrite=True to replace it. In v1.5.x, the client also printed the message to stdout.
The old message was a multi-line "<msg>\n\nCaused by\n\n<error_msg>" string. v1.6.x raises errors.MLOpsScoringError, which is also an httpx.HTTPStatusError, with the structured fields status, message, and server_message.
| v1.5.x | v1.6.x |
|---|
try:
scorer.score(payload=...)
except httpx.HTTPStatusError as e:
print(e)
| try:
scorer.score(payload=...)
except errors.MLOpsScoringError as e:
print(
f"scoring failed: {e.status} "
f"{e.message} ({e.server_message})"
)
|
timestamp selector semantics changed for monitoring aggregates
The selector changed from an exact display-string match to a real backend filter value.
| v1.5.x | v1.6.x |
|---|
deployment.monitoring.scoring_aggregates.list(
timestamp="2024-01-01 12:00:00 AM",
)
| deployment.monitoring.scoring_aggregates.list(
timestamp=datetime.datetime(
2024, 1, 1, tzinfo=datetime.timezone.utc,
),
)
|
From v1.4.x to v1.5.x
Use Secure Store Secret IDs for batch scoring credentials
In v1.5.x, secret fields in batch scoring source and sink configurations must reference Secure Store Secret IDs rather than raw sensitive values. Pass the secret's key (Secret ID) in the config dict instead of the raw credential.
This applies to both BatchSourceOptions and BatchSinkOptions.
| v1.4.x | v1.5.x |
|---|
options.BatchSourceOptions(
spec_uid="...",
config={"password": "my-raw-password"},
...,
)
| secret = workspace.secrets.create(
name="batch-source-password",
key="MY_PASSWORD",
value=b"my-raw-password",
)
options.BatchSourceOptions(
spec_uid="...",
config={"password": secret.key},
...,
)
|
Use new monitoring property for baselines and aggregates
v1.5.0 deprecates these methods, and they still work in v1.6.x. Use the new deployment.monitoring API instead.
| v1.4.x | v1.5.x |
|---|
deployment.list_baselines()
| deployment.monitoring.baseline_aggregates.list()
|
deployment.list_aggregates()
| deployment.monitoring.scoring_aggregates.list()
|
Rename scorer HTTP-based health-check methods
In v1.4.x, scorer.state() and scorer.is_ready() are HTTP-based methods that ping the scorer's /readyz endpoint directly. v1.5.x renames these methods to better reflect their purpose, and the names state and is_ready are now used for API-driven properties (see the next section).
| v1.4.x | v1.5.x |
|---|
scorer.state(auth_value=...)
| scorer.readiness(auth_value=...)
|
scorer.is_ready(auth_value=...)
| scorer.is_reachable(auth_value=...)
|
Access scorer state and readiness via API-based properties
In v1.5.x, scorer.state and scorer.is_ready are read-only properties that query the MLOps API for the scorer's routing state. They are not the same as the old HTTP-based methods of the same name (renamed to readiness and is_reachable in v1.5.x — see the previous section).
| v1.4.x | v1.5.x |
|---|
Not available in v1.4.x. | scorer.state
scorer.is_ready
scorer.wait_for_ready(
timeout=60, interval=5, fail_fast=True
)
scorer.raise_for_error()
|
Handle entities' state properties
In v1.4.x, any entity's state property returned a proto enum object, which is a subclass of str. In v1.5.x, all state properties return a plain Python string. The string values themselves remain the same, so comparisons against string literals still work, but any code that relied on the object being an enum will break.
| v1.4.x | v1.5.x |
|---|
entity.state == "SOME_STATE"
| entity.state == "SOME_STATE"
|
isinstance(entity.state, SomeEnum)
| isinstance(entity.state, SomeEnum)
|
Pair id_field with output_fields_type
v1.5.x adds a new output_fields_type field to ModelRequestParameters. In v1.5.0 through v1.5.3, setting id_field without output_fields_type=OutputFieldsType.INCLUDE_ID raises a ValueError. Update code that sets only id_field. v1.6.x drops this requirement.
| v1.4.x | v1.5.x |
|---|
import h2o_mlops.options as options
params = options.ModelRequestParameters(
id_field="row_id",
contributions=...,
prediction_intervals=...,
)
| import h2o_mlops.options as options
import h2o_mlops.types as types
params = options.ModelRequestParameters(
id_field="row_id",
output_fields_type=types.OutputFieldsType.INCLUDE_ID,
contributions=...,
prediction_intervals=...,
)
|
Expect auto-refresh from job.state
In v1.4.x, accessing job.state returns the cached value with no network call. In v1.5.x, job.state calls job.refresh() automatically every time it is accessed, making a network call to get the current value.
Add text_aggregate to BaselineData
v1.5.x inserts a text_aggregate field between categorical_aggregate and missing_values. Code using keyword arguments is unaffected, but positional-argument construction must be updated.
| v1.4.x | v1.5.x |
|---|
options.BaselineData(
column_name,
logical_type,
numerical_aggregate,
categorical_aggregate,
missing_values,
is_model_output,
)
| options.BaselineData(
column_name,
logical_type,
numerical_aggregate,
categorical_aggregate,
text_aggregate,
missing_values,
is_model_output,
)
|
Rename of MLOpsRuntime
In v1.5.x, the MLOpsRuntime NamedTuple has been renamed to MLOpsScoringRuntimeInfo. A backward-compatibility alias is provided, so existing code using MLOpsRuntime continues to work.
| v1.4.x | v1.5.x |
|---|
r: MLOpsRuntime = scoring_runtime.runtime
| r: MLOpsScoringRuntimeInfo = scoring_runtime.runtime
|
From v1.3.x to v1.4.x
Imports
| v1.3.x | v1.4.x |
|---|
import time
import httpx
import h2o_authn
import h2o_mlops
import h2o_mlops.options as options
| import h2o_mlops
import h2o_mlops.options as options
import h2o_mlops.types as types
|
Client creation
From v1.4.x onwards, support for creating the client using gateway_url and token_provider has been removed. Instead, you must use refresh_token and h2o_cloud_url.
| v1.3.x | v1.4.x |
|---|
token_provider = h2o_authn.TokenProvider(
refresh_token=...,
client_id=...,
token_endpoint_url=...,
)
mlops = h2o_mlops.Client(
gateway_url=...,
token_provider=token_provider,
)
| mlops = h2o_mlops.Client(
h2o_cloud_url=<H2O_CLOUD_URL>,
refresh_token=<REFRESH_TOKEN>,
)
|
Get allowed affinities and tolerations
| v1.3.x | v1.4.x |
|---|
| mlops.configs.allowed_k8s_affinities
|
mlops.allowed_tolerations
| mlops.configs.allowed_k8s_tolerations
|
Get the current user
| v1.3.x | v1.4.x |
|---|
| |
Returns the user's information as a Python dictionary. | Returns the user's information as an MLOpsUser instance. |
In version 1.4.x, the concept of projects has been replaced by workspaces. Update your code by replacing references to projects with workspaces.
| v1.3.x | v1.4.x |
|---|
mlops.projects.<action>()
| mlops.workspaces.<action>()
|
Create and register an experiment into a model
The previous method of creating experiments and registering them with models is still supported.
| v1.3.x | v1.4.x |
|---|
experiment = project.experiments.create(
data=..., name=...
)
model = project.models.create(name=...)
or
model = project.models.get(uid=...)
model.register(experiment=experiment)
| model.register(
experiment="/path/experiment.zip",
name=...,
)
or
workspace.models.register(
experiment="/path/experiment.zip",
name=...,
)
Users can pass an instance of the MLOpsExperiment as well. |
- When you link an experiment to a workspace from H2O Driverless AI, a new model version is automatically registered under the model that matches the experiment’s name.
- If no matching model exists, a new model is created with the experiment name, and the experiment is registered as its first version.
- Therefore, you don’t need to manually register experiments in MLOps. You can use the model directly.
Update an artifact’s parent
| v1.3.x | v1.4.x |
|---|
artifact.update(
parent_experiment=experiment,
)
| artifact.update(
parent_entity=experiment,
)
|
| v1.3.x | v1.4.x |
|---|
artifact.get_model_info()
| |
Convert JSON artifact to a dictionary
Get the experiment associated with a model version
| v1.3.x | v1.4.x |
|---|
model.get_experiment(model_version=n)
| model.experiment(model_version=n)
|
List scoring runtimes
The experiment.scoring_artifact_types property was removed in 1.4.x.
| v1.3.x | v1.4.x |
|---|
scoring_runtimes = mlops.runtimes.scoring.list(
artifact_type=experiment.scoring_artifact_types[correct_index]
)
| scoring_runtimes = experiment.scoring_runtimes
|
scoring_runtimes = mlops.runtimes.scoring.list(
artifact_type=..., uid=...
)
| scoring_runtimes = mlops.runtimes.scoring.list(
artifact_type=..., runtime_uid=...
)
|
When creating a deployment, instead of passing scoring_runtimes[correct_index], you can use mlops.runtimes.scoring.get(artifact_type=..., runtime_uid=...) to get the scoring_runtime, if you already know the corresponding artifact_type and runtime_uid.
Create a deployment
| v1.3.x | v1.4.x |
|---|
project.deployments.create_single(
name=...,
model=...,
scoring_runtime=...,
security_options=options.SecurityOptions(
passphrase=...,
hashed_passphrase=...,
disabled_security=...,
oidc_token_auth=...,
),
)
| workspace.deployments.create(
name=...,
composition_options=options.CompositionOptions(
model=...,
scoring_runtime=...,
),
security_options=options.SecurityOptions(
security_type=types.SecurityType.<TYPE>,
passphrase=...,
),
)
|
Starting in v1.4.x, when you create a deployment with hash-based security options, provide the passphrase directly. In earlier versions, you had to provide the hashed value instead.
Create a deployment with new model monitoring options
| v1.3.x | v1.4.x |
|---|
project.deployments.create_single(
...,
monitoring_record_options=options.MonitoringRecordOptions(
...,
),
)
| workspace.deployments.create(
...,
monitoring_options=options.MonitoringOptions(
...,
),
)
|
This is equivalent to how users created deployments with the old monitoring in the previous client. After the old monitoring was removed, this change was introduced. Note that the parameters accepted by options.MonitoringOptions differ from those used in the old monitoring.
Wait for deployment to become healthy
The previous method is still supported.
| v1.3.x | v1.4.x |
|---|
while not deployment.is_healthy():
deployment.raise_for_failure()
time.sleep(5)
| deployment.wait_for_healthy()
|
Get deployment state
Update a deployment
| v1.3.x | v1.4.x |
|---|
deployment.update_security_options(
...,
)
| deployment.update(
security_options=options.SecurityOptions(
...,
),
kubernetes_options=options.KubernetesOptions(
...,
),
environment_variables={
"KEY1": "VALUE1",
"KEY2": "VALUE2",
},
monitoring_options=options.MonitoringOptions(
...,
),
)
|
deployment.update_kubernetes_options(
...,
)
|
deployment.set_environment_variables(
environment_variables={
"KEY1": "VALUE1",
"KEY2": "VALUE2",
},
)
|
deployment.update_monitoring_options(
...,
)
|
In v1.4.x, you can update multiple settings at once.
Access deployment scorer
| v1.3.x | v1.4.x |
|---|
You do not need to fetch the scorer. | scorer = deployment.scorer
or
scorer = workspace.deployments.scorers(
key=value,
)[index]
|
deployment.scorer_api_base_url
| |
deployment.url_for_capabilities
| scorer.capabilities_endpoint
|
deployment.url_for_schema
| |
deployment.url_for_sample_request
| scorer.sample_request_endpoint
|
deployment.url_for_scoring
| |
deployment.get_capabilities(...)
| |
deployment.get_schema(...)
| |
deployment.get_sample_request(...)
| scorer.sample_request(...)
|
Score against a deployment
The previous method is still supported if the correct scoring endpoint URL is provided.
| v1.3.x | v1.4.x |
|---|
response = httpx.post(
url=deployment.url_for_scoring,
json=...,
)
response.json()
| scorer.score(payload=...)
|
Kubernetes options for a batch scoring job
| v1.3.x | v1.4.x |
|---|
project.batch_scoring_jobs.create(
...,
resource_spec=options.BatchKubernetesOptions(
...,
),
)
| workspace.batch_scoring_jobs.create(
...,
kubernetes_options=options.BatchKubernetesOptions(
...,
),
)
|
| |
Get entity creator (if applicable)
View the complete Table
In version 1.4.x, a Table instance renders a nicely formatted view but displays only up to 50 rows by default.
From v1.2.x to v1.3.x
Removal of environments
| v1.2.x | v1.3.x |
|---|
environment = project.environments.get(uid=...)
| You do not need to fetch the environment. |
| |
| |
environment.allowed_affinities
| |
environment.allowed_tolerations
| mlops.allowed_tolerations
|
From v1.1.x to v1.2.x
There are no breaking changes.
From v1.0.x to v1.1.x
Minimal supported version
Create a deployment
| v1.0.x | v1.1.x |
|---|
project.deployments.create_single(
name=...,
model=...,
scoring_runtime=...,
)
| project.deployments.create_single(
name=...,
model=...,
scoring_runtime=...,
security_options=options.SecurityOptions(
passphrase=...,
hashed_passphrase=...,
disabled_security=...,
oidc_token_auth=...,
),
)
|
- The
security_options field is no longer optional.
- To create a deployment with the
No Security option:
- For MLOps version 0.68.0 or later, set:
security_options = options.SecurityOptions(disabled_security=True)
- For MLOps versions earlier than 0.68.0, set:
security_options = options.SecurityOptions()