Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions dvc/api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os
import importlib.util
from contextlib import contextmanager

try:
from contextlib import _GeneratorContextManager as GCM
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
Expand Down Expand Up @@ -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
27 changes: 27 additions & 0 deletions tests/func/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import shutil

import ruamel.yaml
import pytest

from dvc import api
Expand Down Expand Up @@ -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