Added the new flask run module to start the server.
This commit is contained in:
parent
42c28c3758
commit
7503bde223
2 changed files with 224 additions and 0 deletions
220
flask/run.py
Normal file
220
flask/run.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import os
|
||||
import sys
|
||||
from threading import Lock
|
||||
from optparse import OptionParser
|
||||
|
||||
from werkzeug.serving import run_simple
|
||||
|
||||
from ._compat import iteritems
|
||||
|
||||
|
||||
def find_best_app(module):
|
||||
"""Given a module instance this tries to find the best possible application
|
||||
in the module or raises a RuntimeError.
|
||||
"""
|
||||
from flask import Flask
|
||||
|
||||
# The app name wins, even if it's not a flask object.
|
||||
app = getattr(module, 'app', None)
|
||||
if app is not None and callable(app):
|
||||
return app
|
||||
|
||||
# Otherwise find the first object named Flask
|
||||
matches = []
|
||||
for key, value in iteritems(module.__dict__):
|
||||
if isinstance(value, Flask):
|
||||
matches.append(value)
|
||||
|
||||
if matches:
|
||||
if len(matches) > 1:
|
||||
raise RuntimeError('More than one possible Flask application '
|
||||
'found in module "%s", none of which are called '
|
||||
'"app". Be explicit!' % module)
|
||||
return matches[0]
|
||||
|
||||
raise RuntimeError('Failed to find application in module "%s". Are '
|
||||
'you sure it contains a Flask application? Maybe '
|
||||
'you wrapped it in a WSGI middleware or you are '
|
||||
'using a factory function.' % module)
|
||||
|
||||
|
||||
def prepare_exec_for_file(filename):
|
||||
module = []
|
||||
|
||||
# Chop off file extensions or package markers
|
||||
if filename.endswith('.py'):
|
||||
filename = filename[:-3]
|
||||
elif os.path.split(filename)[1] == '__init__.py':
|
||||
filename = os.path.dirname(filename)
|
||||
filename = os.path.realpath(filename)
|
||||
|
||||
dirpath = filename
|
||||
while 1:
|
||||
dirpath, extra = os.path.split(dirpath)
|
||||
module.append(extra)
|
||||
if not os.path.isfile(os.path.join(dirpath, '__init__.py')):
|
||||
break
|
||||
|
||||
sys.path.insert(0, dirpath)
|
||||
return '.'.join(module[::-1])
|
||||
|
||||
|
||||
def locate_app(app_id, debug=None):
|
||||
"""Attempts to locate the application."""
|
||||
if ':' in app_id:
|
||||
module, app_obj = app_id.split(':', 1)
|
||||
else:
|
||||
module = app_id
|
||||
app_obj = None
|
||||
|
||||
__import__(module)
|
||||
mod = sys.modules[module]
|
||||
if app_obj is None:
|
||||
app = find_best_app(mod)
|
||||
else:
|
||||
app = getattr(mod, app_obj, None)
|
||||
if app is None:
|
||||
raise RuntimeError('Failed to find application in module "%s"'
|
||||
% module)
|
||||
if debug is not None:
|
||||
app.debug = debug
|
||||
return app
|
||||
|
||||
|
||||
class DispatchingApp(object):
|
||||
"""Special applicationt that dispatches to a flask application which
|
||||
is imported by name on first request. This is safer than importing
|
||||
the application upfront because it means that we can forward all
|
||||
errors for import problems into the browser as error.
|
||||
"""
|
||||
|
||||
def __init__(self, app_id, debug=None, use_eager_loading=False):
|
||||
self.app_id = app_id
|
||||
self.app = None
|
||||
self.debug = debug
|
||||
self._lock = Lock()
|
||||
if use_eager_loading:
|
||||
self._load_unlocked()
|
||||
|
||||
def _load_unlocked(self):
|
||||
self.app = rv = locate_app(self.app_id, self.debug)
|
||||
return rv
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
if self.app is not None:
|
||||
return self.app(environ, start_response)
|
||||
with self._lock:
|
||||
if self.app is not None:
|
||||
rv = self.app
|
||||
else:
|
||||
rv = self._load_unlocked()
|
||||
return rv(environ, start_response)
|
||||
|
||||
|
||||
def run_application(app_id, host='127.0.0.1', port=5000, debug=None,
|
||||
use_reloader=False, use_debugger=False,
|
||||
use_eager_loading=None, magic_app_id=True):
|
||||
"""Useful function to start a Werkzeug server for an application that
|
||||
is known by it's import name. By default the app ID can also be a
|
||||
full file name in which case Flask attempts to reconstruct the import
|
||||
name from it and do the right thing.
|
||||
|
||||
:param app_id: the import name of the application module. If a colon
|
||||
is provided, everything afterwards is the application
|
||||
object name. In case the magic app id is enabled this
|
||||
can also be a filename.
|
||||
:param host: the host to bind to.
|
||||
:param port: the port to bind to.
|
||||
:param debug: if set to something other than None then the application's
|
||||
debug flag will be set to this.
|
||||
:param use_reloader: enables or disables the reloader.
|
||||
:param use_debugger: enables or disables the builtin debugger.
|
||||
:param use_eager_loading: enables or disables eager loading. This is
|
||||
normally conditional to the reloader.
|
||||
:param magic_app_id: if this is enabled then the app id can also be a
|
||||
filename instead of an import module and Flask
|
||||
will attempt to reconstruct the import name.
|
||||
"""
|
||||
if magic_app_id:
|
||||
if os.path.isfile(app_id) or os.sep in app_id or \
|
||||
os.altsep is not None and os.altsep in app_id:
|
||||
app_id = prepare_exec_for_file(app_id)
|
||||
|
||||
if use_eager_loading is None:
|
||||
use_eager_loading = not use_reloader
|
||||
|
||||
# Extra startup messages. This depends a but on Werkzeug internals to
|
||||
# not double execute when the reloader kicks in.
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||
print ' * Serving Flask app "%s"' % app_id
|
||||
if debug is not None:
|
||||
print ' * Forcing debug %s' % (debug and 'on' or 'off')
|
||||
|
||||
app = DispatchingApp(app_id, debug, use_eager_loading)
|
||||
run_simple(host, port, app, use_reloader=use_reloader,
|
||||
use_debugger=use_debugger)
|
||||
|
||||
|
||||
def main(as_module=False):
|
||||
this_module = __package__ + '.run'
|
||||
|
||||
if as_module:
|
||||
name = 'python -m ' + this_module
|
||||
else:
|
||||
name = 'flask-run'
|
||||
|
||||
parser = OptionParser(usage='%prog [options] module', prog=name)
|
||||
parser.add_option('--debug', action='store_true',
|
||||
dest='debug', help='Flip debug flag on. If enabled '
|
||||
'this also affects debugger and reloader defaults.')
|
||||
parser.add_option('--no-debug', action='store_false',
|
||||
dest='debug', help='Flip debug flag off.')
|
||||
parser.add_option('--host', default='127.0.0.1',
|
||||
help='The host to bind on. (defaults to 127.0.0.1)')
|
||||
parser.add_option('--port', default=5000,
|
||||
help='The port to bind on. (defaults to 5000)')
|
||||
parser.add_option('--with-reloader', action='store_true',
|
||||
dest='with_reloader',
|
||||
help='Enable the reloader.')
|
||||
parser.add_option('--without-reloader', action='store_false',
|
||||
dest='with_reloader',
|
||||
help='Disable the reloader.')
|
||||
parser.add_option('--with-debugger', action='store_true',
|
||||
dest='with_debugger',
|
||||
help='Enable the debugger.')
|
||||
parser.add_option('--without-debugger', action='store_false',
|
||||
dest='with_debugger',
|
||||
help='Disable the debugger.')
|
||||
parser.add_option('--with-eager-loading', action='store_true',
|
||||
dest='with_eager_loading',
|
||||
help='Force enable the eager-loading. This makes the '
|
||||
'application load immediately but makes development '
|
||||
'flows harder. It\'s not recommended to enable eager '
|
||||
'loading when the reloader is enabled as it can lead '
|
||||
'to unexpected crashes.')
|
||||
parser.add_option('--without-eager-loading', action='store_false',
|
||||
dest='with_eager_loading',
|
||||
help='Disable the eager-loading.')
|
||||
opts, args = parser.parse_args()
|
||||
if len(args) != 1:
|
||||
parser.error('Expected exactly one argument which is the import '
|
||||
'name of the application.')
|
||||
|
||||
if opts.with_debugger is None:
|
||||
opts.with_debugger = opts.debug
|
||||
if opts.with_reloader is None:
|
||||
opts.with_reloader = opts.debug
|
||||
|
||||
# This module is always executed as "python -m flask.run" and as such
|
||||
# we need to ensure that we restore the actual command line so that
|
||||
# the reloader can properly operate.
|
||||
sys.argv = ['-m', this_module] + sys.argv[1:]
|
||||
|
||||
run_application(args[0], opts.host, opts.port, debug=opts.debug,
|
||||
use_reloader=opts.with_reloader,
|
||||
use_debugger=opts.with_debugger,
|
||||
use_eager_loading=opts.with_eager_loading)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(as_module=True)
|
||||
4
setup.py
4
setup.py
|
|
@ -106,6 +106,10 @@ setup(
|
|||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
flask-run=flask.run:main
|
||||
''',
|
||||
cmdclass={'audit': run_audit},
|
||||
test_suite='flask.testsuite.suite'
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue