|
| 1 | +"""For running Python code that could interrupt itself at any time |
| 2 | +
|
| 3 | +in order to, for example, ask for a read on stdin, or a write on stdout |
| 4 | +
|
| 5 | +The CodeRunner spawns a greenlet to run code it, and that code can suspend |
| 6 | +its own execution to ask the main thread to refresh the display or get information. |
| 7 | +""" |
| 8 | + |
1 | 9 | import code |
2 | 10 | import signal |
3 | 11 | import sys |
4 | 12 | import greenlet |
5 | 13 | import logging |
6 | 14 |
|
7 | 15 | class SigintHappened(object): |
8 | | - pass |
| 16 | + """If this class is returned, a SIGINT happened while the main thread""" |
9 | 17 |
|
10 | 18 | class SystemExitFromCodeThread(SystemExit): |
| 19 | + """If this class is returned, a SystemExit happened while in the code thread""" |
11 | 20 | pass |
12 | 21 |
|
13 | 22 | class CodeRunner(object): |
14 | | - """Runs user code in an interpreter, taking care of stdout/in/err""" |
| 23 | + """Runs user code in an interpreter. |
| 24 | +
|
| 25 | + Running code requests a refresh by calling refresh_and_get_value(), which |
| 26 | + suspends execution of the code and switches back to the main thread |
| 27 | +
|
| 28 | + After load_code() is called with the source code to be run, |
| 29 | + the run_code() method should be called to start running the code. |
| 30 | + The running code may request screen refreshes and user input |
| 31 | + by calling the refresh_and_get_value and wait_and_get_value calls |
| 32 | + respectively. When these are called, the running source code cedes |
| 33 | + control, and the current run_code() method call returns. |
| 34 | +
|
| 35 | + The return value of run_code() determines whether the method ought |
| 36 | + to be called again to complete execution of the source code. |
| 37 | +
|
| 38 | + Once the screen refresh has occurred or the requested user input |
| 39 | + has been gathered, run_code() should be called again, passing in any |
| 40 | + requested user input. This continues until run_code returns 'done'. |
| 41 | +
|
| 42 | + Question: How does the caller of run_code know that user input ought |
| 43 | + to be returned? |
| 44 | + """ |
15 | 45 | def __init__(self, interp=None, stuff_a_refresh_request=lambda:None): |
| 46 | + """ |
| 47 | + interp is an interpreter object to use. By default a new one is |
| 48 | + created. |
| 49 | +
|
| 50 | + stuff_a_refresh_request is a function that will be called each time |
| 51 | + the running code asks for a refresh - to, for example, update the screen. |
| 52 | + """ |
16 | 53 | self.interp = interp or code.InteractiveInterpreter() |
17 | 54 | self.source = None |
18 | 55 | self.main_greenlet = greenlet.getcurrent() |
19 | 56 | self.code_greenlet = None |
20 | 57 | self.stuff_a_refresh_request = stuff_a_refresh_request |
21 | | - self.code_is_waiting = False |
| 58 | + self.code_is_waiting = False # waiting for response from main thread |
22 | 59 | self.sigint_happened = False |
23 | 60 | self.orig_sigint_handler = None |
24 | 61 |
|
25 | 62 | @property |
26 | 63 | def running(self): |
| 64 | + """Returns greenlet if code has been loaded greenlet has been started""" |
27 | 65 | return self.source and self.code_greenlet |
28 | 66 |
|
29 | 67 | def load_code(self, source): |
30 | 68 | """Prep code to be run""" |
| 69 | + assert self.source is None, "you shouldn't load code when some is already running" |
31 | 70 | self.source = source |
32 | 71 | self.code_greenlet = None |
33 | 72 |
|
@@ -77,6 +116,7 @@ def run_code(self, for_code=None): |
77 | 116 | raise ValueError("Not a valid value from code greenlet: %r" % request) |
78 | 117 |
|
79 | 118 | def sigint_handler(self, *args): |
| 119 | + """SIGINT handler to use while code is running or request being fufilled""" |
80 | 120 | if greenlet.getcurrent() is self.code_greenlet: |
81 | 121 | logging.debug('sigint while running user code!') |
82 | 122 | raise KeyboardInterrupt() |
@@ -109,11 +149,11 @@ def refresh_and_get_value(self): |
109 | 149 | return value |
110 | 150 |
|
111 | 151 | class FakeOutput(object): |
112 | | - def __init__(self, coderunner, please): |
| 152 | + def __init__(self, coderunner, on_write): |
113 | 153 | self.coderunner = coderunner |
114 | | - self.please = please |
| 154 | + self.on_write = on_write |
115 | 155 | def write(self, *args, **kwargs): |
116 | | - self.please(*args, **kwargs) |
| 156 | + self.on_write(*args, **kwargs) |
117 | 157 | return self.coderunner.refresh_and_get_value() |
118 | 158 | def writelines(self, l): |
119 | 159 | for s in l: |
|
0 commit comments