Add webpython + instructions

This commit is contained in:
Janusch Jacoby
2015-09-15 19:56:10 +02:00
parent e6eeebfd4b
commit f21310e5fe
5 changed files with 4368 additions and 0 deletions

11
webpython/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM ubuntu:14.04
MAINTAINER "Martin v. Löwis"
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ADD webpython.py /usr/lib/python3.4/webpython.py
RUN rm /usr/lib/python3.4/turtle.py
ADD turtle.py /usr/lib/python3.4/turtle.py
ADD assess /usr/lib/python3.4/assess
RUN adduser --disabled-password --gecos Python python
USER python
WORKDIR /home/python

58
webpython/Makefile Normal file
View File

@ -0,0 +1,58 @@
CODEMIRROR=html/js/codemirror.js
all: logs
logs:
mkdir logs
# These are put into source control
generated: $(CODEMIRROR)
$(CODEMIRROR): CodeMirror/lib/codemirror.js CodeMirror/mode/python/python.js
(cd CodeMirror; bin/compress codemirror python) > $@
run: all kill
twistd -l logs/webpython.log -y webpython.tac
runpy: killpy
twistd --pidfile pylaunch.pid -l logs/pylaunch.log -y pylaunch.tac
manager: all killmanager
twistd --pidfile manager.pid -l logs/manager.log -y manager.tac
kill:
if [ -f twistd.pid ];\
then\
kill `cat twistd.pid`;\
fi
killpy:
if [ -f pylaunch.pid ];\
then\
kill `cat pylaunch.pid`;\
fi
killmanager:
if [ -f manager.pid ];\
then\
kill `cat manager.pid`;\
fi
docker:
docker.io build --rm -t webpython .
update:
git pull
make docker
service webpython-worker restart
rmexited:
docker.io ps -a|grep 'Exit '|awk '{print $$1;}'|xargs docker.io rm
rmi:
docker.io rmi $$(docker.io images | grep "^<none>" | awk '{print $$3;}')
killold:
-docker.io ps | egrep 'hours? ago' |awk '{print $$1}'|xargs docker.io kill
-killall -o 30m -9 python3

47
webpython/README.md Normal file
View File

@ -0,0 +1,47 @@
Local setup
===========
1. `git checkout webpython-hybrid`
2. Make sure to install all dependencies and migrations:
rake db:migrate
bundle install
3. Create a new docker image containing the Turtle library and the i/o wrapper:
cd webpython
docker build -t IMAGE_NAME .
4. Configure your Docker host at `config/docker.yml.erb`. Make sure to add a websocket host, for example like this (this is probably different for you):
host: tcp://localhost:2375
ws_host: ws://localhost:2375
5. Run the CodeOcean server with `rails s -p 3333`
6. Login with admin@example.org (pw: admin) and create a new execution environment picking the newly created Docker image from the dropdown. Set the initial command to:
cd /usr/lib/python3.4 && python3 webpython.py
7. Create a new exercise for the newly created execution environment with an arbritrary main file.
8. Implement the exercise. The code below can be used as an example to see the canvas and I/O in action:
import turtle
wn = turtle.Screen()
alex = turtle.Turtle()
# i/o test
print("hello!")
print("please enter your name")
name = input()
print("your name is", name)
# canvas test
alex.forward(50)
alex.right(90)
alex.forward(30)
alex.right(90)
alex.forward(30)
wn.mainloop()

4078
webpython/turtle.py Normal file

File diff suppressed because it is too large Load Diff

174
webpython/webpython.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/python3
# Main interpreter entry for webpython
import io, select, sys, os, threading, code
import pickle, struct, builtins, json
#, ressource
from queue import Queue
# hard limit to 64M
#try:
# resource.setrlimit(resource.RLIMIT_AS, (1<<26, 1<<26))
#except ValueError:
# tried to raise it
# pass
# output limit 16MiB
output_capacity = 16*1024*1024
# adapted from IDLE (PyShell.py)
class PseudoFile(io.TextIOBase):
def __init__(self, shell, name):
self.shell = shell
self._name = name
@property
def encoding(self):
return "UTF-8"
@property
def name(self):
return '<%s>' % self._name
def isatty(self):
return True
class PseudoInputFile(PseudoFile):
def __init__(self, shell, name):
PseudoFile.__init__(self, shell, name)
self._line_buffer = ''
def readable(self):
return True
def read(self, size=-1):
if self.closed:
raise ValueError("read from closed file")
if size is None:
size = -1
elif not isinstance(size, int):
raise TypeError('must be int, not ' + type(size).__name__)
result = self._line_buffer
self._line_buffer = ''
if size < 0:
while True:
line = self.shell.readline()
if not line: break
result += line
else:
while len(result) < size:
line = self.shell.readline()
if not line: break
result += line
self._line_buffer = result[size:]
result = result[:size]
return result
def readline(self, size=-1):
if self.closed:
raise ValueError("read from closed file")
if size is None:
size = -1
elif not isinstance(size, int):
raise TypeError('must be int, not ' + type(size).__name__)
line = self._line_buffer or self.shell.readline()
if size < 0:
size = len(line)
self._line_buffer = line[size:]
return line[:size]
def close(self):
self.shell.close()
class PseudoOutputFile(PseudoFile):
def writable(self):
return True
def write(self, s):
if self.closed:
raise ValueError("write to closed file")
if not isinstance(s, str):
raise TypeError('must be str, not ' + type(s).__name__)
return self.shell.write(s, self._name)
# RPC proxy
orig_stdin = sys.stdin
orig_stdout = sys.stdout
orig_stderr = sys.stderr
class Shell:
def __init__(self):
self.stdin = io.FileIO(0)
self.buf = b''
self.canvas = []
self.messages = []
self.capacity = output_capacity
# PseudoFile interaction
#def readline(self):
# self.sendpickle({'cmd':'readline',
# 'stream':'stdin',
# })
# return self.inputq.get()
def write(self, data, name):
self.sendpickle({'cmd':'write',
'stream':name,
'data':data
})
def input(self, prompt=''):
self.sendpickle({'cmd':'input',
'stream':'stdin',
'data':prompt})
result = self.receivemsg()
return result['data']
# internal
def sendpickle(self, data):
data = json.dumps(data) + "\n\r"
self.capacity -= len(data)
if self.capacity < 0:
data = json.dumps({'cmd':'stop',
'timedout':True}, 2)
orig_stdout.write(data)
raise SystemExit
orig_stdout.write(data)
def receivepickle(self):
msg = json.loads(orig_stdin.readline())
if msg['cmd'] == 'canvasevent':
self.canvas.append(msg)
else:
self.messages.append(msg)
def receivemsg(self):
while not self.messages:
self.receivepickle()
return self.messages.pop()
def receivecanvas(self):
while not self.canvas:
self.receivepickle()
return self.canvas.pop(0)
# Hide 0/1 from sys
shell = Shell()
sys.__stdin__ = sys.stdin = PseudoInputFile(shell, 'stdin')
sys.__stdout__ = sys.stdout = PseudoOutputFile(shell, 'stdout')
#sys.__stderr__ = sys.stderr = PseudoOutputFile(shell, 'stderr')
builtins.input = shell.input
#iothread = threading.Thread(target=shell.run)
#iothread.start()
if __name__ == '__main__':
#script = "print (\"Hello world.\")\n\rmsg = input()\n\rprint(\"Out:\")\n\rprint(msg)"
#with open("programm.py", "w", encoding='utf-8') as f:
# f.write(script)
with open(os.path.join("/", "workspace", "exercise.py"), "r", encoding='utf-8') as f:
script = f.read()
c = compile(script, "exercise.py", 'exec')
exec(c, {})
# work-around for docker not terminating properly
shell.sendpickle({'cmd':'exit'})