Add webpython + instructions
This commit is contained in:
11
webpython/Dockerfile
Normal file
11
webpython/Dockerfile
Normal 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
58
webpython/Makefile
Normal 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
47
webpython/README.md
Normal 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
4078
webpython/turtle.py
Normal file
File diff suppressed because it is too large
Load Diff
174
webpython/webpython.py
Normal file
174
webpython/webpython.py
Normal 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'})
|
Reference in New Issue
Block a user