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