1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
7 limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
8 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
9
10 """
11
12 import os
13 import sys
14 import code
15 import logging
16 import types
17 import re
18 import optparse
19 import glob
20 import traceback
21 import fileutils
22 from settings import global_settings
23 from utils import web2py_uuid
24 from compileapp import build_environment, read_pyc, run_models_in
25 from restricted import RestrictedError
26 from globals import Request, Response, Session
27 from storage import Storage
28 from admin import w2p_unpack
29 from dal import BaseAdapter
30
31 logger = logging.getLogger("web2py")
32
33
34 -def exec_environment(
35 pyfile='',
36 request=None,
37 response=None,
38 session=None,
39 ):
40 """
41 .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request()
42 [, response=Response[, session=Session()]]]])
43
44 Environment builder and module loader.
45
46
47 Builds a web2py environment and optionally executes a Python
48 file into the environment.
49 A Storage dictionary containing the resulting environment is returned.
50 The working directory must be web2py root -- this is the web2py default.
51
52 """
53
54 if request is None:
55 request = Request()
56 if response is None:
57 response = Response()
58 if session is None:
59 session = Session()
60
61 if request.folder is None:
62 mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile)
63 if mo:
64 appname = mo.group('appname')
65 request.folder = os.path.join('applications', appname)
66 else:
67 request.folder = ''
68 env = build_environment(request, response, session, store_current=False)
69 if pyfile:
70 pycfile = pyfile + 'c'
71 if os.path.isfile(pycfile):
72 exec read_pyc(pycfile) in env
73 else:
74 execfile(pyfile, env)
75 return Storage(env)
76
77
78 -def env(
79 a,
80 import_models=False,
81 c=None,
82 f=None,
83 dir='',
84 extra_request={},
85 ):
86 """
87 Return web2py execution environment for application (a), controller (c),
88 function (f).
89 If import_models is True the exec all application models into the
90 environment.
91
92 extra_request allows you to pass along any extra
93 variables to the request object before your models
94 get executed. This was mainly done to support
95 web2py_utils.test_runner, however you can use it
96 with any wrapper scripts that need access to the
97 web2py environment.
98 """
99
100 request = Request()
101 response = Response()
102 session = Session()
103 request.application = a
104
105
106
107 if not dir:
108 request.folder = os.path.join('applications', a)
109 else:
110 request.folder = dir
111 request.controller = c or 'default'
112 request.function = f or 'index'
113 response.view = '%s/%s.html' % (request.controller,
114 request.function)
115 request.env.path_info = '/%s/%s/%s' % (a, c, f)
116 request.env.http_host = '127.0.0.1:8000'
117 request.env.remote_addr = '127.0.0.1'
118 request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae
119
120 for k, v in extra_request.items():
121 request[k] = v
122
123
124
125 def check_credentials(request, other_application='admin'):
126 return True
127
128 fileutils.check_credentials = check_credentials
129
130 environment = build_environment(request, response, session)
131
132 if import_models:
133 try:
134 run_models_in(environment)
135 except RestrictedError, e:
136 sys.stderr.write(e.traceback + '\n')
137 sys.exit(1)
138
139 environment['__name__'] = '__main__'
140 return environment
141
142
144 pythonrc = os.environ.get('PYTHONSTARTUP')
145 if pythonrc and os.path.isfile(pythonrc):
146 def execfile_getlocals(file):
147 execfile(file)
148 return locals()
149 try:
150 return execfile_getlocals(pythonrc)
151 except NameError:
152 pass
153 return dict()
154
155
156 -def run(
157 appname,
158 plain=False,
159 import_models=False,
160 startfile=None,
161 bpython=False,
162 python_code=False
163 ):
164 """
165 Start interactive shell or run Python script (startfile) in web2py
166 controller environment. appname is formatted like:
167
168 a web2py application name
169 a/c exec the controller c into the application environment
170 """
171
172 (a, c, f) = parse_path_info(appname)
173 errmsg = 'invalid application name: %s' % appname
174 if not a:
175 die(errmsg)
176 adir = os.path.join('applications', a)
177 if not os.path.exists(adir):
178 if sys.stdin and not sys.stdin.name == '/dev/null':
179 confirm = raw_input(
180 'application %s does not exist, create (y/n)?' % a)
181 else:
182 logging.warn('application does not exist and will not be created')
183 return
184 if confirm.lower() in ['y', 'yes']:
185
186 os.mkdir(adir)
187 w2p_unpack('welcome.w2p', adir)
188 for subfolder in ['models', 'views', 'controllers', 'databases',
189 'modules', 'cron', 'errors', 'sessions',
190 'languages', 'static', 'private', 'uploads']:
191 subpath = os.path.join(adir, subfolder)
192 if not os.path.exists(subpath):
193 os.mkdir(subpath)
194 db = os.path.join(adir, 'models/db.py')
195 if os.path.exists(db):
196 data = fileutils.read_file(db)
197 data = data.replace(
198 '<your secret key>', 'sha512:' + web2py_uuid())
199 fileutils.write_file(db, data)
200
201 if c:
202 import_models = True
203 _env = env(a, c=c, f=f, import_models=import_models)
204 if c:
205 cfile = os.path.join('applications', a, 'controllers', c + '.py')
206 if not os.path.isfile(cfile):
207 cfile = os.path.join('applications', a, 'compiled',
208 "controllers_%s_%s.pyc" % (c, f))
209 if not os.path.isfile(cfile):
210 die(errmsg)
211 else:
212 exec read_pyc(cfile) in _env
213 else:
214 execfile(cfile, _env)
215
216 if f:
217 exec ('print %s()' % f, _env)
218 return
219
220 _env.update(exec_pythonrc())
221 if startfile:
222 try:
223 ccode = None
224 if startfile.endswith('.pyc'):
225 ccode = read_pyc(startfile)
226 exec ccode in _env
227 else:
228 execfile(startfile, _env)
229
230 if import_models:
231 BaseAdapter.close_all_instances('commit')
232 except Exception, e:
233 print traceback.format_exc()
234 if import_models:
235 BaseAdapter.close_all_instances('rollback')
236 elif python_code:
237 try:
238 exec(python_code, _env)
239 if import_models:
240 BaseAdapter.close_all_instances('commit')
241 except Exception, e:
242 print traceback.format_exc()
243 if import_models:
244 BaseAdapter.close_all_instances('rollback')
245 else:
246 if not plain:
247 if bpython:
248 try:
249 import bpython
250 bpython.embed(locals_=_env)
251 return
252 except:
253 logger.warning(
254 'import bpython error; trying ipython...')
255 else:
256 try:
257 import IPython
258 if IPython.__version__ >= '0.11':
259 from IPython.frontend.terminal.embed import InteractiveShellEmbed
260 shell = InteractiveShellEmbed(user_ns=_env)
261 shell()
262 return
263 else:
264
265
266 if '__builtins__' in _env:
267 del _env['__builtins__']
268 shell = IPython.Shell.IPShell(argv=[], user_ns=_env)
269 shell.mainloop()
270 return
271 except:
272 logger.warning(
273 'import IPython error; use default python shell')
274 try:
275 import readline
276 import rlcompleter
277 except ImportError:
278 pass
279 else:
280 readline.set_completer(rlcompleter.Completer(_env).complete)
281 readline.parse_and_bind('tab:complete')
282 code.interact(local=_env)
283
284
286 """
287 Parse path info formatted like a/c/f where c and f are optional
288 and a leading / accepted.
289 Return tuple (a, c, f). If invalid path_info a is set to None.
290 If c or f are omitted they are set to None.
291 """
292
293 mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$',
294 path_info)
295 if mo:
296 return (mo.group('a'), mo.group('c'), mo.group('f'))
297 else:
298 return (None, None, None)
299
300
302 print >> sys.stderr, msg
303 sys.exit(1)
304
305
306 -def test(testpath, import_models=True, verbose=False):
307 """
308 Run doctests in web2py environment. testpath is formatted like:
309
310 a tests all controllers in application a
311 a/c tests controller c in application a
312 a/c/f test function f in controller c, application a
313
314 Where a, c and f are application, controller and function names
315 respectively. If the testpath is a file name the file is tested.
316 If a controller is specified models are executed by default.
317 """
318
319 import doctest
320 if os.path.isfile(testpath):
321 mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath)
322 if not mo:
323 die('test file is not in application directory: %s'
324 % testpath)
325 a = mo.group('a')
326 c = f = None
327 files = [testpath]
328 else:
329 (a, c, f) = parse_path_info(testpath)
330 errmsg = 'invalid test path: %s' % testpath
331 if not a:
332 die(errmsg)
333 cdir = os.path.join('applications', a, 'controllers')
334 if not os.path.isdir(cdir):
335 die(errmsg)
336 if c:
337 cfile = os.path.join(cdir, c + '.py')
338 if not os.path.isfile(cfile):
339 die(errmsg)
340 files = [cfile]
341 else:
342 files = glob.glob(os.path.join(cdir, '*.py'))
343 for testfile in files:
344 globs = env(a, import_models)
345 ignores = globs.keys()
346 execfile(testfile, globs)
347
348 def doctest_object(name, obj):
349 """doctest obj and enclosed methods and classes."""
350
351 if type(obj) in (types.FunctionType, types.TypeType,
352 types.ClassType, types.MethodType,
353 types.UnboundMethodType):
354
355
356
357 globs = env(a, c=c, f=f, import_models=import_models)
358 execfile(testfile, globs)
359 doctest.run_docstring_examples(obj, globs=globs,
360 name='%s: %s' % (os.path.basename(testfile),
361 name), verbose=verbose)
362 if type(obj) in (types.TypeType, types.ClassType):
363 for attr_name in dir(obj):
364
365
366
367 o = eval('%s.%s' % (name, attr_name), globs)
368 doctest_object(attr_name, o)
369
370 for (name, obj) in globs.items():
371 if name not in ignores and (f is None or f == name):
372 doctest_object(name, obj)
373
374
376 usage = """
377 %prog [options] pythonfile
378 """
379 return usage
380
381
383 if argv is None:
384 argv = sys.argv
385
386 parser = optparse.OptionParser(usage=get_usage())
387
388 parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME',
389 help='run web2py in interactive shell or IPython(if installed) ' +
390 'with specified appname')
391 msg = 'run web2py in interactive shell or bpython (if installed) with'
392 msg += ' specified appname (if app does not exist it will be created).'
393 msg += '\n Use combined with --shell'
394 parser.add_option(
395 '-B',
396 '--bpython',
397 action='store_true',
398 default=False,
399 dest='bpython',
400 help=msg,
401 )
402 parser.add_option(
403 '-P',
404 '--plain',
405 action='store_true',
406 default=False,
407 dest='plain',
408 help='only use plain python shell, should be used with --shell option',
409 )
410 parser.add_option(
411 '-M',
412 '--import_models',
413 action='store_true',
414 default=False,
415 dest='import_models',
416 help='auto import model files, default is False, ' +
417 ' should be used with --shell option',
418 )
419 parser.add_option(
420 '-R',
421 '--run',
422 dest='run',
423 metavar='PYTHON_FILE',
424 default='',
425 help='run PYTHON_FILE in web2py environment, ' +
426 'should be used with --shell option',
427 )
428
429 (options, args) = parser.parse_args(argv[1:])
430
431 if len(sys.argv) == 1:
432 parser.print_help()
433 sys.exit(0)
434
435 if len(args) > 0:
436 startfile = args[0]
437 else:
438 startfile = ''
439 run(options.shell, options.plain, startfile=startfile,
440 bpython=options.bpython)
441
442
443 if __name__ == '__main__':
444 execute_from_command_line()
445