Skip to content

Commit fe164ad

Browse files
authored
Merge pull request #129 from aviaIguazio/item_to_yaml
Item to yaml
2 parents 957158a + 5789960 commit fe164ad

File tree

11 files changed

+164
-20
lines changed

11 files changed

+164
-20
lines changed

cli/item_to_function.py

+40-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
11
from pathlib import Path
22
from typing import Optional
3-
3+
import os
44
import click
55
from mlrun import code_to_function
66
from yaml import full_load
7+
from cli.path_iterator import PathIterator
8+
from cli.helpers import (
9+
is_item_dir
10+
)
711

812

913
@click.command()
1014
@click.option(
1115
"-i", "--item-path", help="Path to item.yaml file or a directory containing one"
1216
)
1317
@click.option("-o", "--output-path", help="Path to code_to_function output")
14-
def item_to_function(item_path: str, output_path: Optional[str] = None):
15-
item_path = Path(item_path)
18+
@click.option("-r", "--root-directory", help="Path to root directory containing multiple functions")
19+
def item_to_function(item_path: str, output_path: Optional[str] = None, root_directory: Optional[str] = None):
20+
if root_directory is None:
21+
item_to_function_from_path(item_path, output_path)
22+
else:
23+
item_iterator = PathIterator(root=root_directory, rule=is_item_dir, as_path=True)
24+
25+
for inner_dir in item_iterator:
26+
# Iterate individual files in each directory
27+
for inner_file in inner_dir.iterdir():
28+
if inner_file.is_file() and inner_file.name == 'item.yaml':
29+
inner_file_dir = os.path.dirname(str(inner_file))
30+
output_file_path = inner_file_dir + "/" + "gen_function.yaml"
31+
print(inner_file_dir)
32+
try:
33+
item_to_function_from_path(str(inner_file_dir), output_file_path)
34+
except Exception :
35+
print("failed to generate yaml for {}".format(str(inner_dir)))
36+
1637

38+
def item_to_function_from_path(item_path: str, output_path: Optional[str] = None):
39+
item_path = Path(item_path)
40+
base_path = ""
1741
if item_path.is_dir():
1842
if (item_path / "item.yaml").exists():
43+
base_path = str(item_path)
1944
item_path = item_path / "item.yaml"
45+
2046
else:
2147
raise FileNotFoundError(f"{item_path} does not contain a item.yaml file")
2248
elif not item_path.exists():
@@ -30,10 +56,11 @@ def item_to_function(item_path: str, output_path: Optional[str] = None):
3056
if filename.endswith(".ipynb"):
3157
code_output = Path(filename)
3258
code_output = code_output.parent / f"{code_output.stem}.py"
59+
code_file_name = get_filename(base_path, item_yaml)
3360

3461
function_object = code_to_function(
3562
name=item_yaml["name"],
36-
filename=item_yaml.get("spec", {}).get("filename"),
63+
filename=code_file_name,
3764
handler=item_yaml.get("spec", {}).get("handler"),
3865
kind=item_yaml.get("spec", {}).get("kind"),
3966
code_output=code_output,
@@ -58,5 +85,14 @@ def item_to_function(item_path: str, output_path: Optional[str] = None):
5885
function_object.export(target=str(output_path.resolve()))
5986

6087

88+
def get_filename(base_path, item_yaml):
89+
filename = item_yaml.get("spec", {}).get("filename")
90+
if filename is '':
91+
filename = base_path + "/" + item_yaml.get("example")
92+
else:
93+
filename = base_path + "/" + item_yaml.get("spec", {}).get("filename")
94+
return filename
95+
96+
6197
if __name__ == "__main__":
6298
item_to_function()

concept_drift/item.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mlrunVersion: ''
1616
name: concept-drift
1717
platformVersion: ''
1818
spec:
19-
filename: ''
19+
filename: concept_drift.ipynb
2020
handler: concept_drift_deployer
2121
image: mlrun/ml-models
2222
kind: job

coxph_test/coxph_test.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from mlrun.datastore import DataItem
2+
from mlrun.artifacts import get_model
3+
from cloudpickle import load
4+
from mlrun.mlutils.models import eval_class_model
5+
6+
7+
def cox_test(
8+
context,
9+
models_path: DataItem,
10+
test_set: DataItem,
11+
label_column: str,
12+
plots_dest: str = "plots",
13+
model_evaluator=None
14+
) -> None:
15+
"""Test one or more classifier models against held-out dataset
16+
17+
Using held-out test features, evaluates the peformance of the estimated model
18+
19+
Can be part of a kubeflow pipeline as a test step that is run post EDA and
20+
training/validation cycles
21+
22+
:param context: the function context
23+
:param model_file: model artifact to be tested
24+
:param test_set: test features and labels
25+
:param label_column: column name for ground truth labels
26+
:param score_method: for multiclass classification
27+
:param plots_dest: dir for test plots
28+
:param model_evaluator: WIP: specific method to generate eval, passed in as string
29+
or available in this folder
30+
"""
31+
xtest = test_set.as_df()
32+
ytest = xtest.pop(label_column)
33+
34+
model_file, model_obj, _ = get_model(models_path.url, suffix='.pkl')
35+
model_obj = load(open(str(model_file), "rb"))
36+
37+
try:
38+
# there could be different eval_models, type of model (xgboost, tfv1, tfv2...)
39+
if not model_evaluator:
40+
# binary and multiclass
41+
eval_metrics = eval_class_model(context, xtest, ytest, model_obj)
42+
43+
# just do this inside log_model?
44+
model_plots = eval_metrics.pop("plots")
45+
model_tables = eval_metrics.pop("tables")
46+
for plot in model_plots:
47+
context.log_artifact(plot, local_path=f"{plots_dest}/{plot.key}.html")
48+
for tbl in model_tables:
49+
context.log_artifact(tbl, local_path=f"{plots_dest}/{plot.key}.csv")
50+
51+
context.log_results(eval_metrics)
52+
except:
53+
# dummy log:
54+
context.log_dataset("cox-test-summary", df=model_obj.summary, index=True, format="csv")
55+
context.logger.info("cox tester not implemented")

coxph_test/item.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mlrunVersion: ''
1616
name: coxph-test
1717
platformVersion: ''
1818
spec:
19-
filename: ''
19+
filename: coxph_test.py
2020
handler: cox_test
2121
image: mlrun/ml-models
2222
kind: job

describe_dask/item.yaml

+1-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ spec:
1818
handler: summarize
1919
image: mlrun/ml-models
2020
kind: job
21-
requirements:
22-
- mlrun
23-
- dask
24-
- yellowbrick
21+
requirements: []
2522
url: ''
2623
version: 0.0.1

describe_dask/requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mlrun
2+
dask
3+
yellowbrick

load_dask/item.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mlrunVersion: ''
1515
name: load-dask
1616
platformVersion: ''
1717
spec:
18-
filename: ''
18+
filename: load_dask.py
1919
handler: load_dask
2020
image: mlrun/ml-models
2121
kind: dask

load_dask/load_dask.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from mlrun.execution import MLClientCtx
2+
from mlrun.datastore import DataItem
3+
4+
from typing import List, Optional
5+
6+
7+
def load_dask(
8+
context: MLClientCtx,
9+
src_data: DataItem,
10+
dask_key: str = "dask_key",
11+
inc_cols: Optional[List[str]] = None,
12+
index_cols: Optional[List[str]] = None,
13+
dask_persist: bool = True,
14+
refresh_data: bool = True,
15+
scheduler_key: str = "scheduler"
16+
) -> None:
17+
"""Load dataset into an existing dask cluster
18+
19+
dask jobs define the dask client parameters at the job level, this method will raise an error if no client is detected.
20+
21+
:param context: the function context
22+
:param src_data: url of the data file or partitioned dataset as either
23+
artifact DataItem, string, or path object (similar to
24+
pandas read_csv)
25+
:param dask_key: destination key of data on dask cluster and artifact store
26+
:param inc_cols: include only these columns (very fast)
27+
:param index_cols: list of index column names (can be a long-running process)
28+
:param dask_persist: (True) should the data be persisted (through the `client.persist` op)
29+
:param refresh_data: (False) if the dask_key already exists in the dask cluster, this will
30+
raise an Exception. Set to True to replace the existing cluster data.
31+
:param scheduler_key: (scheduler) the dask scheduler configuration, json also logged as an artifact
32+
"""
33+
if hasattr(context, "dask_client"):
34+
dask_client = context.dask_client
35+
else:
36+
raise Exception("a dask client was not found in the execution context")
37+
38+
df = src_data.as_df(df_module=dd)
39+
40+
if dask_persist:
41+
df = dask_client.persist(df)
42+
if dask_client.datasets and dask_key in dask_client.datasets:
43+
dask_client.unpublish_dataset(dask_key)
44+
dask_client.publish_dataset(df, name=dask_key)
45+
46+
if context:
47+
context.dask_client = dask_client
48+
49+
# share the scheduler, whether data is persisted or not
50+
dask_client.write_scheduler_file(scheduler_key + ".json")
51+
52+
# we don't use log_dataset here until it can take into account
53+
# dask origin and apply dask describe.
54+
context.log_artifact(scheduler_key, local_path=scheduler_key + ".json")

rnn_serving/item.yaml

+2-6
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ spec:
1818
handler: handler
1919
image: mlrun/ml-model
2020
kind: serving
21-
requirements:
22-
- mlrun==0.6.2
23-
- keras
24-
- tensorflow
25-
- wget
26-
- pygit2
21+
requirements: []
22+
2723
url: ''
2824
version: 0.0.1

rnn_serving/requirements.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mlrun==0.6.2
2+
keras
3+
tensorflow
4+
wget
5+
pygit2

spark_submit/function.yaml

+1-3
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ spec:
7474
spark_submit:
7575
name: spark_submit
7676
doc: 'spark_submit function
77-
78-
7977
submiting spark via shell'
8078
parameters:
8179
- name: context
@@ -115,4 +113,4 @@ spec:
115113
build:
116114
functionSourceCode: IyBHZW5lcmF0ZWQgYnkgbnVjbGlvLmV4cG9ydC5OdWNsaW9FeHBvcnRlcgoKZnJvbSBtbHJ1biBpbXBvcnQgZ2V0X29yX2NyZWF0ZV9jdHgKZnJvbSBrdWJlcm5ldGVzIGltcG9ydCBjb25maWcsIGNsaWVudApmcm9tIGt1YmVybmV0ZXMuc3RyZWFtIGltcG9ydCBzdHJlYW0KaW1wb3J0IHlhbWwKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmltcG9ydCBvcwoKY2xhc3MgSzhTQ2xpZW50KG9iamVjdCk6CgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGxvZ2dlciwgbmFtZXNwYWNlPSdkZWZhdWx0LXRlbmFudCcsIGNvbmZpZ19maWxlPU5vbmUpOgogICAgICAgIHNlbGYubmFtZXNwYWNlID0gbmFtZXNwYWNlCiAgICAgICAgc2VsZi5sb2dnZXIgPSBsb2dnZXIKICAgICAgICBzZWxmLl9pbml0X2s4c19jb25maWcoY29uZmlnX2ZpbGUpCiAgICAgICAgc2VsZi52MWFwaSA9IGNsaWVudC5Db3JlVjFBcGkoKQoKICAgIGRlZiBfaW5pdF9rOHNfY29uZmlnKHNlbGYsIGNvbmZpZ19maWxlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGNvbmZpZy5sb2FkX2luY2x1c3Rlcl9jb25maWcoKQogICAgICAgICAgICBzZWxmLmxvZ2dlci5pbmZvKCd1c2luZyBpbi1jbHVzdGVyIGNvbmZpZy4nKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIGNvbmZpZy5sb2FkX2t1YmVfY29uZmlnKGNvbmZpZ19maWxlKQogICAgICAgICAgICAgICAgc2VsZi5sb2dnZXIuaW5mbygndXNpbmcgbG9jYWwga3ViZXJuZXRlcyBjb25maWcuJykKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAnY2Fubm90IGZpbmQgbG9jYWwga3ViZXJuZXRlcyBjb25maWcgZmlsZSwnCiAgICAgICAgICAgICAgICAgICAgJyBwbGFjZSBpdCBpbiB+Ly5rdWJlL2NvbmZpZyBvciBzcGVjaWZ5IGl0IGluICcKICAgICAgICAgICAgICAgICAgICAnS1VCRUNPTkZJRyBlbnYgdmFyJykKCiAgICBkZWYgZ2V0X3NoZWxsX3BvZF9uYW1lKHNlbGYsIHBvZF9uYW1lPSdzaGVsbCcpOgogICAgICAgIHNoZWxsX3BvZCA9IHNlbGYudjFhcGkubGlzdF9uYW1lc3BhY2VkX3BvZChuYW1lc3BhY2U9c2VsZi5uYW1lc3BhY2UpCiAgICAgICAgZm9yIGkgaW4gc2hlbGxfcG9kLml0ZW1zOgogICAgICAgICAgICBpZiBwb2RfbmFtZSBpbiBpLm1ldGFkYXRhLm5hbWU6CiAgICAgICAgICAgICAgICBzZWxmLmxvZ2dlci5pbmZvKCIlc1x0JXNcdCVzIiAlIChpLnN0YXR1cy5wb2RfaXAsIGkubWV0YWRhdGEubmFtZXNwYWNlLCBpLm1ldGFkYXRhLm5hbWUpKQogICAgICAgICAgICAgICAgc2hlbGxfbmFtZSA9IGkubWV0YWRhdGEubmFtZQogICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICByZXR1cm4gc2hlbGxfbmFtZQoKICAgIGRlZiBleGVjX3NoZWxsX2NtZChzZWxmLCBjbWQsIHNoZWxsX3BvZF9uYW1lID0gJ3NoZWxsJyk6CiAgICAgICAgc2hlbGxfbmFtZSA9IHNlbGYuZ2V0X3NoZWxsX3BvZF9uYW1lKHNoZWxsX3BvZF9uYW1lKQogICAgICAgIGV4ZWNfY29tbWFuZCA9IFsKICAgICAgICAgICAgJy9iaW4vYmFzaCcsCiAgICAgICAgICAgICctYycsCiAgICAgICAgICAgIGNtZF0KICAgICAgICByZXNwID0gc3RyZWFtKHNlbGYudjFhcGkuY29ubmVjdF9nZXRfbmFtZXNwYWNlZF9wb2RfZXhlYywKICAgICAgICAgICAgICAgICAgICAgIHNoZWxsX25hbWUsCiAgICAgICAgICAgICAgICAgICAgICBzZWxmLm5hbWVzcGFjZSwKICAgICAgICAgICAgICAgICAgICAgIGNvbW1hbmQ9ZXhlY19jb21tYW5kLAogICAgICAgICAgICAgICAgICAgICAgc3RkZXJyPVRydWUsIHN0ZGluPUZhbHNlLAogICAgICAgICAgICAgICAgICAgICAgc3Rkb3V0PVRydWUsIHR0eT1GYWxzZSxfcHJlbG9hZF9jb250ZW50PUZhbHNlKQogICAgICAgIAogICAgICAgIAogICAgICAgIHN0ZGVyciA9IFtdCiAgICAgICAgc3Rkb3V0ID0gW10KICAgICAgICB3aGlsZSByZXNwLmlzX29wZW4oKToKICAgICAgICAgICAgcmVzcC51cGRhdGUodGltZW91dD1Ob25lKQogICAgICAgICAgICBpZiByZXNwLnBlZWtfc3RkZXJyKCk6CiAgICAgICAgICAgICAgICAgc3RkZXJyLmFwcGVuZChyZXNwLnJlYWRfc3RkZXJyKCkpCiAgICAgICAgICAgIGlmIHJlc3AucGVla19zdGRvdXQoKToKICAgICAgICAgICAgICAgICBzdGRvdXQuYXBwZW5kKHJlc3AucmVhZF9zdGRvdXQoKSkKICAgICAgICAgICAKICAgICAgICBlcnIgPSByZXNwLnJlYWRfY2hhbm5lbCgzKQogICAgICAgIGVyciA9IHlhbWwuc2FmZV9sb2FkKGVycikKICAgICAgICBpZiBlcnJbJ3N0YXR1cyddID09ICdTdWNjZXNzJzoKICAgICAgICAgICAgcmMgPSAwCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmMgPSBpbnQoZXJyWydkZXRhaWxzJ11bJ2NhdXNlcyddWzBdWydtZXNzYWdlJ10pCiAgICAgICAgCiAgICAgICAgc3Rkb3V0X2Zvcm1hdGVkPScnCiAgICAgICAgZm9yIGVhY2ggaW4gc3Rkb3V0OgogICAgICAgICAgICBzdGRvdXRfZm9ybWF0ZWQgKz0gZWFjaAogICAgICAgIHNlbGYubG9nZ2VyLmluZm8oIlNURE9VVDogJXMiJSBzdGRvdXRfZm9ybWF0ZWQpCiAgICAgICAgc2VsZi5sb2dnZXIuaW5mbygiUkM6ICVzIiUgcmMpCiAgICAgICAgCiAgICAgICAgc3RkZXJyX2Zvcm1hdGVkPScnCiAgICAgICAgZm9yIGVhY2ggaW4gc3RkZXJyOgogICAgICAgICAgICBzdGRlcnJfZm9ybWF0ZWQgKz0gZWFjaAogICAgICAgIGlmIHJjICE9IDA6CiAgICAgICAgICAgIHNlbGYubG9nZ2VyLmluZm8oIlNUREVSUjogJXMiJSBzdGRlcnJfZm9ybWF0ZWQpCiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbignRXhlY3V0aW9uX2Vycm9yJywgc3RkZXJyX2Zvcm1hdGVkICsgc3Rkb3V0X2Zvcm1hdGVkKQogICAgCmRlZiBuYl9vcl9weShweXNwYXJrX2NvbW1hbmQpOgogICAgaWYgcHlzcGFya19jb21tYW5kLmVuZHN3aXRoKCcuaXB5bmInKToKICAgICAgICBmcm9tIG1scnVuIGltcG9ydCBjb2RlX3RvX2Z1bmN0aW9uCiAgICAgICAgZGlyID0gUGF0aChweXNwYXJrX2NvbW1hbmQpLnBhcmVudAogICAgICAgIGZpbGUgPSAgUGF0aChweXNwYXJrX2NvbW1hbmQpLnN0ZW0KICAgICAgICBweXRob25fZmlsZSA9IG9zLnBhdGguam9pbihkaXIsZmlsZSArJy5weScpCiAgICAgICAgY29kZV90b19mdW5jdGlvbihraW5kPSJsb2NhbCIsZmlsZW5hbWU9cHlzcGFya19jb21tYW5kLGNvZGVfb3V0cHV0PXB5dGhvbl9maWxlKQogICAgICAgIHJldHVybiBweXRob25fZmlsZQogICAgZWxzZToKICAgICAgICByZXR1cm4gcHlzcGFya19jb21tYW5kICAgICAKCgpkZWYgc3BhcmtfY29tbWFuZF9idWlsZGVyKG5hbWUsIGNsYXNzX25hbWUsIGphcnMsIHBhY2thZ2VzLCBzcGFya19vcHRpb25zLHB5c3BhcmtfY29tbWFuZD0nJyxweXNwYXJrX3BhcmFtcz0nJyk6CiAgICBjbWQgPSAnc3Bhcmstc3VibWl0JwogICAgaWYgbmFtZToKICAgICAgICBjbWQgKz0gJyAtLW5hbWUgJyArIG5hbWUKCiAgICBpZiBjbGFzc19uYW1lOgogICAgICAgIGNtZCArPSAnIC0tY2xhc3MgJyArIGNsYXNzX25hbWUKCiAgICBpZiBqYXJzOgogICAgICAgIGNtZCArPSAnIC0tamFycyAnICsgamFycwoKICAgIGlmIHBhY2thZ2VzOgogICAgICAgIGNtZCArPSAnIC0tcGFja2FnZXMgJyArIHBhY2thZ2VzCgogICAgaWYgc3Bhcmtfb3B0aW9uczoKICAgICAgICBjbWQgKz0gJyAnICsgc3Bhcmtfb3B0aW9ucwoKICAgIGlmIHB5c3BhcmtfY29tbWFuZDoKICAgICAgICBjbWQgKz0gJyAnICsgbmJfb3JfcHkocHlzcGFya19jb21tYW5kKQogICAgICAgICAgICAKICAgIGlmIHB5c3BhcmtfcGFyYW1zOgogICAgICAgIGNtZCArPSAnICcgKyBweXNwYXJrX3BhcmFtcwogICAgICAgIAogICAgcmV0dXJuIGNtZAoKCmRlZiBzcGFya19zdWJtaXQoY29udGV4dCwgdjNpb19hY2Nlc3Nfa2V5LCBuYW1lPU5vbmUsIGNsYXNzX25hbWU9Tm9uZSwgamFycz1Ob25lLCBwYWNrYWdlcz1Ob25lLCBzcGFya19vcHRpb25zPScnLHB5c3BhcmtfY29tbWFuZD0nJyxweXNwYXJrX3BhcmFtcz0nJyk6CiAgICAiIiJzcGFya19zdWJtaXQgZnVuY3Rpb24KICAgIAogICAgc3VibWl0aW5nIHNwYXJrIHZpYSBzaGVsbAogICAgCiAgICA6cGFyYW0gbmFtZTogICAgICAgIEEgbmFtZSBvZiB5b3VyIGFwcGxpY2F0aW9uLgogICAgOnBhcmFtIGNsYXNzX25hbWU6ICBZb3VyIGFwcGxpY2F0aW9uJ3MgbWFpbiBjbGFzcyAoZm9yIEphdmEgLyBTY2FsYSBhcHBzKS4KICAgICAgICAgICAgICAgICAgICAgICAgKiBJZiByZWxhdGl2ZSB3aWxsIGFkZCB0byB0aGUge2FydGlmYWN0X3BhdGh9CiAgICA6cGFyYW0gamFyczogICAgICAgIENvbW1hLXNlcGFyYXRlZCBsaXN0IG9mIGphcnMgdG8gaW5jbHVkZSBvbiB0aGUgZHJpdmVyCiAgICAgICAgICAgICAgICAgICAgICAgIGFuZCBleGVjdXRvciBjbGFzc3BhdGhzLgogICAgOnBhcmFtIHBhY2thZ2VzOiAgICBDb21tYS1zZXBhcmF0ZWQgbGlzdCBvZiBtYXZlbiBjb29yZGluYXRlcyBvZiBqYXJzIHRvIGluY2x1ZGUKICAgICAgICAgICAgICAgICAgICAgICAgb24gdGhlIGRyaXZlciBhbmQgZXhlY3V0b3IgY2xhc3NwYXRocy4gV2lsbCBzZWFyY2ggdGhlIGxvY2FsCiAgICAgICAgICAgICAgICAgICAgICAgIG1hdmVuIHJlcG8sIHRoZW4gbWF2ZW4gY2VudHJhbCBhbmQgYW55IGFkZGl0aW9uYWwgcmVtb3RlCiAgICAgICAgICAgICAgICAgICAgICAgIHJlcG9zaXRvcmllcyBnaXZlbiBieSAtLXJlcG9zaXRvcmllcy4gVGhlIGZvcm1hdCBmb3IgdGhlCiAgICA6cGFyYW0gc3Bhcmtfb3B0aW9uczogc3BhcmsgcGFyYW1ldGVzIHRoYXQgYXJlIG5vdCBpbmNsdWRlZCBhcyBmdW5jdGlvbiBhcmd1bWVudHMKICAgIDpwYXJhbSBweXNwYXJrX2NvbW1hbmQ6IEEgcHl0aG9uIHNjcmlwdCBvciBKdXB5dGVyIG5vdGVib29rIHRvIGJlIGV4ZWN1dGVkCiAgICA6cGFyYW0gcHlzcGFya19wYXJhbXM6ICBwYXJhbWV0ZXJzIHRvIHRoZSBweXRob24gc2NyaXB0CgogICAgIiIiCiAgICBjbWQgPSBzcGFya19jb21tYW5kX2J1aWxkZXIobmFtZSwgY2xhc3NfbmFtZSwgamFycywgcGFja2FnZXMsIHNwYXJrX29wdGlvbnMscHlzcGFya19jb21tYW5kLHB5c3BhcmtfcGFyYW1zKQogICAgY29udGV4dC5sb2dnZXIuaW5mbygic3VibWl0aW5nIDoiICsgY21kKQoKICAgIGNsaSA9IEs4U0NsaWVudChjb250ZXh0LmxvZ2dlcikKICAgIGNsaS5leGVjX3NoZWxsX2NtZChjbWQpCgo=
117115
commands: []
118-
code_origin: https://github.com/marcelonyc/functions#500364cb773cf4d2f23df1449ad32b6a1e370b8c:submit.ipynb
116+
code_origin: https://github.com/marcelonyc/functions#500364cb773cf4d2f23df1449ad32b6a1e370b8c:submit.ipynb

0 commit comments

Comments
 (0)