Skip to content

Commit 625fdbf

Browse files
authored
2.8.0 (#72)
2 parents 5aedaba + dd63d28 commit 625fdbf

33 files changed

+5783
-116
lines changed

ads/ads_version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "2.7.3"
2+
"version": "2.8.0"
33
}

ads/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
import click
88
import ads.opctl.cli
99
import ads.jobs.cli
10+
import ads.pipeline.cli
1011
import os
1112
import json
1213
except:
1314
print(
1415
"Please run `pip install oracle-ads[opctl]` to install the required dependencies for ADS CLI"
1516
)
1617
exit()
18+
19+
1720
with open(
1821
os.path.join(os.path.dirname(os.path.abspath(__file__)), "ads_version.json")
1922
) as version_file:
@@ -29,6 +32,7 @@ def cli():
2932

3033
cli.add_command(ads.opctl.cli.commands)
3134
cli.add_command(ads.jobs.cli.commands)
35+
cli.add_command(ads.pipeline.cli.commands)
3236

3337

3438
if __name__ == "__main__":

ads/common/auth.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,15 @@
77
import copy
88
import os
99
from dataclasses import dataclass
10-
from typing import Callable, Dict, Optional, Any
11-
12-
import oci
13-
from oci.config import (
14-
DEFAULT_LOCATION, # "~/.oci/config"
15-
DEFAULT_PROFILE, # "DEFAULT"
16-
)
10+
from typing import Any, Callable, Dict, Optional
1711

1812
import ads.telemetry
13+
import oci
1914
from ads.common import logger
2015
from ads.common.decorator.deprecate import deprecated
2116
from ads.common.extended_enum import ExtendedEnumMeta
17+
from oci.config import DEFAULT_LOCATION # "~/.oci/config"
18+
from oci.config import DEFAULT_PROFILE # "DEFAULT"
2219

2320

2421
class AuthType(str, metaclass=ExtendedEnumMeta):
@@ -314,7 +311,9 @@ def create_signer(
314311
)
315312
if config:
316313
auth_type = AuthType.API_KEY
314+
317315
signer_generator = AuthFactory().signerGenerator(auth_type)
316+
318317
return signer_generator(signer_args).create_signer()
319318

320319

ads/jobs/ads_job.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Job(Builder):
4949
.with_runtime(
5050
ScriptRuntime()
5151
.with_source("oci://bucket_name@namespace/path/to/script.py")
52-
.with_service_conda("tensorflow28_p38_cpu_v1")
52+
.with_service_conda("tensorflow26_p37_cpu_v2")
5353
.with_environment_variable(ENV="value")
5454
.with_argument("argument", key="value")
5555
.with_freeform_tag(tag_name="tag_value")
@@ -79,7 +79,7 @@ class Job(Builder):
7979
.with_runtime(
8080
NotebookRuntime()
8181
.with_notebook("path/to/notebook.ipynb")
82-
.with_service_conda("tensorflow28_p38_cpu_v1")
82+
.with_service_conda(tensorflow26_p37_cpu_v2")
8383
# Saves the notebook with outputs to OCI object storage.
8484
.with_output("oci://bucket_name@namespace/path/to/dir")
8585
)

ads/model/model_metadata.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*--
33

4-
# Copyright (c) 2021, 2022 Oracle and/or its affiliates.
4+
# Copyright (c) 2021, 2023 Oracle and/or its affiliates.
55
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
66

77
import json
@@ -38,6 +38,7 @@
3838
METADATA_VALUE_LENGTH_LIMIT = 255
3939
METADATA_DESCRIPTION_LENGTH_LIMIT = 255
4040
_METADATA_EMPTY_VALUE = "NA"
41+
CURRENT_WORKING_DIR = "."
4142

4243

4344
class MetadataSizeTooLarge(ValueError):
@@ -1594,7 +1595,16 @@ def fetch_training_code_details(
15941595
ModelProvenanceMetadata
15951596
A ModelProvenanceMetadata instance.
15961597
"""
1597-
repo = git.Repo(".", search_parent_directories=True)
1598+
git_dir = CURRENT_WORKING_DIR
1599+
if training_script_path:
1600+
if not os.path.exists(training_script_path):
1601+
logger.warning(
1602+
f"Training script {os.path.abspath(training_script_path)} does not exist."
1603+
)
1604+
else:
1605+
training_script_path = os.path.abspath(training_script_path)
1606+
git_dir = os.path.dirname(training_script_path)
1607+
repo = git.Repo(git_dir, search_parent_directories=True)
15981608
# get repository url
15991609
if len(repo.remotes) > 0:
16001610
repository_url = (
@@ -1608,19 +1618,11 @@ def fetch_training_code_details(
16081618
# get git branch
16091619
git_branch = format(repo.active_branch)
16101620
# get git commit
1611-
git_commit = ""
1621+
git_commit = None
16121622
try:
1613-
git_commit = format(str(repo.head.commit.hexsha))
1614-
except ValueError:
1615-
# do not set commit if there isn't any
1623+
git_commit = format(str(repo.head.commit.hexsha)) or None
1624+
except Exception:
16161625
logger.warning("No commit found.")
1617-
if training_script_path is not None and training_script_path != "":
1618-
if not os.path.exists(training_script_path):
1619-
logger.warning(
1620-
f"Training script {os.path.abspath(training_script_path)} does not exists."
1621-
)
1622-
else:
1623-
training_script_path = os.path.abspath(training_script_path)
16241626
return cls(
16251627
repo=repo,
16261628
git_branch=git_branch,

ads/opctl/backend/ads_dataflow.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,22 @@ def __init__(self, config: Dict) -> None:
4646
self.profile = config["execution"].get("oci_profile", None)
4747
self.client = OCIClientFactory(**self.oci_auth).dataflow
4848

49+
def apply(self):
50+
"""
51+
Create DataFlow and DataFlow Run from YAML.
52+
"""
53+
# TODO add the logic for build dataflow and dataflow run from YAML.
54+
raise NotImplementedError(f"`apply` hasn't been supported for data flow yet.")
55+
4956
def run(self) -> None:
57+
"""
58+
Create DataFlow and DataFlow Run from OCID or cli parameters.
59+
"""
5060
with AuthContext():
5161
ads.set_auth(auth=self.auth_type, profile=self.profile)
52-
if self.config["execution"].get("job_id", None):
53-
job_id = self.config["execution"]["job_id"]
54-
run_id = (
55-
Job.from_dataflow_job(self.config["execution"]["job_id"]).run().id
56-
)
62+
if self.config["execution"].get("ocid", None):
63+
data_flow_id = self.config["execution"]["ocid"]
64+
run_id = Job.from_dataflow_job(data_flow_id).run().id
5765
else:
5866
infra = self.config.get("infrastructure", {})
5967
if any(k not in infra for k in REQUIRED_FIELDS):
@@ -85,6 +93,9 @@ def run(self) -> None:
8593
return {"job_id": job_id, "run_id": run_id}
8694

8795
def cancel(self):
96+
"""
97+
Cancel DataFlow Run from OCID.
98+
"""
8899
if not self.config["execution"].get("run_id"):
89100
raise ValueError("Can only cancel a DataFlow run.")
90101
run_id = self.config["execution"]["run_id"]
@@ -93,18 +104,24 @@ def cancel(self):
93104
DataFlowRun.from_ocid(run_id).delete()
94105

95106
def delete(self):
96-
if self.config["execution"].get("job_id"):
97-
job_id = self.config["execution"]["job_id"]
107+
"""
108+
Delete DataFlow or DataFlow Run from OCID.
109+
"""
110+
if self.config["execution"].get("id"):
111+
data_flow_id = self.config["execution"]["id"]
98112
with AuthContext():
99113
ads.set_auth(auth=self.auth_type, profile=self.profile)
100-
Job.from_dataflow_job(job_id).delete()
114+
Job.from_dataflow_job(data_flow_id).delete()
101115
elif self.config["execution"].get("run_id"):
102116
run_id = self.config["execution"]["run_id"]
103117
with AuthContext():
104118
ads.set_auth(auth=self.auth_type, profile=self.profile)
105119
DataFlowRun.from_ocid(run_id).delete()
106120

107121
def watch(self):
122+
"""
123+
Watch DataFlow Run from OCID.
124+
"""
108125
run_id = self.config["execution"]["run_id"]
109126
with AuthContext():
110127
ads.set_auth(auth=self.auth_type, profile=self.profile)

ads/opctl/backend/ads_ml_job.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,29 @@ def __init__(self, config: Dict) -> None:
6060
self.profile = config["execution"].get("oci_profile", None)
6161
self.client = OCIClientFactory(**self.oci_auth).data_science
6262

63+
def apply(self) -> None:
64+
"""
65+
Create Job and Job Run from YAML.
66+
"""
67+
with AuthContext():
68+
ads.set_auth(auth=self.auth_type, profile=self.profile)
69+
job = Job.from_dict(self.config)
70+
job.create()
71+
job_run = job.run()
72+
print("JOB OCID:", job.id)
73+
print("JOB RUN OCID:", job_run.id)
74+
6375
def run(self) -> None:
76+
"""
77+
Create Job and Job Run from OCID or cli parameters.
78+
"""
6479
# TODO Check that this still runs smoothly for distributed
6580
with AuthContext():
6681
ads.set_auth(auth=self.auth_type, profile=self.profile)
67-
if self.config["execution"].get("job_id", None):
68-
job_id = self.config["execution"]["job_id"]
82+
if self.config["execution"].get("ocid", None):
83+
job_id = self.config["execution"]["ocid"]
6984
run_id = (
70-
Job.from_datascience_job(self.config["execution"]["job_id"])
71-
.run()
72-
.id
85+
Job.from_datascience_job(self.config["execution"]["ocid"]).run().id
7386
)
7487
else:
7588
payload = self._create_payload() # create job with infrastructure
@@ -121,24 +134,36 @@ def init_operator(self):
121134
return operator_folder
122135

123136
def delete(self):
124-
if self.config["execution"].get("job_id"):
125-
job_id = self.config["execution"]["job_id"]
137+
"""
138+
Delete Job or Job Run from OCID.
139+
"""
140+
if self.config["execution"].get("id"):
141+
job_id = self.config["execution"]["id"]
126142
with AuthContext():
127143
ads.set_auth(auth=self.auth_type, profile=self.profile)
128144
Job.from_datascience_job(job_id).delete()
145+
print(f"Job {job_id} has been deleted.")
129146
elif self.config["execution"].get("run_id"):
130147
run_id = self.config["execution"]["run_id"]
131148
with AuthContext():
132149
ads.set_auth(auth=self.auth_type, profile=self.profile)
133150
DataScienceJobRun.from_ocid(run_id).delete()
151+
print(f"Job run {run_id} has been deleted.")
134152

135153
def cancel(self):
154+
"""
155+
Cancel Job Run from OCID.
156+
"""
136157
run_id = self.config["execution"]["run_id"]
137158
with AuthContext():
138159
ads.set_auth(auth=self.auth_type, profile=self.profile)
139160
DataScienceJobRun.from_ocid(run_id).cancel()
161+
print(f"Job run {run_id} has been cancelled.")
140162

141163
def watch(self):
164+
"""
165+
Watch Job Run from OCID.
166+
"""
142167
run_id = self.config["execution"]["run_id"]
143168

144169
with AuthContext():

ads/opctl/backend/ads_ml_pipeline.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8; -*-
3+
4+
# Copyright (c) 2022, 2023 Oracle and/or its affiliates.
5+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
7+
from typing import Dict
8+
import ads
9+
from ads.common.auth import create_signer, AuthContext
10+
from ads.common.oci_client import OCIClientFactory
11+
from ads.opctl.backend.base import Backend
12+
from ads.pipeline import Pipeline, PipelineRun
13+
14+
15+
class PipelineBackend(Backend):
16+
def __init__(self, config: Dict) -> None:
17+
"""
18+
Initialize a MLPipeline object given config dictionary.
19+
20+
Parameters
21+
----------
22+
config: dict
23+
dictionary of configurations
24+
"""
25+
self.config = config
26+
self.oci_auth = create_signer(
27+
config["execution"].get("auth"),
28+
config["execution"].get("oci_config", None),
29+
config["execution"].get("oci_profile", None),
30+
)
31+
self.auth_type = config["execution"].get("auth")
32+
self.profile = config["execution"].get("oci_profile", None)
33+
self.client = OCIClientFactory(**self.oci_auth).data_science
34+
35+
def apply(self) -> None:
36+
"""
37+
Create Pipeline and Pipeline Run from YAML.
38+
"""
39+
with AuthContext():
40+
ads.set_auth(auth=self.auth_type, profile=self.profile)
41+
pipeline = Pipeline.from_dict(self.config)
42+
pipeline.create()
43+
pipeline_run = pipeline.run()
44+
print("PIPELINE OCID:", pipeline.id)
45+
print("PIPELINE RUN OCID:", pipeline_run.id)
46+
47+
def run(self) -> None:
48+
"""
49+
Create Pipeline and Pipeline Run from OCID.
50+
"""
51+
pipeline_id = self.config["execution"]["ocid"]
52+
with AuthContext():
53+
ads.set_auth(auth=self.auth_type, profile=self.profile)
54+
pipeline = Pipeline.from_ocid(ocid=pipeline_id)
55+
pipeline_run = pipeline.run()
56+
print("PIPELINE RUN OCID:", pipeline_run.id)
57+
58+
def delete(self) -> None:
59+
"""
60+
Delete Pipeline or Pipeline Run from OCID.
61+
"""
62+
if self.config["execution"].get("id"):
63+
pipeline_id = self.config["execution"]["id"]
64+
with AuthContext():
65+
ads.set_auth(auth=self.auth_type, profile=self.profile)
66+
Pipeline.from_ocid(pipeline_id).delete()
67+
print(f"Pipeline {pipeline_id} has been deleted.")
68+
elif self.config["execution"].get("run_id"):
69+
run_id = self.config["execution"]["run_id"]
70+
with AuthContext():
71+
ads.set_auth(auth=self.auth_type, profile=self.profile)
72+
PipelineRun.from_ocid(run_id).delete()
73+
print(f"Pipeline run {run_id} has been deleted.")
74+
75+
def cancel(self) -> None:
76+
"""
77+
Cancel Pipeline Run from OCID.
78+
"""
79+
run_id = self.config["execution"]["run_id"]
80+
with AuthContext():
81+
ads.set_auth(auth=self.auth_type, profile=self.profile)
82+
PipelineRun.from_ocid(run_id).cancel()
83+
print(f"Pipeline run {run_id} has been cancelled.")
84+
85+
def watch(self) -> None:
86+
"""
87+
Watch Pipeline Run from OCID.
88+
"""
89+
run_id = self.config["execution"]["run_id"]
90+
log_type = self.config["execution"]["log_type"]
91+
with AuthContext():
92+
ads.set_auth(auth=self.auth_type, profile=self.profile)
93+
PipelineRun.from_ocid(run_id).watch(log_type=log_type)

0 commit comments

Comments
 (0)