Skip to main content
Version: v1.2.0

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.xv1.6.x
entities.list(uid=...)[0]
entities.get(uid=...)
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.xv1.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.

tags selector renamed to tag

v1.5.xv1.6.x
workspace.experiments.list(tags=...)
workspace.experiments.list(tag=...)
workspace.datasets.list(tags=...)
workspace.datasets.list(tag=...)

additional_metadata no longer the first positional argument

opts is now the first positional argument of experiments.list() and datasets.list(); pass additional_metadata by keyword instead.

v1.5.xv1.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.xv1.6.x
artifact.model_info["artifact_uid"]
artifact.uid
artifact.model_info["artifact_type"]
artifact.name
artifact.model_info["model_metadata"]
experiment.metadata
artifact.model_info["model_parameters"]
experiment.parameters

workspace.events removed

v1.5.xv1.6.x
workspace.events

No replacement.

options.BatchKubernetesOptions.min_replicas removed

v1.5.xv1.6.x
options.BatchKubernetesOptions(
...,
replicas=...,
min_replicas=...,
)
options.BatchKubernetesOptions(
...,
replicas=...,
)

experiment.parameters return type changed

v1.5.xv1.6.x
params = experiment.parameters
params = experiment.parameters
isinstance(
params, dict,
) # True
isinstance(
params, options.ExperimentParameters | None,
) # True
params["target_column"]
params.target_column

exclude_deleted removed from artifacts.list()

The default still excludes deleted artifacts. To fetch deleted artifacts, filter explicitly.

v1.5.xv1.6.x
workspace.artifacts.list(
exclude_deleted=False,
)
workspace.artifacts.list(
state="DELETED",
)

Tag join separator changed in list() results

v1.5.xv1.6.x
row["tags"] # "tag-a\ntag-b"
row["tags"] # "tag-a, tag-b"

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.xv1.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.xv1.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.xv1.6.x
deployment.is_paused # True when paused
deployment.is_paused # True only when paused
deployment.is_paused # True at 0 replicas
deployment.state == "STOPPED" # True at 0 replicas

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.xv1.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.xv1.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.xv1.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.xv1.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.xv1.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.

Scorer error message format changed

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.xv1.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.xv1.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.xv1.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.xv1.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.xv1.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.xv1.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.xv1.5.x
entity.state == "SOME_STATE" # True
entity.state == "SOME_STATE" # True
isinstance(entity.state, SomeEnum) # True
isinstance(entity.state, SomeEnum) # False

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.xv1.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.

v1.4.xv1.5.x
job.refresh()

job.state
job.state

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.xv1.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.xv1.5.x
r: MLOpsRuntime = scoring_runtime.runtime
r: MLOpsScoringRuntimeInfo = scoring_runtime.runtime

From v1.3.x to v1.4.x

Imports

v1.3.xv1.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.xv1.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.xv1.4.x
mlops.allowed_affinities
mlops.configs.allowed_k8s_affinities
mlops.allowed_tolerations
mlops.configs.allowed_k8s_tolerations

Get the current user

v1.3.xv1.4.x
mlops.get_user_info()
mlops.users.get_me()

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.xv1.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.xv1.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.

note
  • 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.xv1.4.x
artifact.update(
parent_experiment=experiment,
)
artifact.update(
parent_entity=experiment,
)

Get artifact's model-specific metadata (if applicable)

v1.3.xv1.4.x
artifact.get_model_info()
artifact.model_info

Convert JSON artifact to a dictionary

v1.3.xv1.4.x
artifact.to_dictionary()
artifact.to_dict()

Get the experiment associated with a model version

v1.3.xv1.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.xv1.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=...
)
note

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.xv1.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=...,
),
)
note

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.xv1.4.x
project.deployments.create_single(
...,
monitoring_record_options=options.MonitoringRecordOptions(
...,
),
)
workspace.deployments.create(
...,
monitoring_options=options.MonitoringOptions(
...,
),
)
note

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.xv1.4.x
while not deployment.is_healthy():
deployment.raise_for_failure()
time.sleep(5)
deployment.wait_for_healthy()

Get deployment state

v1.3.xv1.4.x
deployment.status()
deployment.state
deployment.is_healthy()
deployment.is_healthy

Update a deployment

v1.3.xv1.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(
...,
)
note

In v1.4.x, you can update multiple settings at once.

Access deployment scorer

v1.3.xv1.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
scorer.api_base_url
deployment.url_for_capabilities
scorer.capabilities_endpoint
deployment.url_for_schema
scorer.schema_endpoint
deployment.url_for_sample_request
scorer.sample_request_endpoint
deployment.url_for_scoring
scorer.scoring_endpoint
deployment.get_capabilities(...)
scorer.capabilities(...)
deployment.get_schema(...)
scorer.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.xv1.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.xv1.4.x
project.batch_scoring_jobs.create(
...,
resource_spec=options.BatchKubernetesOptions(
...,
),
)
workspace.batch_scoring_jobs.create(
...,
kubernetes_options=options.BatchKubernetesOptions(
...,
),
)
job.resource_spec
job.kubernetes_options

Get entity creator (if applicable)

v1.3.xv1.4.x
entity.owner
entity.creator

View the complete Table

v1.3.xv1.4.x
table
table.show(n=...)
note

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.xv1.3.x
environment = project.environments.get(uid=...)

You do not need to fetch the environment.

environment.deployments
project.deployments
environment.endpoints
project.endpoints
environment.allowed_affinities
mlops.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

v1.0.xv1.1.x
3.8
3.9

Create a deployment

v1.0.xv1.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=...,
),
)
note
  • 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()

Feedback