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 if global_settings.cmd_options:
135 ip = global_settings.cmd_options.ip
136 port = global_settings.cmd_options.port
137 else:
138 ip, port = '127.0.0.1', '8000'
139 request.env.http_host = '%s:%s' % (ip,port)
140 request.env.remote_addr = '127.0.0.1'
141 request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae
142
143 for k, v in extra_request.items():
144 request[k] = v
145
146 path_info = '/%s/%s/%s' % (a, c, f)
147 if request.args:
148 path_info = '%s/%s' % (path_info, '/'.join(request.args))
149 if request.vars:
150 vars = ['%s=%s' % (k,v) if v else '%s' % k
151 for (k,v) in request.vars.iteritems()]
152 path_info = '%s?%s' % (path_info, '&'.join(vars))
153 request.env.path_info = path_info
154
155
156
157 def check_credentials(request, other_application='admin'):
158 return True
159
160 fileutils.check_credentials = check_credentials
161
162 environment = build_environment(request, response, session)
163
164 if import_models:
165 try:
166 run_models_in(environment)
167 except RestrictedError, e:
168 sys.stderr.write(e.traceback + '\n')
169 sys.exit(1)
170
171 environment['__name__'] = '__main__'
172 return environment
173
174
176 pythonrc = os.environ.get('PYTHONSTARTUP')
177 if pythonrc and os.path.isfile(pythonrc):
178 def execfile_getlocals(file):
179 execfile(file)
180 return locals()
181 try:
182 return execfile_getlocals(pythonrc)
183 except NameError:
184 pass
185 return dict()
186
187
188 -def run(
189 appname,
190 plain=False,
191 import_models=False,
192 startfile=None,
193 bpython=False,
194 python_code=False,
195 cronjob=False):
196 """
197 Start interactive shell or run Python script (startfile) in web2py
198 controller environment. appname is formatted like:
199
200 a web2py application name
201 a/c exec the controller c into the application environment
202 """
203
204 (a, c, f, args, vars) = parse_path_info(appname, av=True)
205 errmsg = 'invalid application name: %s' % appname
206 if not a:
207 die(errmsg)
208 adir = os.path.join('applications', a)
209
210 if not os.path.exists(adir):
211 if sys.stdin and not sys.stdin.name == '/dev/null':
212 confirm = raw_input(
213 'application %s does not exist, create (y/n)?' % a)
214 else:
215 logging.warn('application does not exist and will not be created')
216 return
217 if confirm.lower() in ['y', 'yes']:
218
219 os.mkdir(adir)
220 w2p_unpack('welcome.w2p', adir)
221 for subfolder in ['models', 'views', 'controllers', 'databases',
222 'modules', 'cron', 'errors', 'sessions',
223 'languages', 'static', 'private', 'uploads']:
224 subpath = os.path.join(adir, subfolder)
225 if not os.path.exists(subpath):
226 os.mkdir(subpath)
227 db = os.path.join(adir, 'models/db.py')
228 if os.path.exists(db):
229 data = fileutils.read_file(db)
230 data = data.replace(
231 '<your secret key>', 'sha512:' + web2py_uuid())
232 fileutils.write_file(db, data)
233
234 if c:
235 import_models = True
236 extra_request = {}
237 if args:
238 extra_request['args'] = args
239 if vars:
240 extra_request['vars'] = vars
241 _env = env(a, c=c, f=f, import_models=import_models, extra_request=extra_request)
242 if c:
243 pyfile = os.path.join('applications', a, 'controllers', c + '.py')
244 pycfile = os.path.join('applications', a, 'compiled',
245 "controllers_%s_%s.pyc" % (c, f))
246 if ((cronjob and os.path.isfile(pycfile))
247 or not os.path.isfile(pyfile)):
248 exec read_pyc(pycfile) in _env
249 elif os.path.isfile(pyfile):
250 execfile(pyfile, _env)
251 else:
252 die(errmsg)
253
254 if f:
255 exec ('print %s()' % f, _env)
256 return
257
258 _env.update(exec_pythonrc())
259 if startfile:
260 try:
261 ccode = None
262 if startfile.endswith('.pyc'):
263 ccode = read_pyc(startfile)
264 exec ccode in _env
265 else:
266 execfile(startfile, _env)
267
268 if import_models:
269 BaseAdapter.close_all_instances('commit')
270 except Exception, e:
271 print traceback.format_exc()
272 if import_models:
273 BaseAdapter.close_all_instances('rollback')
274 elif python_code:
275 try:
276 exec(python_code, _env)
277 if import_models:
278 BaseAdapter.close_all_instances('commit')
279 except Exception, e:
280 print traceback.format_exc()
281 if import_models:
282 BaseAdapter.close_all_instances('rollback')
283 else:
284 if not plain:
285 if bpython:
286 try:
287 import bpython
288 bpython.embed(locals_=_env)
289 return
290 except:
291 logger.warning(
292 'import bpython error; trying ipython...')
293 else:
294 try:
295 import IPython
296 if IPython.__version__ > '1.0.0':
297 IPython.start_ipython(user_ns=_env)
298 return
299 elif IPython.__version__ == '1.0.0':
300 from IPython.terminal.embed import InteractiveShellEmbed
301 shell = InteractiveShellEmbed(user_ns=_env)
302 shell()
303 return
304 elif IPython.__version__ >= '0.11':
305 from IPython.frontend.terminal.embed import InteractiveShellEmbed
306 shell = InteractiveShellEmbed(user_ns=_env)
307 shell()
308 return
309 else:
310
311
312 if '__builtins__' in _env:
313 del _env['__builtins__']
314 shell = IPython.Shell.IPShell(argv=[], user_ns=_env)
315 shell.mainloop()
316 return
317 except:
318 logger.warning(
319 'import IPython error; use default python shell')
320 enable_autocomplete_and_history(adir,_env)
321 code.interact(local=_env)
322
323
325 """
326 Parse path info formatted like a/c/f where c and f are optional
327 and a leading / accepted.
328 Return tuple (a, c, f). If invalid path_info a is set to None.
329 If c or f are omitted they are set to None.
330 If av=True, parse args and vars
331 """
332 if av:
333 vars = None
334 if '?' in path_info:
335 path_info, query = path_info.split('?', 2)
336 vars = Storage()
337 for var in query.split('&'):
338 (var, val) = var.split('=', 2) if '=' in var else (var, None)
339 vars[var] = val
340 items = List(path_info.split('/'))
341 args = List(items[3:]) if len(items) > 3 else None
342 return (items(0), items(1), items(2), args, vars)
343
344 mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$',
345 path_info)
346 if mo:
347 return (mo.group('a'), mo.group('c'), mo.group('f'))
348 else:
349 return (None, None, None)
350
351
353 print >> sys.stderr, msg
354 sys.exit(1)
355
356
357 -def test(testpath, import_models=True, verbose=False):
358 """
359 Run doctests in web2py environment. testpath is formatted like:
360
361 a tests all controllers in application a
362 a/c tests controller c in application a
363 a/c/f test function f in controller c, application a
364
365 Where a, c and f are application, controller and function names
366 respectively. If the testpath is a file name the file is tested.
367 If a controller is specified models are executed by default.
368 """
369
370 import doctest
371 if os.path.isfile(testpath):
372 mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath)
373 if not mo:
374 die('test file is not in application directory: %s'
375 % testpath)
376 a = mo.group('a')
377 c = f = None
378 files = [testpath]
379 else:
380 (a, c, f) = parse_path_info(testpath)
381 errmsg = 'invalid test path: %s' % testpath
382 if not a:
383 die(errmsg)
384 cdir = os.path.join('applications', a, 'controllers')
385 if not os.path.isdir(cdir):
386 die(errmsg)
387 if c:
388 cfile = os.path.join(cdir, c + '.py')
389 if not os.path.isfile(cfile):
390 die(errmsg)
391 files = [cfile]
392 else:
393 files = glob.glob(os.path.join(cdir, '*.py'))
394 for testfile in files:
395 globs = env(a, import_models)
396 ignores = globs.keys()
397 execfile(testfile, globs)
398
399 def doctest_object(name, obj):
400 """doctest obj and enclosed methods and classes."""
401
402 if type(obj) in (types.FunctionType, types.TypeType,
403 types.ClassType, types.MethodType,
404 types.UnboundMethodType):
405
406
407
408 globs = env(a, c=c, f=f, import_models=import_models)
409 execfile(testfile, globs)
410 doctest.run_docstring_examples(
411 obj, globs=globs,
412 name='%s: %s' % (os.path.basename(testfile),
413 name), verbose=verbose)
414 if type(obj) in (types.TypeType, types.ClassType):
415 for attr_name in dir(obj):
416
417
418
419 o = eval('%s.%s' % (name, attr_name), globs)
420 doctest_object(attr_name, o)
421
422 for (name, obj) in globs.items():
423 if name not in ignores and (f is None or f == name):
424 doctest_object(name, obj)
425
426
428 usage = """
429 %prog [options] pythonfile
430 """
431 return usage
432
433
435 if argv is None:
436 argv = sys.argv
437
438 parser = optparse.OptionParser(usage=get_usage())
439
440 parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME',
441 help='run web2py in interactive shell ' +
442 'or IPython(if installed) with specified appname')
443 msg = 'run web2py in interactive shell or bpython (if installed) with'
444 msg += ' specified appname (if app does not exist it will be created).'
445 msg += '\n Use combined with --shell'
446 parser.add_option(
447 '-B',
448 '--bpython',
449 action='store_true',
450 default=False,
451 dest='bpython',
452 help=msg,
453 )
454 parser.add_option(
455 '-P',
456 '--plain',
457 action='store_true',
458 default=False,
459 dest='plain',
460 help='only use plain python shell, should be used with --shell option',
461 )
462 parser.add_option(
463 '-M',
464 '--import_models',
465 action='store_true',
466 default=False,
467 dest='import_models',
468 help='auto import model files, default is False, ' +
469 ' should be used with --shell option',
470 )
471 parser.add_option(
472 '-R',
473 '--run',
474 dest='run',
475 metavar='PYTHON_FILE',
476 default='',
477 help='run PYTHON_FILE in web2py environment, ' +
478 'should be used with --shell option',
479 )
480
481 (options, args) = parser.parse_args(argv[1:])
482
483 if len(sys.argv) == 1:
484 parser.print_help()
485 sys.exit(0)
486
487 if len(args) > 0:
488 startfile = args[0]
489 else:
490 startfile = ''
491 run(options.shell, options.plain, startfile=startfile,
492 bpython=options.bpython)
493
494
495 if __name__ == '__main__':
496 execute_from_command_line()
497