source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/shell.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 9.3 KB
Line 
1#
2# Copyright 2007 Google Inc.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# Modified by Massimo Di Pierro so it works with and without GAE with web2py
17# the modified version of this file is still released under the original Apache license
18# and it is not released under the web2py license.
19#
20# This should be compatible with the Apache license since it states:
21# "For the purposes of this License, Derivative Works shall not include works
22#   that remain separable from, or merely link (or bind by name) to the interfaces of,
23#   the Work and Derivative Works thereof."
24#
25# In fact this file is Apache-licensed and it is separable from the rest of web2py.
26
27
28"""
29An interactive, stateful AJAX shell that runs Python code on the server.
30"""
31from __future__ import print_function
32from gluon._compat import ClassType, pickle, StringIO
33import logging
34import new
35import sys
36import traceback
37import types
38import threading
39locker = threading.RLock()
40
41# Set to True if stack traces should be shown in the browser, etc.
42_DEBUG = True
43
44# The entity kind for shell historys. Feel free to rename to suit your app.
45_HISTORY_KIND = '_Shell_History'
46
47# Types that can't be pickled.
48UNPICKLABLE_TYPES = [
49    types.ModuleType,
50    type,
51    ClassType,
52    types.FunctionType,
53]
54
55# Unpicklable statements to seed new historys with.
56INITIAL_UNPICKLABLES = [
57    'import logging',
58    'import os',
59    'import sys',
60]
61
62
63class History:
64    """A shell history. Stores the history's globals.
65
66    Each history globals is stored in one of two places:
67
68    If the global is picklable, it's stored in the parallel globals and
69    global_names list properties. (They're parallel lists to work around the
70    unfortunate fact that the datastore can't store dictionaries natively.)
71
72    If the global is not picklable (e.g. modules, classes, and functions), or if
73    it was created by the same statement that created an unpicklable global,
74    it's not stored directly. Instead, the statement is stored in the
75    unpicklables list property. On each request, before executing the current
76    statement, the unpicklable statements are evaluated to recreate the
77    unpicklable globals.
78
79    The unpicklable_names property stores all of the names of globals that were
80    added by unpicklable statements. When we pickle and store the globals after
81    executing a statement, we skip the ones in unpicklable_names.
82
83    Using Text instead of string is an optimization. We don't query on any of
84    these properties, so they don't need to be indexed.
85    """
86    global_names = []
87    globals = []
88    unpicklable_names = []
89    unpicklables = []
90
91    def set_global(self, name, value):
92        """Adds a global, or updates it if it already exists.
93
94        Also removes the global from the list of unpicklable names.
95
96        Args:
97            name: the name of the global to remove
98            value: any picklable value
99        """
100        blob = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
101
102        if name in self.global_names:
103            index = self.global_names.index(name)
104            self.globals[index] = blob
105        else:
106            self.global_names.append(name)
107            self.globals.append(blob)
108
109        self.remove_unpicklable_name(name)
110
111    def remove_global(self, name):
112        """Removes a global, if it exists.
113
114        Args:
115            name: string, the name of the global to remove
116        """
117        if name in self.global_names:
118            index = self.global_names.index(name)
119            del self.global_names[index]
120            del self.globals[index]
121
122    def globals_dict(self):
123        """Returns a dictionary view of the globals.
124        """
125        return dict((name, pickle.loads(val))
126                    for name, val in zip(self.global_names, self.globals))
127
128    def add_unpicklable(self, statement, names):
129        """Adds a statement and list of names to the unpicklables.
130
131        Also removes the names from the globals.
132
133        Args:
134            statement: string, the statement that created new unpicklable global(s).
135            names: list of strings; the names of the globals created by the statement.
136        """
137        self.unpicklables.append(statement)
138
139        for name in names:
140            self.remove_global(name)
141            if name not in self.unpicklable_names:
142                self.unpicklable_names.append(name)
143
144    def remove_unpicklable_name(self, name):
145        """Removes a name from the list of unpicklable names, if it exists.
146
147        Args:
148            name: string, the name of the unpicklable global to remove
149        """
150        if name in self.unpicklable_names:
151            self.unpicklable_names.remove(name)
152
153
154def represent(obj):
155    """Returns a string representing the given object's value, which should allow the
156    code below to determine whether the object changes over time.
157    """
158    try:
159        return pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
160    except:
161        return repr(obj)
162
163
164def run(history, statement, env={}):
165    """
166    Evaluates a python statement in a given history and returns the result.
167    """
168    history.unpicklables = INITIAL_UNPICKLABLES
169
170    # extract the statement to be run
171    if not statement:
172        return ''
173
174    # the python compiler doesn't like network line endings
175    statement = statement.replace('\r\n', '\n')
176
177    # add a couple newlines at the end of the statement. this makes
178    # single-line expressions such as 'class Foo: pass' evaluate happily.
179    statement += '\n\n'
180
181    # log and compile the statement up front
182    try:
183        logging.info('Compiling and evaluating:\n%s' % statement)
184        compiled = compile(statement, '<string>', 'single')
185    except:
186        return str(traceback.format_exc())
187
188    # create a dedicated module to be used as this statement's __main__
189    statement_module = new.module('__main__')
190
191    # use this request's __builtin__, since it changes on each request.
192    # this is needed for import statements, among other things.
193    import __builtin__
194    statement_module.__builtins__ = __builtin__
195
196    # load the history from the datastore
197    history = History()
198
199    # swap in our custom module for __main__. then unpickle the history
200    # globals, run the statement, and re-pickle the history globals, all
201    # inside it.
202    old_main = sys.modules.get('__main__')
203    output = StringIO()
204    try:
205        sys.modules['__main__'] = statement_module
206        statement_module.__name__ = '__main__'
207        statement_module.__dict__.update(env)
208
209        # re-evaluate the unpicklables
210        for code in history.unpicklables:
211            exec(code, statement_module.__dict__)
212
213        # re-initialize the globals
214        for name, val in history.globals_dict().items():
215            try:
216                statement_module.__dict__[name] = val
217            except:
218                msg = 'Dropping %s since it could not be unpickled.\n' % name
219                output.write(msg)
220                logging.warning(msg + traceback.format_exc())
221                history.remove_global(name)
222
223        # run!
224        old_globals = dict((key, represent(
225            value)) for key, value in statement_module.__dict__.items())
226        try:
227            old_stdout, old_stderr = sys.stdout, sys.stderr
228            try:
229                sys.stderr = sys.stdout = output
230                locker.acquire()
231                exec(compiled, statement_module.__dict__)
232            finally:
233                locker.release()
234                sys.stdout, sys.stderr = old_stdout, old_stderr
235        except:
236            output.write(str(traceback.format_exc()))
237            return output.getvalue()
238
239        # extract the new globals that this statement added
240        new_globals = {}
241        for name, val in statement_module.__dict__.items():
242            if name not in old_globals or represent(val) != old_globals[name]:
243                new_globals[name] = val
244
245        if True in [isinstance(val, tuple(UNPICKLABLE_TYPES))
246                    for val in new_globals.values()]:
247            # this statement added an unpicklable global. store the statement and
248            # the names of all of the globals it added in the unpicklables.
249            history.add_unpicklable(statement, new_globals.keys())
250            logging.debug('Storing this statement as an unpicklable.')
251        else:
252            # this statement didn't add any unpicklables. pickle and store the
253            # new globals back into the datastore.
254            for name, val in new_globals.items():
255                if not name.startswith('__'):
256                    try:
257                        history.set_global(name, val)
258                    except (TypeError, pickle.PicklingError) as ex:
259                        UNPICKLABLE_TYPES.append(type(val))
260                        history.add_unpicklable(statement, new_globals.keys())
261
262    finally:
263        sys.modules['__main__'] = old_main
264    return output.getvalue()
265
266if __name__ == '__main__':
267    history = History()
268    while True:
269        print(run(history, raw_input('>>> ')).rstrip())
Note: See TracBrowser for help on using the repository browser.