forked from orbit-oss/flask
Merge pull request #2490 from davidism/cli-review
Rewrite CLI errors and docs
This commit is contained in:
commit
0932d8f428
5 changed files with 388 additions and 312 deletions
514
docs/cli.rst
514
docs/cli.rst
|
|
@ -1,145 +1,148 @@
|
||||||
|
.. currentmodule:: flask
|
||||||
|
|
||||||
.. _cli:
|
.. _cli:
|
||||||
|
|
||||||
Command Line Interface
|
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 <http://click.pocoo.org/>`_ 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
|
The ``flask`` command is installed by Flask, not your application; it must be
|
||||||
installed into your virtualenv. If you don't want to install Flask or you
|
told where to find your application in order to use it. The ``FLASK_APP``
|
||||||
have a special use-case you can also use ``python -m flask`` to accomplish
|
environment variable is used to specify how to load the application.
|
||||||
exactly the same.
|
|
||||||
|
|
||||||
The way this script works is by providing access to all the commands on
|
Unix Bash (Linux, Mac, etc.)::
|
||||||
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.
|
|
||||||
|
|
||||||
For the :command:`flask` script to work, an application needs to be discovered.
|
$ export FLASK_APP=hello
|
||||||
Flask looks for a module named :file:`wsgi.py` or :file:`app.py` by default,
|
$ flask run
|
||||||
and if it finds one it assumes the application is defined in it.
|
|
||||||
|
|
||||||
You can instruct Flask to look for the application in a different module by
|
Windows CMD::
|
||||||
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.
|
|
||||||
|
|
||||||
In that imported file the name of the app needs to be called ``app`` or
|
> set FLASK_APP=hello
|
||||||
optionally be specified after a colon. For instance
|
> flask run
|
||||||
``mymodule:application`` would tell it to use the `application` object in
|
|
||||||
the :file:`mymodule.py` file.
|
|
||||||
|
|
||||||
Given a :file:`hello.py` file with the application in it named ``app``
|
Windows PowerShell::
|
||||||
this is how it can be run.
|
|
||||||
|
|
||||||
Environment variables (On Windows use ``set`` instead of ``export``)::
|
> $env:FLASK_APP = "hello"
|
||||||
|
> flask run
|
||||||
|
|
||||||
export FLASK_APP=hello
|
While ``FLASK_APP`` supports a variety of options for specifying your
|
||||||
flask run
|
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_APP=hello``
|
||||||
flask run
|
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
|
``FLASK_APP`` has three parts: an optional path that sets the current working
|
||||||
``export FLASK_APP`` into your ``activate`` script by adding it to the
|
directory, a Python file or dotted import path, and an optional variable
|
||||||
bottom of the file. That way every time you activate your virtualenv you
|
name of the instance or factory. If the name is a factory, it can optionally
|
||||||
automatically also activate the correct application name.
|
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
|
``FLASK_APP=hello:app2``
|
||||||
export FLASK_APP
|
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"
|
If ``FLASK_APP`` is not set, the command will look for a file called
|
||||||
:END
|
: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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Run the Development Server
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The :func:`run <cli.run_command>` command will start the development server. It
|
||||||
|
replaces the :meth:`Flask.run` method in most cases. ::
|
||||||
|
|
||||||
|
$ flask run
|
||||||
|
* Serving Flask app "hello"
|
||||||
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
|
||||||
|
.. 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.
|
||||||
|
|
||||||
|
|
||||||
|
Open a Shell
|
||||||
|
------------
|
||||||
|
|
||||||
|
To explore the data in your application, you can start an interactive Python
|
||||||
|
shell with the :func:`shell <cli.shell_command>` command. An application
|
||||||
|
context will be active, and the app instance will be imported. ::
|
||||||
|
|
||||||
|
$ 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
|
||||||
|
>>>
|
||||||
|
|
||||||
|
Use :meth:`~Flask.shell_context_processor` to add other automatic imports.
|
||||||
|
|
||||||
|
|
||||||
|
Debug Mode
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The :command:`flask` script can also be instructed to enable the debug
|
Set the :envvar:`FLASK_DEBUG` environment variable to override the
|
||||||
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
application's :attr:`~Flask.debug` flag. The value ``1`` enables it, ``0``
|
||||||
set to ``1`` debug is enabled or ``0`` disables it::
|
disables it. Forcing the debug flag on also enables the debugger and reloader
|
||||||
|
when running the development server. ::
|
||||||
|
|
||||||
export FLASK_DEBUG=1
|
$ FLASK_DEBUG=1 flask run
|
||||||
|
* Serving Flask app "hello"
|
||||||
Running a Shell
|
* Forcing debug mode on
|
||||||
---------------
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
* Restarting with inotify reloader
|
||||||
To run an interactive Python shell you can use the ``shell`` command::
|
* Debugger is active!
|
||||||
|
* Debugger PIN: 223-456-919
|
||||||
flask shell
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
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__)
|
|
||||||
|
|
||||||
@app.cli.command()
|
|
||||||
def initdb():
|
|
||||||
"""Initialize the database."""
|
|
||||||
click.echo('Init the db')
|
|
||||||
|
|
||||||
The command will then show up on the command line::
|
|
||||||
|
|
||||||
$ flask initdb
|
|
||||||
Init the db
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
It can also be disabled by passing ``with_appcontext=False`` to the
|
|
||||||
decorator::
|
|
||||||
|
|
||||||
@app.cli.command(with_appcontext=False)
|
|
||||||
def example():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
.. _dotenv:
|
.. _dotenv:
|
||||||
|
|
||||||
Loading Environment Variables From ``.env`` Files
|
Environment Variables From dotenv
|
||||||
-------------------------------------------------
|
---------------------------------
|
||||||
|
|
||||||
If `python-dotenv`_ is installed, running the :command:`flask` command will set
|
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 ``flask`` command will set
|
||||||
environment variables defined in the files :file:`.env` and :file:`.flaskenv`.
|
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
|
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
|
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
|
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.
|
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
|
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
|
location of the file, with the assumption that that is the top level project
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
The files are only loaded by the :command:`flask` command or calling
|
The files are only loaded by the ``flask`` command or calling
|
||||||
:meth:`~flask.Flask.run`. If you would like to load these files when running in
|
:meth:`~Flask.run`. If you would like to load these files when running in
|
||||||
production, you should call :func:`~flask.cli.load_dotenv` manually.
|
production, you should call :func:`~cli.load_dotenv` manually.
|
||||||
|
|
||||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||||
|
|
||||||
|
|
||||||
Factory Functions
|
Environment Variables From virtualenv
|
||||||
-----------------
|
-------------------------------------
|
||||||
|
|
||||||
In case you are using factory functions to create your application (see
|
If you do not want to install dotenv support, you can still set environment
|
||||||
:ref:`app-factories`) you will discover that the :command:`flask` command
|
variables by adding them to the end of the virtualenv's :file:`activate`
|
||||||
cannot work with them directly. Flask won't be able to figure out how to
|
script. Activating the virtualenv will set the variables.
|
||||||
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.
|
|
||||||
|
|
||||||
For instance if you have a factory function that creates an application
|
Unix Bash, :file:`venv/bin/activate`::
|
||||||
from a filename you could make a separate file that creates such an
|
|
||||||
application from an environment variable.
|
|
||||||
|
|
||||||
This could be a file named :file:`autoapp.py` with these contents::
|
export FLASK_APP=hello
|
||||||
|
|
||||||
import os
|
Windows CMD, :file:`venv\Scripts\activate.bat`::
|
||||||
from yourapplication import create_app
|
|
||||||
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
|
||||||
|
|
||||||
Once this has happened you can make the :command:`flask` command automatically
|
set FLASK_APP=hello
|
||||||
pick it up::
|
|
||||||
|
|
||||||
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
It is preferred to use dotenv support over this, since :file:`.flaskenv` can be
|
||||||
export FLASK_APP=/path/to/autoapp.py
|
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
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument('name')
|
||||||
|
def create_user(name):
|
||||||
|
...
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
flask create_user admin
|
||||||
|
|
||||||
|
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. ::
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
Custom Scripts
|
Custom Scripts
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
While the most common way is to use the :command:`flask` command, you can
|
When you are using the app factory pattern, it may be more convenient to define
|
||||||
also make your own "driver scripts". Since Flask uses click for the
|
your own Click script. Instead of using ``FLASK_APP`` and letting Flask load
|
||||||
scripts there is no reason you cannot hook these scripts into any click
|
your application, you can create your own Click object and export it as a
|
||||||
application. There is one big caveat and that is, that commands
|
`console script`_ entry point.
|
||||||
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
|
Create an instance of :class:`~cli.FlaskGroup` and pass it the factory::
|
||||||
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
|
|
||||||
import click
|
import click
|
||||||
|
from flask import Flask
|
||||||
from flask.cli import FlaskGroup
|
from flask.cli import FlaskGroup
|
||||||
|
|
||||||
def create_wiki_app(info):
|
def create_app():
|
||||||
from yourwiki import create_app
|
app = Flask('wiki')
|
||||||
return create_app(
|
# other setup
|
||||||
config=os.environ.get('WIKI_CONFIG', 'wikiconfig.py'))
|
return app
|
||||||
|
|
||||||
@click.group(cls=FlaskGroup, create_app=create_wiki_app)
|
@click.group(cls=FlaskGroup, create_app=create_app)
|
||||||
def cli():
|
def cli():
|
||||||
"""This is a management script for the wiki application."""
|
"""Management script for the Wiki application."""
|
||||||
|
|
||||||
if __name__ == '__main__':
|
Define the entry point in :file:`setup.py`::
|
||||||
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`::
|
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='flask-my-extension',
|
name='flask-my-extension',
|
||||||
...
|
...,
|
||||||
entry_points='''
|
entry_points={
|
||||||
[flask.commands]
|
'console_scripts': [
|
||||||
my-command=mypackage.commands:cli
|
'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()
|
.. _console script: https://packaging.python.org/tutorials/distributing-packages/#console-scripts
|
||||||
def cli():
|
|
||||||
"""This is an example command."""
|
|
||||||
|
|
||||||
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
|
PyCharm Integration
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The new Flask CLI features aren’t yet fully integrated into the PyCharm IDE,
|
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.
|
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*
|
In PyCharm, with your project open, click on *Run* from the menu bar and go to
|
||||||
from the menu bar and go to *Edit Configurations*. You’ll be greeted by a
|
*Edit Configurations*. You'll be greeted by a screen similar to this:
|
||||||
screen similar to this:
|
|
||||||
|
|
||||||
.. image:: _static/pycharm-runconfig.png
|
.. image:: _static/pycharm-runconfig.png
|
||||||
:align: center
|
:align: center
|
||||||
:class: screenshot
|
:class: screenshot
|
||||||
:alt: screenshot of pycharm's run configuration settings
|
: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
|
There's quite a few options to change, but once we've done it for one command,
|
||||||
for one command, we can easily copy the entire configuration and make a
|
we can easily copy the entire configuration and make a single tweak to give us
|
||||||
single tweak to give us access to other flask cli commands, including
|
access to other commands, including any custom ones you may implement yourself.
|
||||||
any custom ones you may implement yourself.
|
|
||||||
|
|
||||||
For the *Script* input (**A**), we want to navigate to the virtual environment
|
For the *Script* input (**A**), navigate to your project's virtual environment.
|
||||||
we’re using for our project and within that folder we want to pick the ``flask``
|
Within that folder, pick the ``flask`` executable which will reside in the
|
||||||
file which will reside in the ``bin`` folder, or in the ``Scripts`` folder if
|
``bin`` folder, or in the ``Scripts`` on Windows.
|
||||||
you're on Windows.
|
|
||||||
|
|
||||||
The *Script Parameter* field (**B**) is set to the cli command you wish to
|
The *Script Parameter* field (**B**) is set to the CLI command you to execute.
|
||||||
execute, in this example we use ``run`` which will run our development server.
|
In this example we use ``run``, which will run the development server.
|
||||||
|
|
||||||
We need to add an environment variable (**C**) to identify our application.
|
You can skip this next step if you're using :ref:`dotenv`. We need to add an
|
||||||
Click on the browse button and add an entry with ``FLASK_APP`` on the
|
environment variable (**C**) to identify our application. Click on the browse
|
||||||
left and the name of the python file, or package on the right
|
button and add an entry with ``FLASK_APP`` on the left and the name of the
|
||||||
(``app.py`` for example).
|
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
|
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
|
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
|
Now that we have a configuration which runs ``flask run`` from within PyCharm,
|
||||||
PyCharm, we can simply copy that configuration and alter the script argument
|
we can simply copy that configuration and alter the *Script* argument
|
||||||
to run a different cli command, e.g. ``flask shell``.
|
to run a different CLI command, e.g. ``flask shell``.
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,13 @@ use them if you install them.
|
||||||
installed.
|
installed.
|
||||||
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
|
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
|
||||||
commands.
|
commands.
|
||||||
|
* `Watchdog`_ provides a faster, more efficient reloader for the development
|
||||||
|
server.
|
||||||
|
|
||||||
.. _Blinker: https://pythonhosted.org/blinker/
|
.. _Blinker: https://pythonhosted.org/blinker/
|
||||||
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||||
|
.. _watchdog: https://pythonhosted.org/watchdog/
|
||||||
|
|
||||||
Virtual environments
|
Virtual environments
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
||||||
171
flask/cli.py
171
flask/cli.py
|
|
@ -46,6 +46,7 @@ def find_best_app(script_info, module):
|
||||||
# Search for the most common names first.
|
# Search for the most common names first.
|
||||||
for attr_name in ('app', 'application'):
|
for attr_name in ('app', 'application'):
|
||||||
app = getattr(module, attr_name, None)
|
app = getattr(module, attr_name, None)
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
@ -58,9 +59,9 @@ def find_best_app(script_info, module):
|
||||||
return matches[0]
|
return matches[0]
|
||||||
elif len(matches) > 1:
|
elif len(matches) > 1:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Auto-detected multiple Flask applications in module "{module}".'
|
'Detected multiple Flask applications in module "{module}". Use '
|
||||||
' Use "FLASK_APP={module}:name" to specify the correct'
|
'"FLASK_APP={module}:name" to specify the correct '
|
||||||
' one.'.format(module=module.__name__)
|
'one.'.format(module=module.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search for app factory functions.
|
# Search for app factory functions.
|
||||||
|
|
@ -69,25 +70,29 @@ def find_best_app(script_info, module):
|
||||||
|
|
||||||
if inspect.isfunction(app_factory):
|
if inspect.isfunction(app_factory):
|
||||||
try:
|
try:
|
||||||
app = call_factory(app_factory, script_info)
|
app = call_factory(script_info, app_factory)
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Auto-detected "{function}()" in module "{module}", but '
|
'Detected factory "{factory}" in module "{module}", but '
|
||||||
'could not call it without specifying arguments.'.format(
|
'could not call it without arguments. Use '
|
||||||
function=attr_name, module=module.__name__
|
'"FLASK_APP=\'{module}:{factory}(args)\'" to specify '
|
||||||
|
'arguments.'.format(
|
||||||
|
factory=attr_name, module=module.__name__
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Failed to find application in module "{module}". Are you sure '
|
'Failed to find Flask application or factory in module "{module}". '
|
||||||
'it contains a Flask application? Maybe you wrapped it in a WSGI '
|
'Use "FLASK_APP={module}:name to specify one.'.format(
|
||||||
'middleware.'.format(module=module.__name__)
|
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
|
"""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
|
of arguments. Checks for the existence of a script_info argument and calls
|
||||||
the app_factory depending on that and the arguments provided.
|
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)
|
return app_factory(*arguments)
|
||||||
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
||||||
return app_factory(script_info)
|
return app_factory(script_info)
|
||||||
|
|
||||||
return app_factory()
|
return app_factory()
|
||||||
|
|
||||||
|
|
||||||
def find_app_by_string(string, script_info, module):
|
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
|
"""Checks if the given string is a variable name or a function. If it is a
|
||||||
a function, it checks for specified arguments and whether it takes
|
function, it checks for specified arguments and whether it takes a
|
||||||
a ``script_info`` argument and calls the function with the appropriate
|
``script_info`` argument and calls the function with the appropriate
|
||||||
arguments."""
|
arguments.
|
||||||
from . import Flask
|
"""
|
||||||
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$'
|
from flask import Flask
|
||||||
match = re.match(function_regex, string)
|
match = re.match(r'^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$', app_name)
|
||||||
if match:
|
|
||||||
name, args = match.groups()
|
|
||||||
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):
|
if not match:
|
||||||
return app
|
|
||||||
else:
|
|
||||||
raise NoAppException('Failed to find application in module '
|
|
||||||
'"{name}"'.format(name=module))
|
|
||||||
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])
|
|
||||||
else:
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'The provided string "{string}" is not a valid variable name'
|
'"{name}" is not a valid variable name or function '
|
||||||
'or function expression.'.format(string=string))
|
'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:
|
||||||
|
app = call_factory(script_info, attr, args)
|
||||||
|
except TypeError as e:
|
||||||
|
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:
|
||||||
|
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):
|
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):
|
def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
"""Attempts to locate the application."""
|
|
||||||
__traceback_hide__ = True
|
__traceback_hide__ = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -206,7 +221,7 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
if app_name is None:
|
if app_name is None:
|
||||||
return find_best_app(script_info, module)
|
return find_best_app(script_info, module)
|
||||||
else:
|
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):
|
def get_version(ctx, param, value):
|
||||||
|
|
@ -219,11 +234,16 @@ def get_version(ctx, param, value):
|
||||||
}, color=ctx.color)
|
}, color=ctx.color)
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
version_option = click.Option(['--version'],
|
|
||||||
help='Show the flask version',
|
version_option = click.Option(
|
||||||
expose_value=False,
|
['--version'],
|
||||||
callback=get_version,
|
help='Show the flask version',
|
||||||
is_flag=True, is_eager=True)
|
expose_value=False,
|
||||||
|
callback=get_version,
|
||||||
|
is_flag=True,
|
||||||
|
is_eager=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DispatchingApp(object):
|
class DispatchingApp(object):
|
||||||
"""Special application that dispatches to a Flask application which
|
"""Special application that dispatches to a Flask application which
|
||||||
|
|
@ -312,7 +332,7 @@ class ScriptInfo(object):
|
||||||
app = None
|
app = None
|
||||||
|
|
||||||
if self.create_app is not None:
|
if self.create_app is not None:
|
||||||
app = call_factory(self.create_app, self)
|
app = call_factory(self, self.create_app)
|
||||||
else:
|
else:
|
||||||
if self.app_import_path:
|
if self.app_import_path:
|
||||||
path, name = (self.app_import_path.split(':', 1) + [None])[:2]
|
path, name = (self.app_import_path.split(':', 1) + [None])[:2]
|
||||||
|
|
@ -698,24 +718,21 @@ def routes_command(sort, all_methods):
|
||||||
|
|
||||||
|
|
||||||
cli = FlaskGroup(help="""\
|
cli = FlaskGroup(help="""\
|
||||||
This shell command acts as general utility script for Flask applications.
|
A general utility script for Flask applications.
|
||||||
|
|
||||||
It loads the application configured (through the FLASK_APP environment
|
Provides commands from Flask, extensions, and the application. Loads the
|
||||||
variable) and then provides commands either provided by the application or
|
application defined in the FLASK_APP environment variable, or from a wsgi.py
|
||||||
Flask itself.
|
file. Debug mode can be controlled with the FLASK_DEBUG
|
||||||
|
environment variable.
|
||||||
The most useful commands are the "run" and "shell" command.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
\b
|
\b
|
||||||
%(prefix)s%(cmd)s FLASK_APP=hello.py
|
{prefix}{cmd} FLASK_APP=hello.py
|
||||||
%(prefix)s%(cmd)s FLASK_DEBUG=1
|
{prefix}{cmd} FLASK_DEBUG=1
|
||||||
%(prefix)sflask run
|
{prefix}flask run
|
||||||
""" % {
|
""".format(
|
||||||
'cmd': os.name == 'posix' and 'export' or 'set',
|
cmd='export' if os.name == 'posix' else 'set',
|
||||||
'prefix': os.name == 'posix' and '$ ' or '',
|
prefix='$ ' if os.name == 'posix' else '> '
|
||||||
})
|
))
|
||||||
|
|
||||||
|
|
||||||
def main(as_module=False):
|
def main(as_module=False):
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,7 @@ def create_app2(foo, bar):
|
||||||
|
|
||||||
def create_app3(foo, script_info):
|
def create_app3(foo, script_info):
|
||||||
return Flask('_'.join(['app3', foo, script_info.data['test']]))
|
return Flask('_'.join(['app3', foo, script_info.data['test']]))
|
||||||
|
|
||||||
|
|
||||||
|
def no_app():
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,8 @@ def test_prepare_import(request, value, path, result):
|
||||||
('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'),
|
('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'),
|
||||||
# takes script_info
|
# takes script_info
|
||||||
('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'),
|
('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'),
|
||||||
|
# strip whitespace
|
||||||
|
('cliapp.factory', ' create_app () ', 'app'),
|
||||||
))
|
))
|
||||||
def test_locate_app(test_apps, iname, aname, result):
|
def test_locate_app(test_apps, iname, aname, result):
|
||||||
info = ScriptInfo()
|
info = ScriptInfo()
|
||||||
|
|
@ -213,12 +215,14 @@ def test_locate_app(test_apps, iname, aname, result):
|
||||||
('cliapp.app', 'notanapp'),
|
('cliapp.app', 'notanapp'),
|
||||||
# not enough arguments
|
# not enough arguments
|
||||||
('cliapp.factory', 'create_app2("foo")'),
|
('cliapp.factory', 'create_app2("foo")'),
|
||||||
|
# invalid identifier
|
||||||
|
('cliapp.factory', 'create_app('),
|
||||||
|
# no app returned
|
||||||
|
('cliapp.factory', 'no_app'),
|
||||||
# nested import error
|
# nested import error
|
||||||
('cliapp.importerrorapp', None),
|
('cliapp.importerrorapp', None),
|
||||||
# not a Python file
|
# not a Python file
|
||||||
('cliapp.message.txt', None),
|
('cliapp.message.txt', None),
|
||||||
# space before arg list
|
|
||||||
('cliapp.factory', 'create_app ()'),
|
|
||||||
))
|
))
|
||||||
def test_locate_app_raises(test_apps, iname, aname):
|
def test_locate_app_raises(test_apps, iname, aname):
|
||||||
info = ScriptInfo()
|
info = ScriptInfo()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue