diff --git a/dvc/api.py b/dvc/api.py index bf976dcf01..f281971cd4 100644 --- a/dvc/api.py +++ b/dvc/api.py @@ -1,4 +1,5 @@ import os +import importlib.util from contextlib import contextmanager try: @@ -6,6 +7,8 @@ except ImportError: from contextlib import GeneratorContextManager as GCM +import ruamel.yaml + from dvc.utils.compat import urlparse from dvc.repo import Repo from dvc.external_repo import external_repo @@ -69,3 +72,39 @@ def _make_repo(repo_url, rev=None): else: with external_repo(url=repo_url, rev=rev) as repo: yield repo + + +def summon(name, repo=None, rev=None): + # 1. Read artifacts.yaml + # 2. Pull dependencies + # 3. Get the call and parameters + # 4. Invoke the call with the given parameters + # 5. Return the result + + with _make_repo(repo, rev=rev) as _repo: + artifacts_path = os.path.join(_repo.root_dir, "artifacts.yaml") + + with _repo.open(artifacts_path, mode="r") as fd: + artifacts = ruamel.yaml.load(fd.read()).get("artifacts") + + artifact = next(x for x in artifacts if x.get("name") == name) + + spec = importlib.util.spec_from_file_location( + artifact.get("call"), + os.path.join(_repo.root_dir, artifact.get("file")), + ) + + module = importlib.util.module_from_spec(spec) + + # ugly af, don't use exec / global, pls + call = "global result; result = {method}({params})".format( + method=artifact.get("call"), + params=", ".join( + k + "=" + v for k, v in artifact.get("params").items() + ), + ) + + spec.loader.exec_module(module) + exec(call) + global result + return result diff --git a/tests/func/test_api.py b/tests/func/test_api.py index 54623c080d..029a5e114a 100644 --- a/tests/func/test_api.py +++ b/tests/func/test_api.py @@ -3,6 +3,7 @@ import os import shutil +import ruamel.yaml import pytest from dvc import api @@ -126,3 +127,29 @@ def test_open_not_cached(dvc): os.remove(metric_file) with pytest.raises(FileMissingError): api.read(metric_file) + + +def test_summon(tmp_dir, erepo_dir, dvc): + artifacts_yaml = ruamel.yaml.dump( + { + "artifacts": [ + { + "name": "sum", + "description": "The sum of 1 + 2", + "call": "module.sum", + "file": "module.py", + "params": {"x": "1", "y": "2"}, + "deps": ["foo"], + } + ] + } + ) + + erepo_dir.gen("module.py", "def sum(x, y): return x + y") + erepo_dir.gen("artifacts.yaml", artifacts_yaml) + erepo_dir.scm.add(["module.py", "artifacts.yaml"]) + erepo_dir.scm.commit("Add module.py and artifacts.yaml") + + repo_url = "file://{}".format(erepo_dir) + + assert api.summon("sum", repo=repo_url) == 3