From 5436dddf64f0103088c4ca212f16efa40c88766a Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 14 Jul 2017 20:34:23 -0700 Subject: [PATCH 1/2] rewrite cli errors consistent order for arguments to load functions refactor find_app_by_string to flow better more cli loader tests --- flask/cli.py | 127 +++++++++++++++++------------- tests/test_apps/cliapp/factory.py | 4 + tests/test_cli.py | 8 +- 3 files changed, 81 insertions(+), 58 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index bea4a29f..98056e27 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -46,6 +46,7 @@ def find_best_app(script_info, module): # Search for the most common names first. for attr_name in ('app', 'application'): app = getattr(module, attr_name, None) + if isinstance(app, Flask): return app @@ -58,9 +59,9 @@ def find_best_app(script_info, module): return matches[0] elif len(matches) > 1: raise NoAppException( - 'Auto-detected multiple Flask applications in module "{module}".' - ' Use "FLASK_APP={module}:name" to specify the correct' - ' one.'.format(module=module.__name__) + 'Detected multiple Flask applications in module "{module}". Use ' + '"FLASK_APP={module}:name" to specify the correct ' + 'one.'.format(module=module.__name__) ) # Search for app factory functions. @@ -69,25 +70,29 @@ def find_best_app(script_info, module): if inspect.isfunction(app_factory): try: - app = call_factory(app_factory, script_info) + app = call_factory(script_info, app_factory) + if isinstance(app, Flask): return app except TypeError: raise NoAppException( - 'Auto-detected "{function}()" in module "{module}", but ' - 'could not call it without specifying arguments.'.format( - function=attr_name, module=module.__name__ + 'Detected factory "{factory}" in module "{module}", but ' + 'could not call it without arguments. Use ' + '"FLASK_APP=\'{module}:{factory}(args)\'" to specify ' + 'arguments.'.format( + factory=attr_name, module=module.__name__ ) ) raise NoAppException( - 'Failed to find application in module "{module}". Are you sure ' - 'it contains a Flask application? Maybe you wrapped it in a WSGI ' - 'middleware.'.format(module=module.__name__) + 'Failed to find Flask application or factory in module "{module}". ' + 'Use "FLASK_APP={module}:name to specify one.'.format( + module=module.__name__ + ) ) -def call_factory(app_factory, script_info, arguments=()): +def call_factory(script_info, app_factory, arguments=()): """Takes an app factory, a ``script_info` object and optionally a tuple of arguments. Checks for the existence of a script_info argument and calls the app_factory depending on that and the arguments provided. @@ -102,54 +107,65 @@ def call_factory(app_factory, script_info, arguments=()): return app_factory(*arguments) elif not arguments and len(arg_names) == 1 and arg_defaults is None: return app_factory(script_info) + return app_factory() -def find_app_by_string(string, script_info, module): - """Checks if the given string is a variable name or a function. If it is - a function, it checks for specified arguments and whether it takes - a ``script_info`` argument and calls the function with the appropriate - arguments.""" - from . import Flask - function_regex = r'^(?P\w+)(?:\((?P.*)\))?$' - match = re.match(function_regex, string) - if match: - name, args = match.groups() +def find_app_by_string(script_info, module, app_name): + """Checks if the given string is a variable name or a function. If it is a + function, it checks for specified arguments and whether it takes a + ``script_info`` argument and calls the function with the appropriate + arguments. + """ + from flask import Flask + match = re.match(r'^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$', app_name) + + if not match: + raise NoAppException( + '"{name}" is not a valid variable name or function ' + 'expression.'.format(name=app_name) + ) + + name, args = match.groups() + + try: + attr = getattr(module, name) + except AttributeError as e: + raise NoAppException(e.args[0]) + + if inspect.isfunction(attr): + if args: + try: + args = ast.literal_eval('({args},)'.format(args=args)) + except (ValueError, SyntaxError)as e: + raise NoAppException( + 'Could not parse the arguments in ' + '"{app_name}".'.format(e=e, app_name=app_name) + ) + else: + args = () + try: - if args is not None: - args = args.rstrip(' ,') - if args: - args = ast.literal_eval( - "({args}, )".format(args=args)) - else: - args = () - app_factory = getattr(module, name, None) - app = call_factory(app_factory, script_info, args) - else: - attr = getattr(module, name, None) - if inspect.isfunction(attr): - app = call_factory(attr, script_info) - else: - app = attr - - if isinstance(app, Flask): - return app - else: - raise NoAppException('Failed to find application in module ' - '"{name}"'.format(name=module)) + app = call_factory(script_info, attr, args) except TypeError as e: - new_error = NoAppException( - '{e}\nThe app factory "{factory}" in module "{module}" could' - ' not be called with the specified arguments (and a' - ' script_info argument automatically added if applicable).' - ' Did you make sure to use the right number of arguments as' - ' well as not using keyword arguments or' - ' non-literals?'.format(e=e, factory=string, module=module)) - reraise(NoAppException, new_error, sys.exc_info()[2]) + raise NoAppException( + '{e}\nThe factory "{app_name}" in module "{module}" could not ' + 'be called with the specified arguments.'.format( + e=e, app_name=app_name, module=module.__name__ + ) + ) else: - raise NoAppException( - 'The provided string "{string}" is not a valid variable name' - 'or function expression.'.format(string=string)) + app = attr + + if isinstance(app, Flask): + return app + + raise NoAppException( + 'A valid Flask application was not obtained from ' + '"{module}:{app_name}".'.format( + module=module.__name__, app_name=app_name + ) + ) def prepare_import(path): @@ -181,7 +197,6 @@ def prepare_import(path): def locate_app(script_info, module_name, app_name, raise_if_not_found=True): - """Attempts to locate the application.""" __traceback_hide__ = True try: @@ -206,7 +221,7 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True): if app_name is None: return find_best_app(script_info, module) else: - return find_app_by_string(app_name, script_info, module) + return find_app_by_string(script_info, module, app_name) def get_version(ctx, param, value): @@ -312,7 +327,7 @@ class ScriptInfo(object): app = None if self.create_app is not None: - app = call_factory(self.create_app, self) + app = call_factory(self, self.create_app) else: if self.app_import_path: path, name = (self.app_import_path.split(':', 1) + [None])[:2] diff --git a/tests/test_apps/cliapp/factory.py b/tests/test_apps/cliapp/factory.py index 2e8598e8..95d60396 100644 --- a/tests/test_apps/cliapp/factory.py +++ b/tests/test_apps/cliapp/factory.py @@ -13,3 +13,7 @@ def create_app2(foo, bar): def create_app3(foo, script_info): return Flask('_'.join(['app3', foo, script_info.data['test']])) + + +def no_app(): + pass diff --git a/tests/test_cli.py b/tests/test_cli.py index c66bd17e..811ef0c8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -200,6 +200,8 @@ def test_prepare_import(request, value, path, result): ('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'), # takes script_info ('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'), + # strip whitespace + ('cliapp.factory', ' create_app () ', 'app'), )) def test_locate_app(test_apps, iname, aname, result): info = ScriptInfo() @@ -213,12 +215,14 @@ def test_locate_app(test_apps, iname, aname, result): ('cliapp.app', 'notanapp'), # not enough arguments ('cliapp.factory', 'create_app2("foo")'), + # invalid identifier + ('cliapp.factory', 'create_app('), + # no app returned + ('cliapp.factory', 'no_app'), # nested import error ('cliapp.importerrorapp', None), # not a Python file ('cliapp.message.txt', None), - # space before arg list - ('cliapp.factory', 'create_app ()'), )) def test_locate_app_raises(test_apps, iname, aname): info = ScriptInfo() From 9bc329c0c9dcdb5a3a6919f31542b6e7453d8aa8 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 10 Oct 2017 11:10:20 -0700 Subject: [PATCH 2/2] rewrite cli docs reflects the current FLASK_APP detection --- docs/cli.rst | 496 +++++++++++++++++++++++------------------- docs/installation.rst | 3 + flask/cli.py | 42 ++-- 3 files changed, 297 insertions(+), 244 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index b481991d..cdb0fc59 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -1,145 +1,148 @@ +.. currentmodule:: flask + .. _cli: Command Line Interface ====================== -.. versionadded:: 0.11 +Installing Flask installs the ``flask`` script, a `Click`_ command line +interface, in your virtualenv. Executed from the terminal, this script gives +access to built-in, extension, and application-defined commands. The ``--help`` +option will give more information about any commands and options. -.. currentmodule:: flask +.. _Click: http://click.pocoo.org/ -One of the nice new features in Flask 0.11 is the built-in integration of -the `click `_ command line interface. This -enables a wide range of new features for the Flask ecosystem and your own -applications. -Basic Usage ------------ +Application Discovery +--------------------- -After installation of Flask you will now find a :command:`flask` script -installed into your virtualenv. If you don't want to install Flask or you -have a special use-case you can also use ``python -m flask`` to accomplish -exactly the same. +The ``flask`` command is installed by Flask, not your application; it must be +told where to find your application in order to use it. The ``FLASK_APP`` +environment variable is used to specify how to load the application. -The way this script works is by providing access to all the commands on -your Flask application's :attr:`Flask.cli` instance as well as some -built-in commands that are always there. Flask extensions can also -register more commands there if they desire so. +Unix Bash (Linux, Mac, etc.):: -For the :command:`flask` script to work, an application needs to be discovered. -Flask looks for a module named :file:`wsgi.py` or :file:`app.py` by default, -and if it finds one it assumes the application is defined in it. + $ export FLASK_APP=hello + $ flask run -You can instruct Flask to look for the application in a different module by -exporting the ``FLASK_APP`` environment variable. It can be either set to an -import path or to a filename of a Python module that contains a Flask -application. +Windows CMD:: -In that imported file the name of the app needs to be called ``app`` or -optionally be specified after a colon. For instance -``mymodule:application`` would tell it to use the `application` object in -the :file:`mymodule.py` file. + > set FLASK_APP=hello + > flask run -Given a :file:`hello.py` file with the application in it named ``app`` -this is how it can be run. +Windows PowerShell:: -Environment variables (On Windows use ``set`` instead of ``export``):: + > $env:FLASK_APP = "hello" + > flask run - export FLASK_APP=hello - flask run +While ``FLASK_APP`` supports a variety of options for specifying your +application, most use cases should be simple. Here are the typical values: -Or with a filename:: +(nothing) + The file :file:`wsgi.py` is imported, automatically detecting an app + (``app``). This provides an easy way to create an app from a factory with + extra arguments. - export FLASK_APP=/path/to/hello.py - flask run +``FLASK_APP=hello`` + The name is imported, automatically detecting an app (``app``) or factory + (``create_app``). -Virtualenv Integration ----------------------- +---- -If you are constantly working with a virtualenv you can also put the -``export FLASK_APP`` into your ``activate`` script by adding it to the -bottom of the file. That way every time you activate your virtualenv you -automatically also activate the correct application name. +``FLASK_APP`` has three parts: an optional path that sets the current working +directory, a Python file or dotted import path, and an optional variable +name of the instance or factory. If the name is a factory, it can optionally +be followed by arguments in parentheses. The following values demonstrate these +parts: -Edit the activate script for the shell you use. For example: +``FLASK_APP=src/hello`` + Sets the current working directory to ``src`` then imports ``hello``. -Unix Bash: ``venv/bin/activate``:: +``FLASK_APP=hello.web`` + Imports the path ``hello.web``. - FLASK_APP=hello - export FLASK_APP +``FLASK_APP=hello:app2`` + Uses the ``app2`` Flask instance in ``hello``. -Windows CMD.exe: ``venv\Scripts\activate.bat``:: +``FLASK_APP="hello:create_app('dev')"`` + The ``create_app`` factory in ``hello`` is called with the string ``'dev'`` + as the argument. - set "FLASK_APP=hello" - :END +If ``FLASK_APP`` is not set, the command will look for a file called +:file:`wsgi.py` or :file:`app.py` and try to detect an application instance or +factory. -Debug Flag ----------- +Within the given import, the command looks for an application instance named +``app`` or ``application``, then any application instance. If no instance is +found, the command looks for a factory function named ``create_app`` or +``make_app`` that returns an instance. -The :command:`flask` script can also be instructed to enable the debug -mode of the application automatically by exporting ``FLASK_DEBUG``. If -set to ``1`` debug is enabled or ``0`` disables it:: +When calling an application factory, if the factory takes an argument named +``info``, then the :class:`~cli.ScriptInfo` instance is passed as a keyword +argument. If parentheses follow the factory name, their contents are parsed +as Python literals and passes as arguments to the function. This means that +strings must still be in quotes. - export FLASK_DEBUG=1 -Running a Shell ---------------- +Run the Development Server +-------------------------- -To run an interactive Python shell you can use the ``shell`` command:: +The :func:`run ` command will start the development server. It +replaces the :meth:`Flask.run` method in most cases. :: - flask shell + $ flask run + * Serving Flask app "hello" + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) -This will start up an interactive Python shell, setup the correct -application context and setup the local variables in the shell. This is -done by invoking the :meth:`Flask.make_shell_context` method of the -application. By default you have access to your ``app`` and :data:`g`. +.. warning:: Do not use this command to run your application in production. + Only use the development server during development. The development server + is provided for convenience, but is not designed to be particularly secure, + stable, or efficient. See :ref:`deployment` for how to run in production. -Custom Commands ---------------- - -If you want to add more commands to the shell script you can do this -easily. For instance if you want a shell command to initialize the database you -can do this:: - - import click - from flask import Flask - app = Flask(__name__) +Open a Shell +------------ - @app.cli.command() - def initdb(): - """Initialize the database.""" - click.echo('Init the db') +To explore the data in your application, you can start an interactive Python +shell with the :func:`shell ` command. An application +context will be active, and the app instance will be imported. :: -The command will then show up on the command line:: + $ flask shell + Python 3.6.2 (default, Jul 20 2017, 03:52:27) + [GCC 7.1.1 20170630] on linux + App: example + Instance: /home/user/Projects/hello/instance + >>> - $ flask initdb - Init the db +Use :meth:`~Flask.shell_context_processor` to add other automatic imports. -Application Context -------------------- -Most commands operate on the application so it makes a lot of sense if -they have the application context setup. Because of this, if you register -a callback on ``app.cli`` with the :meth:`~flask.cli.AppGroup.command` the -callback will automatically be wrapped through :func:`cli.with_appcontext` -which informs the cli system to ensure that an application context is set -up. This behavior is not available if a command is added later with -:func:`~click.Group.add_command` or through other means. +Debug Mode +---------- -It can also be disabled by passing ``with_appcontext=False`` to the -decorator:: +Set the :envvar:`FLASK_DEBUG` environment variable to override the +application's :attr:`~Flask.debug` flag. The value ``1`` enables it, ``0`` +disables it. Forcing the debug flag on also enables the debugger and reloader +when running the development server. :: - @app.cli.command(with_appcontext=False) - def example(): - pass + $ FLASK_DEBUG=1 flask run + * Serving Flask app "hello" + * Forcing debug mode on + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + * Restarting with inotify reloader + * Debugger is active! + * Debugger PIN: 223-456-919 .. _dotenv: -Loading Environment Variables From ``.env`` Files -------------------------------------------------- +Environment Variables From dotenv +--------------------------------- + +Rather than setting ``FLASK_APP`` each time you open a new terminal, you can +use Flask's dotenv support to set environment variables automatically. -If `python-dotenv`_ is installed, running the :command:`flask` command will set +If `python-dotenv`_ is installed, running the ``flask`` command will set environment variables defined in the files :file:`.env` and :file:`.flaskenv`. This can be used to avoid having to set ``FLASK_APP`` manually every time you open a new terminal, and to set configuration using environment variables @@ -150,186 +153,231 @@ which are used over those set in :file:`.flaskenv`. :file:`.flaskenv` should be used for public variables, such as ``FLASK_APP``, while :file:`.env` should not be committed to your repository so that it can set private variables. -Directories are scanned upwards from the directory you call :command:`flask` +Directories are scanned upwards from the directory you call ``flask`` from to locate the files. The current working directory will be set to the location of the file, with the assumption that that is the top level project directory. -The files are only loaded by the :command:`flask` command or calling -:meth:`~flask.Flask.run`. If you would like to load these files when running in -production, you should call :func:`~flask.cli.load_dotenv` manually. +The files are only loaded by the ``flask`` command or calling +:meth:`~Flask.run`. If you would like to load these files when running in +production, you should call :func:`~cli.load_dotenv` manually. .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme -Factory Functions ------------------ +Environment Variables From virtualenv +------------------------------------- + +If you do not want to install dotenv support, you can still set environment +variables by adding them to the end of the virtualenv's :file:`activate` +script. Activating the virtualenv will set the variables. + +Unix Bash, :file:`venv/bin/activate`:: + + export FLASK_APP=hello + +Windows CMD, :file:`venv\Scripts\activate.bat`:: + + set FLASK_APP=hello + +It is preferred to use dotenv support over this, since :file:`.flaskenv` can be +committed to the repository so that it works automatically wherever the project +is checked out. + + +Custom Commands +--------------- + +The ``flask`` command is implemented using `Click`_. See that project's +documentation for full information about writing commands. + +This example adds the command ``create_user`` that takes the argument +``name``. :: + + import click + from flask import Flask -In case you are using factory functions to create your application (see -:ref:`app-factories`) you will discover that the :command:`flask` command -cannot work with them directly. Flask won't be able to figure out how to -instantiate your application properly by itself. Because of this reason -the recommendation is to create a separate file that instantiates -applications. This is not the only way to make this work. Another is the -:ref:`custom-scripts` support. + app = Flask(__name__) -For instance if you have a factory function that creates an application -from a filename you could make a separate file that creates such an -application from an environment variable. + @app.cli.command() + @click.argument('name') + def create_user(name): + ... -This could be a file named :file:`autoapp.py` with these contents:: +:: - import os - from yourapplication import create_app - app = create_app(os.environ['YOURAPPLICATION_CONFIG']) + flask create_user admin -Once this has happened you can make the :command:`flask` command automatically -pick it up:: +This example adds the same command, but as ``user create``, a command in a +group. This is useful if you want to organize multiple related commands. :: - export YOURAPPLICATION_CONFIG=/path/to/config.cfg - export FLASK_APP=/path/to/autoapp.py + import click + from flask import Flask + from flask.cli import AppGroup + + app = Flask(__name__) + user_cli = AppGroup('user') + + @user_cli.command('create') + @click.argument('name') + def create_user(name): + ... + + app.cli.add_command(user_cli) + +:: + + flask user create demo + + +Application Context +~~~~~~~~~~~~~~~~~~~ + +Commands added using the Flask app's :attr:`~Flask.cli` +:meth:`~cli.AppGroup.command` decorator will be executed with an application +context pushed, so your command and extensions have access to the app and its +configuration. If you create a command using the Click :func:`~click.command` +decorator instead of the Flask decorator, you can use +:func:`~cli.with_appcontext` to get the same behavior. :: + + import click + from flask.cli import with_appcontext + + @click.command + @with_appcontext + def do_work(): + ... + + app.cli.add_command(do_work) + +If you're sure a command doesn't need the context, you can disable it:: + + @app.cli.command(with_appcontext=False) + def do_work(): + ... + + +Plugins +------- + +Flask will automatically load commands specified in the ``flask.commands`` +`entry point`_. This is useful for extensions that want to add commands when +they are installed. Entry points are specified in :file:`setup.py` :: + + from setuptools import setup + + setup( + name='flask-my-extension', + ..., + entry_points={ + 'flask.commands': [ + 'my-command=flask_my_extension.commands:cli' + ], + }, + ) + + +.. _entry point: https://packaging.python.org/tutorials/distributing-packages/#entry-points + +Inside :file:`flask_my_extension/commands.py` you can then export a Click +object:: + + import click + + @click.command() + def cli(): + ... + +Once that package is installed in the same virtualenv as your Flask project, +you can run ``flask my-command`` to invoke the command. -From this point onwards :command:`flask` will find your application. .. _custom-scripts: Custom Scripts -------------- -While the most common way is to use the :command:`flask` command, you can -also make your own "driver scripts". Since Flask uses click for the -scripts there is no reason you cannot hook these scripts into any click -application. There is one big caveat and that is, that commands -registered to :attr:`Flask.cli` will expect to be (indirectly at least) -launched from a :class:`flask.cli.FlaskGroup` click group. This is -necessary so that the commands know which Flask application they have to -work with. - -To understand why you might want custom scripts you need to understand how -click finds and executes the Flask application. If you use the -:command:`flask` script you specify the application to work with on the -command line or environment variable as an import name. This is simple -but it has some limitations. Primarily it does not work with application -factory functions (see :ref:`app-factories`). - -With a custom script you don't have this problem as you can fully -customize how the application will be created. This is very useful if you -write reusable applications that you want to ship to users and they should -be presented with a custom management script. - -To explain all of this, here is an example :file:`manage.py` script that -manages a hypothetical wiki application. We will go through the details -afterwards:: - - import os +When you are using the app factory pattern, it may be more convenient to define +your own Click script. Instead of using ``FLASK_APP`` and letting Flask load +your application, you can create your own Click object and export it as a +`console script`_ entry point. + +Create an instance of :class:`~cli.FlaskGroup` and pass it the factory:: + import click + from flask import Flask from flask.cli import FlaskGroup - def create_wiki_app(info): - from yourwiki import create_app - return create_app( - config=os.environ.get('WIKI_CONFIG', 'wikiconfig.py')) + def create_app(): + app = Flask('wiki') + # other setup + return app - @click.group(cls=FlaskGroup, create_app=create_wiki_app) + @click.group(cls=FlaskGroup, create_app=create_app) def cli(): - """This is a management script for the wiki application.""" - - if __name__ == '__main__': - cli() - -That's a lot of code for not much, so let's go through all parts step by -step. - -1. First we import the ``click`` library as well as the click extensions - from the ``flask.cli`` package. Primarily we are here interested - in the :class:`~flask.cli.FlaskGroup` click group. -2. The next thing we do is defining a function that is invoked with the - script info object (:class:`~flask.cli.ScriptInfo`) from Flask and its - purpose is to fully import and create the application. This can - either directly import an application object or create it (see - :ref:`app-factories`). In this case we load the config from an - environment variable. -3. Next step is to create a :class:`FlaskGroup`. In this case we just - make an empty function with a help doc string that just does nothing - and then pass the ``create_wiki_app`` function as a factory function. - - Whenever click now needs to operate on a Flask application it will - call that function with the script info and ask for it to be created. -4. All is rounded up by invoking the script. - -CLI Plugins ------------ - -Flask extensions can always patch the :attr:`Flask.cli` instance with more -commands if they want. However there is a second way to add CLI plugins -to Flask which is through ``setuptools``. If you make a Python package that -should export a Flask command line plugin you can ship a :file:`setup.py` file -that declares an entrypoint that points to a click command: - -Example :file:`setup.py`:: + """Management script for the Wiki application.""" + +Define the entry point in :file:`setup.py`:: from setuptools import setup setup( name='flask-my-extension', - ... - entry_points=''' - [flask.commands] - my-command=mypackage.commands:cli - ''', + ..., + entry_points={ + 'console_scripts': [ + 'wiki=wiki:cli' + ], + }, ) -Inside :file:`mypackage/commands.py` you can then export a Click object:: +Install the application in the virtualenv in editable mode and the custom +script is available. Note that you don't need to set ``FLASK_APP``. :: - import click + $ pip install -e . + $ wiki run - @click.command() - def cli(): - """This is an example command.""" +.. _console script: https://packaging.python.org/tutorials/distributing-packages/#console-scripts -Once that package is installed in the same virtualenv as Flask itself you -can run ``flask my-command`` to invoke your command. This is useful to -provide extra functionality that Flask itself cannot ship. PyCharm Integration ------------------- -The new Flask CLI features aren’t yet fully integrated into the PyCharm IDE, -so we have to do a few tweaks to get them working smoothly. +The new Flask CLI features aren't yet fully integrated into the PyCharm IDE, +so we have to do a few tweaks to get them working smoothly. These instructions +should be similar for any other IDE you might want to use. -In your PyCharm application, with your project open, click on *Run* -from the menu bar and go to *Edit Configurations*. You’ll be greeted by a -screen similar to this: +In PyCharm, with your project open, click on *Run* from the menu bar and go to +*Edit Configurations*. You'll be greeted by a screen similar to this: .. image:: _static/pycharm-runconfig.png :align: center :class: screenshot :alt: screenshot of pycharm's run configuration settings -There’s quite a few options to change, but don’t worry— once we’ve done it -for one command, we can easily copy the entire configuration and make a -single tweak to give us access to other flask cli commands, including -any custom ones you may implement yourself. +There's quite a few options to change, but once we've done it for one command, +we can easily copy the entire configuration and make a single tweak to give us +access to other commands, including any custom ones you may implement yourself. -For the *Script* input (**A**), we want to navigate to the virtual environment -we’re using for our project and within that folder we want to pick the ``flask`` -file which will reside in the ``bin`` folder, or in the ``Scripts`` folder if -you're on Windows. +For the *Script* input (**A**), navigate to your project's virtual environment. +Within that folder, pick the ``flask`` executable which will reside in the +``bin`` folder, or in the ``Scripts`` on Windows. -The *Script Parameter* field (**B**) is set to the cli command you wish to -execute, in this example we use ``run`` which will run our development server. +The *Script Parameter* field (**B**) is set to the CLI command you to execute. +In this example we use ``run``, which will run the development server. -We need to add an environment variable (**C**) to identify our application. -Click on the browse button and add an entry with ``FLASK_APP`` on the -left and the name of the python file, or package on the right -(``app.py`` for example). +You can skip this next step if you're using :ref:`dotenv`. We need to add an +environment variable (**C**) to identify our application. Click on the browse +button and add an entry with ``FLASK_APP`` on the left and the name of the +Python file or package on the right (``app.py`` for example). Next we need to set the working directory (**D**) to be the same folder where -our application file or package resides. +our application file or package resides. PyCharm changed it to the directory +with the ``flask`` executable when we selected it earlier, which is incorrect. Finally, untick the *PYTHONPATH* options (**E**) and give the configuration a -good descriptive name, such as “Run Flask Server” and click *Apply*. +good descriptive name, such as "Run Flask Server", and click *Apply*. -Now that we have on run configuration which implements ``flask run`` from within -PyCharm, we can simply copy that configuration and alter the script argument -to run a different cli command, e.g. ``flask shell``. +Now that we have a configuration which runs ``flask run`` from within PyCharm, +we can simply copy that configuration and alter the *Script* argument +to run a different CLI command, e.g. ``flask shell``. diff --git a/docs/installation.rst b/docs/installation.rst index 0ae05f06..370ce48c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -43,10 +43,13 @@ use them if you install them. installed. * `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` commands. +* `Watchdog`_ provides a faster, more efficient reloader for the development + server. .. _Blinker: https://pythonhosted.org/blinker/ .. _SimpleJSON: https://simplejson.readthedocs.io/ .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme +.. _watchdog: https://pythonhosted.org/watchdog/ Virtual environments -------------------- diff --git a/flask/cli.py b/flask/cli.py index 98056e27..d4647401 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -234,11 +234,16 @@ def get_version(ctx, param, value): }, color=ctx.color) ctx.exit() -version_option = click.Option(['--version'], - help='Show the flask version', - expose_value=False, - callback=get_version, - is_flag=True, is_eager=True) + +version_option = click.Option( + ['--version'], + help='Show the flask version', + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True +) + class DispatchingApp(object): """Special application that dispatches to a Flask application which @@ -713,24 +718,21 @@ def routes_command(sort, all_methods): cli = FlaskGroup(help="""\ -This shell command acts as general utility script for Flask applications. - -It loads the application configured (through the FLASK_APP environment -variable) and then provides commands either provided by the application or -Flask itself. - -The most useful commands are the "run" and "shell" command. +A general utility script for Flask applications. -Example usage: +Provides commands from Flask, extensions, and the application. Loads the +application defined in the FLASK_APP environment variable, or from a wsgi.py +file. Debug mode can be controlled with the FLASK_DEBUG +environment variable. \b - %(prefix)s%(cmd)s FLASK_APP=hello.py - %(prefix)s%(cmd)s FLASK_DEBUG=1 - %(prefix)sflask run -""" % { - 'cmd': os.name == 'posix' and 'export' or 'set', - 'prefix': os.name == 'posix' and '$ ' or '', -}) + {prefix}{cmd} FLASK_APP=hello.py + {prefix}{cmd} FLASK_DEBUG=1 + {prefix}flask run +""".format( + cmd='export' if os.name == 'posix' else 'set', + prefix='$ ' if os.name == 'posix' else '> ' +)) def main(as_module=False):