Skip to content

Commit

Permalink
Initial implementation of python isolated environment.
Browse files Browse the repository at this point in the history
  • Loading branch information
garciparedes committed Jun 22, 2019
1 parent 1a4c90a commit a45a05d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ source_code = [

env = psc.PythonEnvironment(
version='3.7.3',
dependencies=[
pip_dependencies=[
'numpy>=1.14.0',
]
)
Expand Down
31 changes: 31 additions & 0 deletions examples/hello_world_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging

import pascua as psc

logging.basicConfig(level=logging.DEBUG)


def main():
context = {
'size': 100,
}

source_code = [
'import numpy as np',
'random_numbers = np.random.uniform(size=size)',
'numbers = list(map(lambda x: f"number {x}", range(size)))',
]

env = psc.PythonEnvironment(
version='3.7.3',
pip_dependencies=[
'numpy>=1.14.0',
]
)

result = env.exec(source_code, context)
print(result)


if __name__ == '__main__':
main()
32 changes: 28 additions & 4 deletions pascua/environments/abc.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
from abc import ABC, abstractmethod
from io import BytesIO
from typing import Sequence
from uuid import uuid4

import docker

from ..types import Context, SourceCode


class Environment(ABC):

def __init__(self, *args, **kwargs):
self.uuid = str(uuid4())
self.container = None
self.client = docker.from_env()

@property
@abstractmethod
def up(self):
def docker_file(self) -> Sequence[str]:
pass

@abstractmethod
def build(self):
output = BytesIO('\n'.join(self.docker_file).encode())
self.client.images.build(fileobj=output, tag=self.uuid)

def up(self):
self.build()
self.container = self.client.containers.run(self.uuid, 'tail -f /dev/null', detach=True)

def exec(self, source_code: SourceCode, context: Context) -> Context:
pass
if self.container is None:
self.up()
return self._exec(source_code, context)

@abstractmethod
def down(self):
def _exec(self, source_code: SourceCode, context: Context) -> Context:
pass

def down(self):
if self.container is not None:
self.container.stop()
70 changes: 68 additions & 2 deletions pascua/environments/python.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,79 @@
import ast
import logging
from typing import Sequence

from .abc import Environment

from ..types import (
Dependencies,
Version,
Context,
SourceCode,
)
import dill

logger = logging.getLogger(__name__)


class PythonEnvironment(Environment):

def __init__(self, version: Version, dependencies: Dependencies = tuple()):
def __init__(self, version: Version, pip_dependencies: Dependencies = None,
apt_dependencies: Dependencies = tuple(), *args, **kwargs):
super().__init__(*args, **kwargs)

if pip_dependencies is None:
pip_dependencies = list()

pip_dependencies.append("dill>=0.29")

self.version = version
self.dependencies = dependencies
self.pip_dependencies = pip_dependencies
self.apt_dependencies = apt_dependencies

@property
def raw_apt_dependencies(self):
return ' '.join(self.apt_dependencies)

@property
def raw_pip_dependencies(self):
return ' '.join(self.pip_dependencies)

@property
def docker_file(self) -> Sequence[str]:
docker_file = [
f'FROM python:{self.version}',
f'RUN apt-get update && apt-get install -y {self.raw_apt_dependencies}',
f'RUN pip install {self.raw_pip_dependencies}',
]
logger.debug(f"Dockerfile: {docker_file}")
return docker_file

def _exec(self, source_code: SourceCode, context: Context) -> Context:
if not isinstance(source_code, str):
source_code = ';'.join(source_code)

logger.debug(f"Context: {context}")
logger.debug(f"Source Code: {source_code}")

context = dill.dumps(context)
source_code = dill.dumps(source_code)

python_sentence = ';'.join([
f'import dill',
f'context = dill.loads({context})',
f'source_code = dill.loads({source_code})',
f'exec(source_code, context)',
f'context.pop("__builtins__", None)',
f'context = dill.dumps(context)',
f'print(context)',
]).replace('"', '\\"')
logging.debug("Python Sentence: f{python_sentence}")

python_command = f'python -c "{python_sentence}"'
logging.debug(f"Python command: {python_command}")

result = self.container.exec_run(python_command)
logger.debug(f"Result: {result.output.decode()}")
context = ast.literal_eval(result.output.decode())
context = dill.loads(context)
return context
4 changes: 2 additions & 2 deletions pascua/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, Dict, Union, Sequence
from typing import Any, Dict, Union, Sequence, MutableSequence

Dependency = str
Dependencies = Sequence[Dependency]
Dependencies = MutableSequence[Dependency]

Version = Union[str, Sequence[str]]

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
long_description_content_type='text/markdown',
packages=find_packages(),
install_requires=[
'docker>=4.0.0'
'docker>=4.0.0',
'dill>=0.2.9'
],
)

0 comments on commit a45a05d

Please sign in to comment.