Skip to content

Commit 79044ea

Browse files
authored
ODSC-37692:[MLFlow][Projects] Improve ADS OPCTL to be able to generat… (#161)
2 parents 12a8d06 + 8dad93d commit 79044ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2213
-337
lines changed

.pre-commit-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.4.0
4+
hooks:
5+
- id: check-ast
6+
- id: check-docstring-first
7+
exclude: ^tests/
8+
- id: check-json
9+
- id: check-merge-conflict
10+
- id: check-yaml
11+
args: ['--allow-multiple-documents']
12+
- id: detect-private-key
13+
- id: end-of-file-fixer
14+
- id: pretty-format-json
15+
args: ['--autofix']
16+
- id: trailing-whitespace
17+
- repo: https://github.com/psf/black
18+
rev: 22.12.0
19+
hooks:
20+
- id: black
21+
- repo: https://github.com/pre-commit/pygrep-hooks
22+
rev: v1.9.0
23+
hooks:
24+
- id: rst-backticks
25+
- id: rst-inline-touching-normal
26+
27+
exclude: ^(docs/)

ads/__init__.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,8 @@ def _set_test_mode(mode=False):
122122

123123

124124
def hello():
125-
"""
126-
Imports Pandas, sets the documentation mode, and prints a fancy "Hello".
127-
"""
128-
import pandas
129-
130-
global documentation_mode
131-
global debug_mode
125+
import oci
126+
import ocifs
132127

133128
print(
134129
f"""
@@ -139,11 +134,11 @@ def hello():
139134
| || / |
140135
o oo-o o--o
141136
142-
ADS SDK version: {__version__}
143-
Pandas version: {pandas.__version__}
144-
Debug mode: {debug_mode}
145-
"""
146-
)
137+
ads v{__version__}
138+
oci v{oci.__version__}
139+
ocifs v{ocifs.__version__}
140+
141+
""")
147142

148143

149144
configure_plotting()

ads/cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33

44
# Copyright (c) 2021, 2022 Oracle and/or its affiliates.
55
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
from ads.common import logger
7+
import traceback
8+
69
try:
710
import click
811
import ads.opctl.cli
912
import ads.jobs.cli
1013
import ads.pipeline.cli
1114
import os
1215
import json
13-
except:
16+
except Exception as ex:
1417
print(
15-
"Please run `pip install oracle-ads[opctl]` to install the required dependencies for ADS CLI"
18+
"Please run `pip install oracle-ads[opctl]` to install "
19+
"the required dependencies for ADS CLI."
1620
)
21+
logger.debug(ex)
22+
logger.debug(traceback.format_exc())
1723
exit()
1824

1925

ads/common/extended_enum.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
from abc import ABCMeta
9+
from enum import Enum
910

1011

1112
class ExtendedEnumMeta(ABCMeta):
@@ -15,13 +16,20 @@ class ExtendedEnumMeta(ABCMeta):
1516
-------
1617
values(cls) -> list:
1718
Gets the list of class attributes.
19+
20+
Examples
21+
--------
22+
>>> class TestEnum(str, metaclass=ExtendedEnumMeta):
23+
... KEY1 = "value1"
24+
... KEY2 = "value2"
25+
>>> print(TestEnum.KEY1) # "value1"
1826
"""
1927

2028
def __contains__(cls, value):
2129
return value and value.lower() in tuple(value.lower() for value in cls.values())
2230

2331
def values(cls) -> list:
24-
"""Gets the list of class attributes.
32+
"""Gets the list of class attributes values.
2533
2634
Returns
2735
-------
@@ -31,3 +39,35 @@ def values(cls) -> list:
3139
return tuple(
3240
value for key, value in cls.__dict__.items() if not key.startswith("_")
3341
)
42+
43+
def keys(cls) -> list:
44+
"""Gets the list of class attributes names.
45+
46+
Returns
47+
-------
48+
list
49+
The list of class attributes names.
50+
"""
51+
return tuple(
52+
key for key, value in cls.__dict__.items() if not key.startswith("_")
53+
)
54+
55+
56+
class ExtendedEnum(Enum):
57+
"""The base class to extend functionality of a generic Enum.
58+
59+
Examples
60+
--------
61+
>>> class TestEnum(ExtendedEnumMeta):
62+
... KEY1 = "value1"
63+
... KEY2 = "value2"
64+
>>> print(TestEnum.KEY1.value) # "value1"
65+
"""
66+
67+
@classmethod
68+
def values(cls):
69+
return sorted(map(lambda c: c.value, cls))
70+
71+
@classmethod
72+
def keys(cls):
73+
return sorted(map(lambda c: c.name, cls))

ads/common/serializer.py

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
# 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

7+
import dataclasses
78
import json
89
from abc import ABC, abstractmethod
9-
import dataclasses
10-
from typing import Dict, Union, Optional, List
1110
from enum import Enum
11+
from typing import Dict, Optional, Union
12+
from urllib.parse import urlparse
1213

1314
import fsspec
1415
import yaml
16+
1517
from ads.common import logger
1618

1719
try:
@@ -53,15 +55,19 @@ class Serializable(ABC):
5355
"""
5456

5557
@abstractmethod
56-
def to_dict(self, **kwargs) -> dict:
57-
"""Serializes instance of class into a dictionary.
58+
def to_dict(self, **kwargs: Dict) -> Dict:
59+
"""Serializes an instance of class into a dictionary.
60+
61+
Parameters
62+
----------
63+
**kwargs: Dict
64+
Additional arguments.
5865
5966
Returns
6067
-------
6168
Dict
62-
A dictionary.
69+
The result dictionary.
6370
"""
64-
pass
6571

6672
@classmethod
6773
@abstractmethod
@@ -90,17 +96,25 @@ def _write_to_file(s: str, uri: str, **kwargs) -> None:
9096
content
9197
uri: (string)
9298
URI location to save string s
93-
94-
kwargs
95-
------
96-
keyword arguments to be passed into fsspec.open(). For OCI object storage, this should be config="path/to/.oci/config".
97-
For other storage connections consider e.g. host, port, username, password, etc.
99+
kwargs : dict
100+
keyword arguments to be passed into fsspec.open().
101+
For OCI object storage, this can be config="path/to/.oci/config".
98102
99103
Returns
100104
-------
101105
None
102-
Nothing.
106+
Nothing
103107
"""
108+
109+
overwrite = kwargs.pop("overwrite", True)
110+
if not overwrite:
111+
dst_path_scheme = urlparse(uri).scheme or "file"
112+
if fsspec.filesystem(dst_path_scheme, **kwargs).exists(uri):
113+
raise FileExistsError(
114+
f"The `{uri}` is already exists. Set `overwrite` to True "
115+
"if you wish to overwrite."
116+
)
117+
104118
with fsspec.open(uri, "w", **kwargs) as f:
105119
f.write(s)
106120

@@ -112,15 +126,13 @@ def _read_from_file(uri: str, **kwargs) -> str:
112126
----------
113127
uri: (string)
114128
URI location
115-
116-
kwargs
117-
------
118-
keyword arguments to be passed into fsspec.open(). For OCI object storage, this should be config="path/to/.oci/config".
119-
For other storage connections consider e.g. host, port, username, password, etc.
129+
kwargs : dict
130+
keyword arguments to be passed into fsspec.open().
131+
For OCI object storage, this can be config="path/to/.oci/config".
120132
121133
Returns
122134
-------
123-
string: Contents in file specified by URI
135+
string: Contents in file specified by URI
124136
"""
125137
with fsspec.open(uri, "r", **kwargs) as f:
126138
return f.read()
@@ -139,15 +151,23 @@ def to_json(
139151
140152
kwargs
141153
------
142-
keyword arguments to be passed into fsspec.open(). For OCI object storage, this should be config="path/to/.oci/config".
143-
For other storage connections consider e.g. host, port, username, password, etc.
154+
overwrite: (bool, optional). Defaults to True.
155+
Whether to overwrite existing file or not.
144156
145-
Returns:
146-
string: Serialized version of object
157+
keyword arguments to be passed into fsspec.open().
158+
For OCI object storage, this could be config="path/to/.oci/config".
159+
For other storage connections consider e.g. host, port, username, password, etc.
160+
161+
Returns
162+
-------
163+
Union[str, None]
164+
Serialized version of object.
165+
`None` in case when `uri` provided.
147166
"""
148167
json_string = json.dumps(self.to_dict(**kwargs), cls=encoder)
149168
if uri:
150169
self._write_to_file(s=json_string, uri=uri, **kwargs)
170+
return None
151171
return json_string
152172

153173
@classmethod
@@ -190,31 +210,40 @@ def from_json(
190210
return cls.from_dict(json_dict)
191211
raise ValueError("Must provide either JSON string or URI location")
192212

193-
def to_yaml(self, uri: str = None, dumper: callable = dumper, **kwargs) -> str:
213+
def to_yaml(
214+
self, uri: str = None, dumper: callable = yaml.SafeDumper, **kwargs
215+
) -> Union[str, None]:
194216
"""Returns object serialized as a YAML string
195217
196218
Parameters
197219
----------
198-
uri: (string, optional)
199-
URI location to save the YAML string. Defaults to None.
200-
dumper: (callable, optional)
201-
Custom YAML Dumper. Defaults to CDumper/SafeDumper.
202-
203-
kwargs
204-
------
205-
side_effect: Optional[SideEffect]
206-
side effect to take on the dictionary. The side effect can be either
207-
convert the dictionary keys to "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
208-
or "upper"(SideEffect.CONVERT_KEYS_TO_UPPER.value) cases.
209-
keyword arguments to be passed into fsspec.open(). For OCI object storage, this should be config="path/to/.oci/config".
210-
For other storage connections consider e.g. host, port, username, password, etc.
220+
uri : str, optional
221+
URI location to save the YAML string, by default None
222+
dumper : callable, optional
223+
Custom YAML Dumper, by default yaml.SafeDumper
224+
kwargs : dict
225+
overwrite: (bool, optional). Defaults to True.
226+
Whether to overwrite existing file or not.
227+
note: (str, optional)
228+
The note that needs to be added in the beginning of the YAML.
229+
It will be added as is without any formatting.
230+
side_effect: Optional[SideEffect]
231+
side effect to take on the dictionary. The side effect can be either
232+
convert the dictionary keys to "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
233+
or "upper"(SideEffect.CONVERT_KEYS_TO_UPPER.value) cases.
234+
235+
The other keyword arguments to be passed into fsspec.open().
236+
For OCI object storage, this could be config="path/to/.oci/config".
211237
212-
Returns:
213-
Union[str, None]
214-
Serialized version of object.
215-
None in case when `uri` provided.
238+
Returns
239+
-------
240+
Union[str, None]
241+
Serialized version of object.
242+
`None` in case when `uri` provided.
216243
"""
217-
yaml_string = yaml.dump(self.to_dict(**kwargs), Dumper=dumper)
244+
note = kwargs.pop("note", "")
245+
246+
yaml_string = f"{note}\n" + yaml.dump(self.to_dict(**kwargs), Dumper=dumper)
218247
if uri:
219248
self._write_to_file(s=yaml_string, uri=uri, **kwargs)
220249
return None

ads/jobs/ads_job.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# 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/
6-
from typing import List, Union
6+
from typing import List, Union, Dict
77
from urllib.parse import urlparse
88

99
import fsspec
@@ -359,6 +359,8 @@ def with_name(self, name: str) -> "Job":
359359

360360
def build(self) -> "Job":
361361
"""Load default values from the environment for the job infrastructure."""
362+
super().build()
363+
362364
build_method = getattr(self.infrastructure, "build", None)
363365
if build_method and callable(build_method):
364366
build_method()
@@ -457,19 +459,27 @@ def status(self) -> str:
457459
"""
458460
return getattr(self.infrastructure, "status", None)
459461

460-
def to_dict(self) -> dict:
462+
def to_dict(self, **kwargs: Dict) -> Dict:
461463
"""Serialize the job specifications to a dictionary.
462464
465+
Parameters
466+
----------
467+
**kwargs: Dict
468+
The additional arguments.
469+
- filter_by_attribute_map: bool
470+
If True, then in the result will be included only the fields
471+
presented in the `attribute_map`.
472+
463473
Returns
464474
-------
465-
dict
475+
Dict
466476
A dictionary containing job specifications.
467477
"""
468478
spec = {"name": self.name}
469479
if self.runtime:
470-
spec["runtime"] = self.runtime.to_dict()
480+
spec["runtime"] = self.runtime.to_dict(**kwargs)
471481
if self.infrastructure:
472-
spec["infrastructure"] = self.infrastructure.to_dict()
482+
spec["infrastructure"] = self.infrastructure.to_dict(**kwargs)
473483
if self.id:
474484
spec["id"] = self.id
475485
return {
@@ -495,7 +505,7 @@ def from_dict(cls, config: dict) -> "Job":
495505
Raises
496506
------
497507
NotImplementedError
498-
If the type of the intrastructure or runtime is not supported.
508+
If the type of the infrastructure or runtime is not supported.
499509
"""
500510
if not isinstance(config, dict):
501511
raise ValueError("The config data for initializing the job is invalid.")

0 commit comments

Comments
 (0)