Skip to content

Commit

Permalink
Reworked defining tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
mikicz committed Feb 25, 2018
1 parent 9d0a6e9 commit a1f75bb
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 203 deletions.
2 changes: 1 addition & 1 deletion arca/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_requirements_file(self, path: Path) -> Optional[Path]:
return requirements_file

def create_script(self, task: Task, venv_path: Path=None) -> Tuple[str, str]:
script = task.build_script(venv_path)
script = task.build_script()
script_hash = hashlib.sha1(bytes(script, "utf-8")).hexdigest()

return f"{script_hash}.py", script
Expand Down
2 changes: 0 additions & 2 deletions arca/backend/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ def get_or_build_image(self, name, tag, dockerfile, pull=True, build_context: Op
if build_context is None:
self.client.images.build(
fileobj=dockerfile,
pull=True,
tag=f"{name}:{tag}"
)
else:
Expand All @@ -184,7 +183,6 @@ def get_or_build_image(self, name, tag, dockerfile, pull=True, build_context: Op

self.client.images.build(
path=str(build_context.resolve()),
pull=pull,
dockerfile=dockerfile_file.name,
tag=f"{name}:{tag}"
)
Expand Down
43 changes: 22 additions & 21 deletions arca/backend/vagrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import subprocess
from pathlib import Path
from textwrap import dedent
from uuid import uuid4

from fabric import api
Expand Down Expand Up @@ -86,27 +87,27 @@ def create_vagrant_file(self, repo: str, branch: str, git_repo: Repo, repo_path:

vagrant_file.parent.mkdir(exist_ok=True, parents=True)

vagrant_file.write_text(f"""
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "{self.box}"
config.ssh.insert_key = true
config.vm.provision "docker" do |d|
d.pull_images "{image_name}:{image_tag}"
d.run "{image_name}:{image_tag}",
name: "{container_name}",
args: "-t -w {workdir}",
cmd: "bash -i"
end
config.vm.synced_folder ".", "/vagrant"
config.vm.synced_folder "{repo_path}", "/srv/data"
config.vm.provider "{self.provider}"
end
""")
vagrant_file.write_text(dedent(f"""
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "{self.box}"
config.ssh.insert_key = true
config.vm.provision "docker" do |d|
d.pull_images "{image_name}:{image_tag}"
d.run "{image_name}:{image_tag}",
name: "{container_name}",
args: "-t -w {workdir}",
cmd: "bash -i"
end
config.vm.synced_folder ".", "/vagrant"
config.vm.synced_folder "{repo_path}", "/srv/data"
config.vm.provider "{self.provider}"
end
"""))

def run(self, repo: str, branch: str, task: Task, git_repo: Repo, repo_path: Path):
""" Gets or creates Vagrantfile, starts up a VM with it, executes Fabric script over SSH, returns result.
Expand Down
112 changes: 56 additions & 56 deletions arca/task.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,92 @@
import hashlib
import json
import re

from pathlib import Path
from pprint import pformat
from typing import Optional, Tuple, Any, Dict, Iterable
from textwrap import dedent, indent
from typing import Optional, Any, Dict, Iterable

from entrypoints import EntryPoint, BadEntryPoint

from .exceptions import TaskMisconfigured

custom_pattern = re.compile(r"[.\w]*:[.\w]*")


class Task:

def __init__(self, function_call: str, *,
imports: Optional[Iterable[str]]=None,
from_imports: Optional[Iterable[Tuple[str, str]]]=None,
def __init__(self, entry_point: str, *,
args: Optional[Iterable[Any]]=None,
kwargs: Optional[Dict[str, Any]]=None) -> None:
if re.match(r".*\s.*", function_call):
raise TaskMisconfigured("function_call contains a whitespace")

self.function_call = function_call
self.imports = list(imports or [])
self.from_imports = list(from_imports or [])
if not custom_pattern.match(entry_point):
raise TaskMisconfigured("Task entry point must be an object, not a module.")

try:
self.entry_point = EntryPoint.from_string(entry_point, "task")
except BadEntryPoint:
raise TaskMisconfigured("Incorrectly defined entry point.")

self.args = list(args or [])
self.kwargs = dict(kwargs or {})

def __repr__(self):
return f"Task({self.function_call})"

def build_imports(self):
return "\r\n".join([f" import {x}" for x in self.imports])

def build_from_imports(self):
return "\r\n".join([f" from {x[0]} import {x[1]}" for x in self.from_imports])
return f"Task({self.entry_point})"

def build_function_call(self):
if len(self.args) and len(self.kwargs):
return "{}(*{}, **{})".format(
self.function_call,
return "{!r}.load()(*{}, **{})".format(
self.entry_point,
pformat(self.args),
pformat(self.kwargs)
)
elif len(self.args):
return "{}(*{})".format(
self.function_call,
return "{!r}.load()(*{})".format(
self.entry_point,
pformat(self.args)
)
elif len(self.kwargs):
return "{}(**{})".format(
self.function_call,
return "{!r}.load()(**{})".format(
self.entry_point,
pformat(self.kwargs)
)
else:
return f"{self.function_call}()"

def build_script(self, venv_path: Path=None) -> str:
result = ""

if venv_path is not None:
result += "#!" + str(venv_path.resolve() / "bin" / "python3") + "\r\n\r\n"
else:
result += "#!python3\r\n"

result += "# encoding=utf-8\r\n\r\n"
result += "import json\r\n"
result += "import traceback\r\n"
result += "import sys\r\n"
result += "import os\r\n"
result += "sys.path.insert(1, os.getcwd())\r\n"
result += "try:\r\n"

result += self.build_imports() + "\r\n"
result += self.build_from_imports() + "\r\n"

result += f"""
res = {self.build_function_call()}
print(json.dumps({{"success": True, "result": res}}))
except:
print(json.dumps({{"success": False, "error": traceback.format_exc()}}))
"""

return result
return "{!r}.load()()".format(self.entry_point)

def build_script(self) -> str:
function_call = self.build_function_call()
function_call = indent(function_call, " ", lambda x: not x.startswith("EntryPoint"))

return dedent(f"""
# encoding=utf-8
import json
import traceback
import sys
import os
from importlib import import_module
class EntryPoint:
def __init__(self, name, module_name, object_name, *args, **kwargs):
self.module_name = module_name
self.object_name = object_name
def load(self):
mod = import_module(self.module_name)
obj = mod
for attr in self.object_name.split('.'):
obj = getattr(obj, attr)
return obj
sys.path.insert(1, os.getcwd())
try:
res = {function_call}
print(json.dumps({{"success": True, "result": res}}))
except:
print(json.dumps({{"success": False, "error": traceback.format_exc()}}))
""")

def serialize(self):
return hashlib.sha1(bytes(json.dumps({
"function_call": self.function_call,
"imports": self.imports,
"from_imports": self.from_imports,
"entry_point": self.entry_point.__repr__(),
"args": self.args,
"kwargs": self.kwargs,
}), "utf-8")).hexdigest()
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ pytest-mock
requests
python-vagrant
fabric3
entrypoints>=0.2.3

20 changes: 4 additions & 16 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from git import Repo

from arca import Arca, VenvBackend, DockerBackend, Task, CurrentEnvironmentBackend
from arca.exceptions import BuildError

from common import BASE_DIR, RETURN_DJANGO_VERSION_FUNCTION, RETURN_STR_FUNCTION, SECOND_RETURN_STR_FUNCTION, \
TEST_UNICODE, ARG_STR_FUNCTION, KWARG_STR_FUNCTION
Expand Down Expand Up @@ -57,8 +56,7 @@ def test_backends(backend, requirements_location, file_location):
repo_url = f"file://{git_dir}"

task = Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")]
"test_file:return_str_function",
)

result = arca.run(repo_url, "master", task)
Expand Down Expand Up @@ -117,20 +115,12 @@ def test_backends(backend, requirements_location, file_location):
assert result.output == "1.11.5"

django_task = Task(
"django.get_version",
imports=["django"]
"django:get_version"
)

result = arca.run(repo_url, "master", django_task)
assert result.output == "1.11.5"

django_task_error = Task(
"django.get_version",
)

with pytest.raises(BuildError):
arca.run(f"file://{git_dir}", "master", django_task_error)

if isinstance(backend, CurrentEnvironmentBackend):
backend._uninstall("django")

Expand All @@ -139,8 +129,7 @@ def test_backends(backend, requirements_location, file_location):
repo.index.commit("Argument function")

assert arca.run(repo_url, "master", Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")],
"test_file:return_str_function",
args=[TEST_UNICODE]
)).output == TEST_UNICODE[::-1]

Expand All @@ -149,7 +138,6 @@ def test_backends(backend, requirements_location, file_location):
repo.index.commit("Keyword argument function")

assert arca.run(repo_url, "master", Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")],
"test_file:return_str_function",
kwargs={"kwarg": TEST_UNICODE}
)).output == TEST_UNICODE[::-1]
3 changes: 1 addition & 2 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ def test_cache(mocker, backend, cache_backend, arguments):
git_repo.index.commit("Added requirements")

django_task = Task(
"django.get_version",
imports=["django"]
"django:get_version",
)

repo = f"file://{git_dir}"
Expand Down
9 changes: 3 additions & 6 deletions tests/test_current_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def test_strategy_ignore(mocker, strategy):
repo.index.commit("Initial")

task = Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")]
"test_file:return_str_function",
)

# nor the current env or the repo has any requirements, install requirements is not called at all
Expand Down Expand Up @@ -168,8 +167,7 @@ def test_strategy_raise(strategy):
repo.index.commit("Initial")

task = Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")]
"test_file:return_str_function",
)

# nor the current env or the repo has any requirements, install requirements is not called at all
Expand Down Expand Up @@ -240,8 +238,7 @@ def test_strategy_install_extra(mocker, strategy):
repo.index.commit("Initial")

task = Task(
"return_str_function",
from_imports=[("test_file", "return_str_function")]
"test_file:return_str_function",
)

# nor the current env or the repo has any requirements, install requirements is not called at all
Expand Down

0 comments on commit a1f75bb

Please sign in to comment.