├── examples
├── prod.cfg
└── manage.py
├── requirements.txt
├── docs
├── _static
│ ├── flask-script.png
│ └── index.html
├── _themes
│ ├── flask
│ │ ├── theme.conf
│ │ └── static
│ │ │ └── flasky.css_t
│ ├── flask_small
│ │ ├── theme.conf
│ │ ├── layout.html
│ │ └── static
│ │ │ └── flasky.css_t
│ ├── README
│ └── flask_theme_support.py
├── make.bat
├── Makefile
├── conf.py
└── index.rst
├── MANIFEST.in
├── .gitignore
├── .travis.yml
├── tox.ini
├── README.rst
├── LICENSE
├── setup.py
├── CHANGES
├── flask_script
├── cli.py
├── _compat.py
├── __init__.py
└── commands.py
└── tests.py
/examples/prod.cfg:
--------------------------------------------------------------------------------
1 | DEBUG=False
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flask>=0.10
2 |
--------------------------------------------------------------------------------
/docs/_static/flask-script.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smurfix/flask-script/HEAD/docs/_static/flask-script.png
--------------------------------------------------------------------------------
/docs/_themes/flask/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = basic
3 | stylesheet = flasky.css
4 | pygments_style = flask_theme_support.FlaskyStyle
5 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE tests.py
2 | recursive-include docs *
3 | recursive-exclude docs *.pyc
4 | recursive-exclude docs *.pyo
5 | prune docs/_build
6 | prune docs/_themes/.git
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | _build/
3 | *.cfg
4 | *.coverage
5 | *.db
6 | dist/
7 | *.egg-info
8 | *.log
9 | *.orig
10 | *.pyc
11 | *.swo
12 | *.swp
13 | *.tox
14 | *.zip
15 | *.sublime-*
16 | .env
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | python:
4 | - '2.7'
5 | - '3.4'
6 | - '3.5'
7 | - '3.6'
8 | install:
9 | - pip install tox-travis
10 | script:
11 | - tox
12 |
--------------------------------------------------------------------------------
/docs/_themes/flask_small/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = basic
3 | stylesheet = flasky.css
4 | nosidebar = true
5 | pygments_style = flask_theme_support.FlaskyStyle
6 |
7 | [options]
8 | index_logo = ''
9 | index_logo_height = 120px
10 | github_fork = ''
11 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | # Tox (http://tox.testrun.org/) is a tool for running tests
2 | # in multiple virtualenvs. This configuration file will run the
3 | # test suite on all supported python versions. To use it, "pip install tox"
4 | # and then run "tox" from this directory.
5 |
6 | [tox]
7 | envlist =
8 | {py27,py34,py35,py36}-{lowest,release,devel}
9 |
10 | [testenv]
11 | deps =
12 | pytest
13 |
14 | lowest: Flask>=0.10, <0.11
15 | release: Flask
16 | devel: git+https://github.com/mitsuhiko/flask.git
17 | commands = py.test tests.py
18 |
--------------------------------------------------------------------------------
/docs/_static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Flask-Script documentation has moved...
5 |
6 |
7 |
8 |
9 | Flask-Script documentation is now maintained at Read the Docs.
10 | If your browser does not automatically redirect you, please click here.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/_themes/flask_small/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "basic/layout.html" %}
2 | {% block header %}
3 | {{ super() }}
4 | {% if pagename == 'index' %}
5 |
6 | {% endif %}
7 | {% endblock %}
8 | {% block footer %}
9 | {% if pagename == 'index' %}
10 |
11 | {% endif %}
12 | {% endblock %}
13 | {# do not display relbars #}
14 | {% block relbar1 %}{% endblock %}
15 | {% block relbar2 %}
16 | {% if theme_github_fork %}
17 |
19 | {% endif %}
20 | {% endblock %}
21 | {% block sidebar1 %}{% endblock %}
22 | {% block sidebar2 %}{% endblock %}
23 |
--------------------------------------------------------------------------------
/docs/_themes/README:
--------------------------------------------------------------------------------
1 | Flask Sphinx Styles
2 | ===================
3 |
4 | This repository contains sphinx styles for Flask and Flask related
5 | projects. To use this style in your Sphinx documentation, follow
6 | this guide:
7 |
8 | 1. put this folder as _themes into your docs folder. Alternatively
9 | you can also use git submodules to check out the contents there.
10 | 2. add this to your conf.py:
11 |
12 | sys.path.append(os.path.abspath('_themes'))
13 | html_theme_path = ['_themes']
14 | html_theme = 'flask'
15 |
16 | The following themes exist:
17 |
18 | - 'flask' - the standard flask documentation theme for large
19 | projects
20 | - 'flask_small' - small one-page theme. Intended to be used by
21 | very small addon libraries for flask.
22 |
23 | The following options exist for the flask_small theme:
24 |
25 | [options]
26 | index_logo = '' filename of a picture in _static
27 | to be used as replacement for the
28 | h1 in the index.rst file.
29 | index_logo_height = 120px height of the index logo
30 | github_fork = '' repository name on github for the
31 | "fork me" badge
32 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Deprecated
2 | ------------
3 |
4 | While the maintainers are willing to merge PR's, they are not actively developing features. As of Flask 0.11, Flask includes `a built-in CLI tool`__, and that may fit your needs better.
5 |
6 | __ http://flask.pocoo.org/docs/latest/cli/
7 |
8 |
9 | Flask-Script
10 | ==============
11 |
12 | .. image:: https://travis-ci.org/smurfix/flask-script.svg?branch=master
13 | :target: https://travis-ci.org/smurfix/flask-script
14 |
15 | .. image:: https://img.shields.io/pypi/v/flask-script.svg
16 | :target: http://flask-script.readthedocs.org/en/latest/
17 | :alt: Latest Version
18 |
19 | .. image:: https://img.shields.io/pypi/pyversions/flask-script.svg
20 | :target: https://pypi.python.org/pypi/flask-script/
21 | :alt: Supported Python versions
22 |
23 | .. image:: https://img.shields.io/pypi/l/flask-script.svg
24 | :target: https://github.com/smurfix/flask-script/blob/master/LICENSE
25 | :alt: License
26 |
27 | A set of utilities for use with the Flask framework which provide
28 | decorators, classes and helpers for writing your own script commands.
29 |
30 | Useful for creating command-line scripts, cronjobs etc outside your
31 | web application.
32 |
33 |
34 | Resources
35 | ---------
36 |
37 | - `Documentation `_
38 | - `Issue Tracker `_
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 by Dan Jacob.
2 | Copyright (c) 2014 by Matthias Urlichs.
3 |
4 | Some rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are
8 | met:
9 |
10 | * Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above
14 | copyright notice, this list of conditions and the following
15 | disclaimer in the documentation and/or other materials provided
16 | with the distribution.
17 |
18 | * The names of the contributors may not be used to endorse or
19 | promote products derived from this software without specific
20 | prior written permission.
21 |
22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/examples/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # PYTHON_ARGCOMPLETE_OK
3 |
4 | import pprint
5 |
6 | from flask import Flask, current_app
7 | from flask_script import Manager, prompt_choices, Server
8 | from flask_script.commands import ShowUrls, Clean
9 |
10 |
11 | def create_app(config=None):
12 | app = Flask(__name__)
13 | app.debug = False
14 | print "CONFIG", config
15 |
16 | app.config.from_envvar('APP_CONFIG', silent=True)
17 |
18 | @app.route("/")
19 | def index():
20 | # deliberate error, test debug working
21 | assert False, "oops"
22 |
23 | return app
24 |
25 | manager = Manager(create_app)
26 |
27 |
28 | @manager.command
29 | def dumpconfig():
30 | "Dumps config"
31 | pprint.pprint(current_app.config)
32 |
33 |
34 | @manager.command
35 | def output(name):
36 | "print something"
37 | print name
38 | print type(name)
39 |
40 |
41 | @manager.command
42 | def outputplus(name, url=None):
43 | "print name and url"
44 | print name, url
45 |
46 |
47 | @manager.command
48 | def getrolesimple():
49 |
50 | choices = ("member", "moderator", "admin")
51 |
52 | role = prompt_choices("role", choices=choices, default="member")
53 | print "ROLE:", role
54 |
55 |
56 | @manager.command
57 | def getrole():
58 |
59 | choices = (
60 | (1, "member"),
61 | (2, "moderator"),
62 | (3, "admin"),
63 | )
64 |
65 | role = prompt_choices("role", choices=choices, resolve=int, default=1)
66 | print "ROLE:", role
67 |
68 |
69 | @manager.option('-n', '--name', dest='name', help="your name")
70 | @manager.option('-u', '--url', dest='url', help="your url")
71 | def optional(name, url):
72 | "print name and url"
73 | print name, url
74 |
75 | manager.add_option("-c", "--config",
76 | dest="config",
77 | help="config file",
78 | required=False)
79 |
80 | manager.add_command("runservernoreload", Server(use_reloader=False))
81 | manager.add_command("urls", ShowUrls())
82 | manager.add_command("clean", Clean())
83 |
84 | if __name__ == "__main__":
85 | manager.run()
86 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | Flask-Script
3 | --------------
4 |
5 | Flask support for writing external scripts.
6 |
7 | Links
8 | `````
9 |
10 | * `documentation `_
11 |
12 |
13 | """
14 | import sys
15 | from setuptools import setup
16 |
17 | version='2.0.6'
18 |
19 | # Hack to prevent stupid TypeError: 'NoneType' object is not callable error on
20 | # exit of python setup.py test # in multiprocessing/util.py _exit_function when
21 | # running python setup.py test (see
22 | # https://github.com/pypa/virtualenv/pull/259)
23 | try:
24 | import multiprocessing
25 | except ImportError:
26 | pass
27 |
28 | install_requires = ['Flask']
29 |
30 | setup(
31 | name='Flask-Script',
32 | version=version,
33 | url='http://github.com/smurfix/flask-script',
34 | download_url = 'https://github.com/smurfix/flask-script/tarball/v'+version,
35 | license='BSD',
36 | author='Dan Jacob',
37 | author_email='danjac354@gmail.com',
38 | maintainer='Matthias Urlichs',
39 | maintainer_email='matthias@urlichs.de',
40 | description='Scripting support for Flask',
41 | long_description=__doc__,
42 | packages=[
43 | 'flask_script'
44 | ],
45 | zip_safe=False,
46 | install_requires=install_requires,
47 | tests_require=[
48 | 'pytest',
49 | ],
50 | platforms='any',
51 | classifiers=[
52 | 'Development Status :: 5 - Production/Stable',
53 | 'Environment :: Web Environment',
54 | 'Intended Audience :: Developers',
55 | 'License :: OSI Approved :: BSD License',
56 | 'Operating System :: OS Independent',
57 | 'Programming Language :: Python',
58 | 'Programming Language :: Python :: 2',
59 | 'Programming Language :: Python :: 2.7',
60 | 'Programming Language :: Python :: 3',
61 | 'Programming Language :: Python :: 3.4',
62 | 'Programming Language :: Python :: 3.5',
63 | 'Programming Language :: Python :: 3.6',
64 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
65 | 'Topic :: Software Development :: Libraries :: Python Modules'
66 | ]
67 | )
68 |
--------------------------------------------------------------------------------
/CHANGES:
--------------------------------------------------------------------------------
1 | Flask-Script Changelog
2 | ======================
3 |
4 | Here you can see the full list of changes between each Flask-Script release.
5 |
6 | Version 0.6.7
7 | -----------------
8 |
9 | Released on February 16, 2014
10 |
11 | - Expose app instance in a command commands (manage.app). #83
12 | - Show full help for submanagers if called without arguments. #85
13 | - Fix ShowUrls command conflict. #88
14 |
15 | Version 0.6.6
16 | -----------------
17 |
18 | Released on December 6, 2013
19 |
20 | - Fix global being passed after command by not expliciting checking for the 'parents' argument.
21 |
22 | Version 0.6.5
23 | -----------------
24 |
25 | Released on December 5, 2013
26 |
27 | - Change warning from UserWarning to DeprecationWarning so it is ignored by default
28 |
29 | Version 0.6.4
30 | -----------------
31 |
32 | Released on December 5, 2013
33 |
34 | - Only pass `parents` argument if a command's `create_parser` accepts it. Workaround for #71
35 |
36 | Version 0.6.3
37 | -----------------
38 |
39 | Released on November 11, 2013
40 |
41 | - Separate usage into usage/help/description
42 | - Allow for command auto detection
43 |
44 | Version 0.6.2
45 | -----------------
46 |
47 | Released on August 10, 2013
48 |
49 | - FIXED: 0.6.1 fails to embed ipython at all
50 |
51 | Version 0.6.1
52 | -----------------
53 |
54 | Released on August 9, 2013
55 |
56 | - FIXED: IPython Shell embedding fails after upgrade to IPython 1.0
57 |
58 | Version 0.6.0
59 | -------------
60 |
61 | Released on August 7, 2013.
62 |
63 | - Drop support for Python 2.5
64 | - Support Python 2.6/2.7 and >= 3.3 using same source code.
65 | Import necessary compatibility code from flask._compat module of current
66 | Flask repo code.
67 | - Use proper argparse subparsers
68 | - Tab completion using `argcomplete`
69 | - Remove question marks from automatically being appended to prompt_bool and prompt_choices
70 | - FIXED: ipython with disabled bpython
71 | - FIXED: debug parameter no longer passed in from flask_script
72 |
73 | Version 0.5.3
74 | -------------
75 |
76 | Released on January 9, 2013.
77 |
78 | - Fix nasty bug in Clean command that deletes all files
79 |
80 | Version 0.5.2
81 | -------------
82 |
83 | Released on December 26, 2012.
84 |
85 | - Change from module to package and refactor project structure
86 | - Add BPython shell support
87 | - Add Clean and ShowUrls commands
88 | - Add Group, a simple way to group arguments
89 |
90 | Version 0.5.1
91 | -------------
92 |
93 | Released on October 2nd, 2012.
94 |
95 | - Fixed an issue where debug settings were being overridden if present in config.
96 | - Expose 'passthrough_errors' on Server to disable error catching (useful to hook debuggers in (ex. pdb))
97 |
98 | Version 0.5.0
99 | -------------
100 |
101 | Released on September 22, 2012.
102 |
103 | - Sub-Manager support (see: http://flask-script.readthedocs.org/en/latest/index.html#sub-managers)
104 |
--------------------------------------------------------------------------------
/flask_script/cli.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import getpass
4 | from ._compat import string_types, input
5 |
6 |
7 | def prompt(name, default=None):
8 | """
9 | Grab user input from command line.
10 |
11 | :param name: prompt text
12 | :param default: default value if no input provided.
13 | """
14 |
15 | prompt = name + (default and ' [%s]' % default or '')
16 | prompt += name.endswith('?') and ' ' or ': '
17 | while True:
18 | rv = input(prompt)
19 | if rv:
20 | return rv
21 | if default is not None:
22 | return default
23 |
24 |
25 | def prompt_pass(name, default=None):
26 | """
27 | Grabs hidden (password) input from command line.
28 |
29 | :param name: prompt text
30 | :param default: default value if no input provided.
31 | """
32 |
33 | prompt = name + (default and ' [%s]' % default or '')
34 | prompt += name.endswith('?') and ' ' or ': '
35 | while True:
36 | rv = getpass.getpass(prompt)
37 | if rv:
38 | return rv
39 | if default is not None:
40 | return default
41 |
42 |
43 | def prompt_bool(name, default=False, yes_choices=None, no_choices=None):
44 | """
45 | Grabs user input from command line and converts to boolean
46 | value.
47 |
48 | :param name: prompt text
49 | :param default: default value if no input provided.
50 | :param yes_choices: default 'y', 'yes', '1', 'on', 'true', 't'
51 | :param no_choices: default 'n', 'no', '0', 'off', 'false', 'f'
52 | """
53 |
54 | yes_choices = yes_choices or ('y', 'yes', '1', 'on', 'true', 't')
55 | no_choices = no_choices or ('n', 'no', '0', 'off', 'false', 'f')
56 |
57 | while True:
58 | rv = prompt(name, default and yes_choices[0] or no_choices[0])
59 | if not rv:
60 | return default
61 | if rv.lower() in yes_choices:
62 | return True
63 | elif rv.lower() in no_choices:
64 | return False
65 |
66 |
67 | def prompt_choices(name, choices, default=None, no_choice=('none',)):
68 | """
69 | Grabs user input from command line from set of provided choices.
70 |
71 | :param name: prompt text
72 | :param choices: list or tuple of available choices. Choices may be
73 | single strings or (key, value) tuples.
74 | :param default: default value if no input provided.
75 | :param no_choice: acceptable list of strings for "null choice"
76 | """
77 |
78 | _choices = []
79 | options = []
80 |
81 | for choice in choices:
82 | if isinstance(choice, string_types):
83 | options.append(choice)
84 | else:
85 | options.append("%s [%s]" % (choice[1], choice[0]))
86 | choice = choice[0]
87 | _choices.append(choice)
88 |
89 | while True:
90 | rv = prompt(name + ' - (%s)' % ', '.join(options), default).lower()
91 | if rv in no_choice:
92 | return None
93 | if rv in _choices or rv == default:
94 | return rv
95 |
--------------------------------------------------------------------------------
/flask_script/_compat.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | flask_script._compat
4 | ~~~~~~~~~~~~~~~~~~~~
5 |
6 | Some py2/py3 compatibility support based on a stripped down
7 | version of six so we don't have to depend on a specific version
8 | of it.
9 |
10 | :copyright: (c) 2013 by Armin Ronacher.
11 | :license: BSD, see LICENSE for more details.
12 | """
13 | import sys
14 |
15 | PY2 = sys.version_info[0] == 2
16 | PYPY = hasattr(sys, 'pypy_translation_info')
17 | _identity = lambda x: x
18 |
19 |
20 | if not PY2:
21 | unichr = chr
22 | range_type = range
23 | text_type = str
24 | string_types = (str, )
25 | integer_types = (int, )
26 |
27 | iterkeys = lambda d: iter(d.keys())
28 | itervalues = lambda d: iter(d.values())
29 | iteritems = lambda d: iter(d.items())
30 |
31 | import pickle
32 | from io import BytesIO, StringIO
33 | NativeStringIO = StringIO
34 |
35 | def reraise(tp, value, tb=None):
36 | if value.__traceback__ is not tb:
37 | raise value.with_traceback(tb)
38 | raise value
39 |
40 | ifilter = filter
41 | imap = map
42 | izip = zip
43 | intern = sys.intern
44 |
45 | implements_iterator = _identity
46 | implements_to_string = _identity
47 | encode_filename = _identity
48 | get_next = lambda x: x.__next__
49 |
50 | input = input
51 |
52 | else:
53 | unichr = unichr
54 | text_type = unicode
55 | range_type = xrange
56 | string_types = (str, unicode)
57 | integer_types = (int, long)
58 |
59 | iterkeys = lambda d: d.iterkeys()
60 | itervalues = lambda d: d.itervalues()
61 | iteritems = lambda d: d.iteritems()
62 |
63 | import cPickle as pickle
64 | from cStringIO import StringIO as BytesIO, StringIO
65 | NativeStringIO = BytesIO
66 |
67 | exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
68 |
69 | from itertools import imap, izip, ifilter
70 | intern = intern
71 |
72 | def implements_iterator(cls):
73 | cls.next = cls.__next__
74 | del cls.__next__
75 | return cls
76 |
77 | def implements_to_string(cls):
78 | cls.__unicode__ = cls.__str__
79 | cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
80 | return cls
81 |
82 | get_next = lambda x: x.next
83 |
84 | def encode_filename(filename):
85 | if isinstance(filename, unicode):
86 | return filename.encode('utf-8')
87 | return filename
88 |
89 | input = raw_input
90 |
91 |
92 | def with_metaclass(meta, *bases):
93 | # This requires a bit of explanation: the basic idea is to make a
94 | # dummy metaclass for one level of class instantiation that replaces
95 | # itself with the actual metaclass. Because of internal type checks
96 | # we also need to make sure that we downgrade the custom metaclass
97 | # for one level to something closer to type (that's why __call__ and
98 | # __init__ comes back from type etc.).
99 | #
100 | # This has the advantage over six.with_metaclass in that it does not
101 | # introduce dummy classes into the final MRO.
102 | class metaclass(meta):
103 | __call__ = type.__call__
104 | __init__ = type.__init__
105 | def __new__(cls, name, this_bases, d):
106 | if this_bases is None:
107 | return type.__new__(cls, name, (), d)
108 | return meta(name, bases, d)
109 | return metaclass('temporary_class', None, {})
110 |
111 |
112 | try:
113 | from urllib.parse import quote_from_bytes as url_quote
114 | except ImportError:
115 | from urllib import quote as url_quote
116 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | if NOT "%PAPER%" == "" (
11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
12 | )
13 |
14 | if "%1" == "" goto help
15 |
16 | if "%1" == "help" (
17 | :help
18 | echo.Please use `make ^` where ^ is one of
19 | echo. html to make standalone HTML files
20 | echo. dirhtml to make HTML files named index.html in directories
21 | echo. singlehtml to make a single large HTML file
22 | echo. pickle to make pickle files
23 | echo. json to make JSON files
24 | echo. htmlhelp to make HTML files and a HTML help project
25 | echo. qthelp to make HTML files and a qthelp project
26 | echo. devhelp to make HTML files and a Devhelp project
27 | echo. epub to make an epub
28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
29 | echo. text to make text files
30 | echo. man to make manual pages
31 | echo. changes to make an overview over all changed/added/deprecated items
32 | echo. linkcheck to check all external links for integrity
33 | echo. doctest to run all doctests embedded in the documentation if enabled
34 | goto end
35 | )
36 |
37 | if "%1" == "clean" (
38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
39 | del /q /s %BUILDDIR%\*
40 | goto end
41 | )
42 |
43 | if "%1" == "html" (
44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
45 | echo.
46 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
47 | goto end
48 | )
49 |
50 | if "%1" == "dirhtml" (
51 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
52 | echo.
53 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
54 | goto end
55 | )
56 |
57 | if "%1" == "singlehtml" (
58 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
59 | echo.
60 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
61 | goto end
62 | )
63 |
64 | if "%1" == "pickle" (
65 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
66 | echo.
67 | echo.Build finished; now you can process the pickle files.
68 | goto end
69 | )
70 |
71 | if "%1" == "json" (
72 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
73 | echo.
74 | echo.Build finished; now you can process the JSON files.
75 | goto end
76 | )
77 |
78 | if "%1" == "htmlhelp" (
79 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
80 | echo.
81 | echo.Build finished; now you can run HTML Help Workshop with the ^
82 | .hhp project file in %BUILDDIR%/htmlhelp.
83 | goto end
84 | )
85 |
86 | if "%1" == "qthelp" (
87 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
88 | echo.
89 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
90 | .qhcp project file in %BUILDDIR%/qthelp, like this:
91 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\flask-unittest.qhcp
92 | echo.To view the help file:
93 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\flask-unittest.ghc
94 | goto end
95 | )
96 |
97 | if "%1" == "devhelp" (
98 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
99 | echo.
100 | echo.Build finished.
101 | goto end
102 | )
103 |
104 | if "%1" == "epub" (
105 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
106 | echo.
107 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
108 | goto end
109 | )
110 |
111 | if "%1" == "latex" (
112 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
113 | echo.
114 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
115 | goto end
116 | )
117 |
118 | if "%1" == "text" (
119 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
120 | echo.
121 | echo.Build finished. The text files are in %BUILDDIR%/text.
122 | goto end
123 | )
124 |
125 | if "%1" == "man" (
126 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
127 | echo.
128 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
129 | goto end
130 | )
131 |
132 | if "%1" == "changes" (
133 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
134 | echo.
135 | echo.The overview file is in %BUILDDIR%/changes.
136 | goto end
137 | )
138 |
139 | if "%1" == "linkcheck" (
140 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
141 | echo.
142 | echo.Link check complete; look for any errors in the above output ^
143 | or in %BUILDDIR%/linkcheck/output.txt.
144 | goto end
145 | )
146 |
147 | if "%1" == "doctest" (
148 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
149 | echo.
150 | echo.Testing of doctests in the sources finished, look at the ^
151 | results in %BUILDDIR%/doctest/output.txt.
152 | goto end
153 | )
154 |
155 | :end
156 |
--------------------------------------------------------------------------------
/docs/_themes/flask_theme_support.py:
--------------------------------------------------------------------------------
1 | # flasky extensions. flasky pygments style based on tango style
2 | from pygments.style import Style
3 | from pygments.token import Keyword, Name, Comment, String, Error, \
4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
5 |
6 |
7 | class FlaskyStyle(Style):
8 | background_color = "#f8f8f8"
9 | default_style = ""
10 |
11 | styles = {
12 | # No corresponding class for the following:
13 | #Text: "", # class: ''
14 | Whitespace: "underline #f8f8f8", # class: 'w'
15 | Error: "#a40000 border:#ef2929", # class: 'err'
16 | Other: "#000000", # class 'x'
17 |
18 | Comment: "italic #8f5902", # class: 'c'
19 | Comment.Preproc: "noitalic", # class: 'cp'
20 |
21 | Keyword: "bold #004461", # class: 'k'
22 | Keyword.Constant: "bold #004461", # class: 'kc'
23 | Keyword.Declaration: "bold #004461", # class: 'kd'
24 | Keyword.Namespace: "bold #004461", # class: 'kn'
25 | Keyword.Pseudo: "bold #004461", # class: 'kp'
26 | Keyword.Reserved: "bold #004461", # class: 'kr'
27 | Keyword.Type: "bold #004461", # class: 'kt'
28 |
29 | Operator: "#582800", # class: 'o'
30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords
31 |
32 | Punctuation: "bold #000000", # class: 'p'
33 |
34 | # because special names such as Name.Class, Name.Function, etc.
35 | # are not recognized as such later in the parsing, we choose them
36 | # to look the same as ordinary variables.
37 | Name: "#000000", # class: 'n'
38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised
39 | Name.Builtin: "#004461", # class: 'nb'
40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
41 | Name.Class: "#000000", # class: 'nc' - to be revised
42 | Name.Constant: "#000000", # class: 'no' - to be revised
43 | Name.Decorator: "#888", # class: 'nd' - to be revised
44 | Name.Entity: "#ce5c00", # class: 'ni'
45 | Name.Exception: "bold #cc0000", # class: 'ne'
46 | Name.Function: "#000000", # class: 'nf'
47 | Name.Property: "#000000", # class: 'py'
48 | Name.Label: "#f57900", # class: 'nl'
49 | Name.Namespace: "#000000", # class: 'nn' - to be revised
50 | Name.Other: "#000000", # class: 'nx'
51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword
52 | Name.Variable: "#000000", # class: 'nv' - to be revised
53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised
54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised
55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
56 |
57 | Number: "#990000", # class: 'm'
58 |
59 | Literal: "#000000", # class: 'l'
60 | Literal.Date: "#000000", # class: 'ld'
61 |
62 | String: "#4e9a06", # class: 's'
63 | String.Backtick: "#4e9a06", # class: 'sb'
64 | String.Char: "#4e9a06", # class: 'sc'
65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment
66 | String.Double: "#4e9a06", # class: 's2'
67 | String.Escape: "#4e9a06", # class: 'se'
68 | String.Heredoc: "#4e9a06", # class: 'sh'
69 | String.Interpol: "#4e9a06", # class: 'si'
70 | String.Other: "#4e9a06", # class: 'sx'
71 | String.Regex: "#4e9a06", # class: 'sr'
72 | String.Single: "#4e9a06", # class: 's1'
73 | String.Symbol: "#4e9a06", # class: 'ss'
74 |
75 | Generic: "#000000", # class: 'g'
76 | Generic.Deleted: "#a40000", # class: 'gd'
77 | Generic.Emph: "italic #000000", # class: 'ge'
78 | Generic.Error: "#ef2929", # class: 'gr'
79 | Generic.Heading: "bold #000080", # class: 'gh'
80 | Generic.Inserted: "#00A000", # class: 'gi'
81 | Generic.Output: "#888", # class: 'go'
82 | Generic.Prompt: "#745334", # class: 'gp'
83 | Generic.Strong: "bold #000000", # class: 'gs'
84 | Generic.Subheading: "bold #800080", # class: 'gu'
85 | Generic.Traceback: "bold #a40000", # class: 'gt'
86 | }
87 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 |
15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
16 |
17 | help:
18 | @echo "Please use \`make ' where is one of"
19 | @echo " html to make standalone HTML files"
20 | @echo " dirhtml to make HTML files named index.html in directories"
21 | @echo " singlehtml to make a single large HTML file"
22 | @echo " pickle to make pickle files"
23 | @echo " json to make JSON files"
24 | @echo " htmlhelp to make HTML files and a HTML help project"
25 | @echo " qthelp to make HTML files and a qthelp project"
26 | @echo " devhelp to make HTML files and a Devhelp project"
27 | @echo " epub to make an epub"
28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
29 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
30 | @echo " text to make text files"
31 | @echo " man to make manual pages"
32 | @echo " changes to make an overview of all changed/added/deprecated items"
33 | @echo " linkcheck to check all external links for integrity"
34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
35 |
36 | clean:
37 | -rm -rf $(BUILDDIR)/*
38 |
39 | html:
40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
41 | @echo
42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
43 |
44 | dirhtml:
45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
48 |
49 | singlehtml:
50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
51 | @echo
52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
53 |
54 | pickle:
55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
56 | @echo
57 | @echo "Build finished; now you can process the pickle files."
58 |
59 | json:
60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
61 | @echo
62 | @echo "Build finished; now you can process the JSON files."
63 |
64 | htmlhelp:
65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
66 | @echo
67 | @echo "Build finished; now you can run HTML Help Workshop with the" \
68 | ".hhp project file in $(BUILDDIR)/htmlhelp."
69 |
70 | qthelp:
71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
72 | @echo
73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/flask-unittest.qhcp"
76 | @echo "To view the help file:"
77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/flask-unittest.qhc"
78 |
79 | devhelp:
80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
81 | @echo
82 | @echo "Build finished."
83 | @echo "To view the help file:"
84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/flask-unittest"
85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/flask-unittest"
86 | @echo "# devhelp"
87 |
88 | epub:
89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
90 | @echo
91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
92 |
93 | latex:
94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
95 | @echo
96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
98 | "(use \`make latexpdf' here to do that automatically)."
99 |
100 | latexpdf:
101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
102 | @echo "Running LaTeX files through pdflatex..."
103 | make -C $(BUILDDIR)/latex all-pdf
104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
105 |
106 | text:
107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
108 | @echo
109 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
110 |
111 | man:
112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
113 | @echo
114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
115 |
116 | changes:
117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
118 | @echo
119 | @echo "The overview file is in $(BUILDDIR)/changes."
120 |
121 | linkcheck:
122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
123 | @echo
124 | @echo "Link check complete; look for any errors in the above output " \
125 | "or in $(BUILDDIR)/linkcheck/output.txt."
126 |
127 | doctest:
128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
129 | @echo "Testing of doctests in the sources finished, look at the " \
130 | "results in $(BUILDDIR)/doctest/output.txt."
131 |
--------------------------------------------------------------------------------
/docs/_themes/flask_small/static/flasky.css_t:
--------------------------------------------------------------------------------
1 | /*
2 | * flasky.css_t
3 | * ~~~~~~~~~~~~
4 | *
5 | * Sphinx stylesheet -- flasky theme based on nature theme.
6 | *
7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: 'Georgia', serif;
18 | font-size: 17px;
19 | color: #000;
20 | background: white;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.documentwrapper {
26 | float: left;
27 | width: 100%;
28 | }
29 |
30 | div.bodywrapper {
31 | margin: 40px auto 0 auto;
32 | width: 700px;
33 | }
34 |
35 | hr {
36 | border: 1px solid #B1B4B6;
37 | }
38 |
39 | div.body {
40 | background-color: #ffffff;
41 | color: #3E4349;
42 | padding: 0 30px 30px 30px;
43 | }
44 |
45 | img.floatingflask {
46 | padding: 0 0 10px 10px;
47 | float: right;
48 | }
49 |
50 | div.footer {
51 | text-align: right;
52 | color: #888;
53 | padding: 10px;
54 | font-size: 14px;
55 | width: 650px;
56 | margin: 0 auto 40px auto;
57 | }
58 |
59 | div.footer a {
60 | color: #888;
61 | text-decoration: underline;
62 | }
63 |
64 | div.related {
65 | line-height: 32px;
66 | color: #888;
67 | }
68 |
69 | div.related ul {
70 | padding: 0 0 0 10px;
71 | }
72 |
73 | div.related a {
74 | color: #444;
75 | }
76 |
77 | /* -- body styles ----------------------------------------------------------- */
78 |
79 | a {
80 | color: #004B6B;
81 | text-decoration: underline;
82 | }
83 |
84 | a:hover {
85 | color: #6D4100;
86 | text-decoration: underline;
87 | }
88 |
89 | div.body {
90 | padding-bottom: 40px; /* saved for footer */
91 | }
92 |
93 | div.body h1,
94 | div.body h2,
95 | div.body h3,
96 | div.body h4,
97 | div.body h5,
98 | div.body h6 {
99 | font-family: 'Garamond', 'Georgia', serif;
100 | font-weight: normal;
101 | margin: 30px 0px 10px 0px;
102 | padding: 0;
103 | }
104 |
105 | {% if theme_index_logo %}
106 | div.indexwrapper h1 {
107 | text-indent: -999999px;
108 | background: url({{ theme_index_logo }}) no-repeat center center;
109 | height: {{ theme_index_logo_height }};
110 | }
111 | {% endif %}
112 |
113 | div.body h2 { font-size: 180%; }
114 | div.body h3 { font-size: 150%; }
115 | div.body h4 { font-size: 130%; }
116 | div.body h5 { font-size: 100%; }
117 | div.body h6 { font-size: 100%; }
118 |
119 | a.headerlink {
120 | color: white;
121 | padding: 0 4px;
122 | text-decoration: none;
123 | }
124 |
125 | a.headerlink:hover {
126 | color: #444;
127 | background: #eaeaea;
128 | }
129 |
130 | div.body p, div.body dd, div.body li {
131 | line-height: 1.4em;
132 | }
133 |
134 | div.admonition {
135 | background: #fafafa;
136 | margin: 20px -30px;
137 | padding: 10px 30px;
138 | border-top: 1px solid #ccc;
139 | border-bottom: 1px solid #ccc;
140 | }
141 |
142 | div.admonition p.admonition-title {
143 | font-family: 'Garamond', 'Georgia', serif;
144 | font-weight: normal;
145 | font-size: 24px;
146 | margin: 0 0 10px 0;
147 | padding: 0;
148 | line-height: 1;
149 | }
150 |
151 | div.admonition p.last {
152 | margin-bottom: 0;
153 | }
154 |
155 | div.highlight{
156 | background-color: white;
157 | }
158 |
159 | dt:target, .highlight {
160 | background: #FAF3E8;
161 | }
162 |
163 | div.note {
164 | background-color: #eee;
165 | border: 1px solid #ccc;
166 | }
167 |
168 | div.seealso {
169 | background-color: #ffc;
170 | border: 1px solid #ff6;
171 | }
172 |
173 | div.topic {
174 | background-color: #eee;
175 | }
176 |
177 | div.warning {
178 | background-color: #ffe4e4;
179 | border: 1px solid #f66;
180 | }
181 |
182 | p.admonition-title {
183 | display: inline;
184 | }
185 |
186 | p.admonition-title:after {
187 | content: ":";
188 | }
189 |
190 | pre, tt {
191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
192 | font-size: 0.85em;
193 | }
194 |
195 | img.screenshot {
196 | }
197 |
198 | tt.descname, tt.descclassname {
199 | font-size: 0.95em;
200 | }
201 |
202 | tt.descname {
203 | padding-right: 0.08em;
204 | }
205 |
206 | img.screenshot {
207 | -moz-box-shadow: 2px 2px 4px #eee;
208 | -webkit-box-shadow: 2px 2px 4px #eee;
209 | box-shadow: 2px 2px 4px #eee;
210 | }
211 |
212 | table.docutils {
213 | border: 1px solid #888;
214 | -moz-box-shadow: 2px 2px 4px #eee;
215 | -webkit-box-shadow: 2px 2px 4px #eee;
216 | box-shadow: 2px 2px 4px #eee;
217 | }
218 |
219 | table.docutils td, table.docutils th {
220 | border: 1px solid #888;
221 | padding: 0.25em 0.7em;
222 | }
223 |
224 | table.field-list, table.footnote {
225 | border: none;
226 | -moz-box-shadow: none;
227 | -webkit-box-shadow: none;
228 | box-shadow: none;
229 | }
230 |
231 | table.footnote {
232 | margin: 15px 0;
233 | width: 100%;
234 | border: 1px solid #eee;
235 | }
236 |
237 | table.field-list th {
238 | padding: 0 0.8em 0 0;
239 | }
240 |
241 | table.field-list td {
242 | padding: 0;
243 | }
244 |
245 | table.footnote td {
246 | padding: 0.5em;
247 | }
248 |
249 | dl {
250 | margin: 0;
251 | padding: 0;
252 | }
253 |
254 | dl dd {
255 | margin-left: 30px;
256 | }
257 |
258 | pre {
259 | padding: 0;
260 | margin: 15px -30px;
261 | padding: 8px;
262 | line-height: 1.3em;
263 | padding: 7px 30px;
264 | background: #eee;
265 | border-radius: 2px;
266 | -moz-border-radius: 2px;
267 | -webkit-border-radius: 2px;
268 | }
269 |
270 | dl pre {
271 | margin-left: -60px;
272 | padding-left: 60px;
273 | }
274 |
275 | tt {
276 | background-color: #ecf0f3;
277 | color: #222;
278 | /* padding: 1px 2px; */
279 | }
280 |
281 | tt.xref, a tt {
282 | background-color: #FBFBFB;
283 | }
284 |
285 | a:hover tt {
286 | background: #EEE;
287 | }
288 |
--------------------------------------------------------------------------------
/docs/_themes/flask/static/flasky.css_t:
--------------------------------------------------------------------------------
1 | /*
2 | * flasky.css_t
3 | * ~~~~~~~~~~~~
4 | *
5 | * Sphinx stylesheet -- flasky theme based on nature theme.
6 | *
7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: 'Georgia', serif;
18 | font-size: 17px;
19 | background-color: #ddd;
20 | color: #000;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.document {
26 | background: #fafafa;
27 | }
28 |
29 | div.documentwrapper {
30 | float: left;
31 | width: 100%;
32 | }
33 |
34 | div.bodywrapper {
35 | margin: 0 0 0 230px;
36 | }
37 |
38 | hr {
39 | border: 1px solid #B1B4B6;
40 | }
41 |
42 | div.body {
43 | background-color: #ffffff;
44 | color: #3E4349;
45 | padding: 0 30px 30px 30px;
46 | min-height: 34em;
47 | }
48 |
49 | img.floatingflask {
50 | padding: 0 0 10px 10px;
51 | float: right;
52 | }
53 |
54 | div.footer {
55 | position: absolute;
56 | right: 0;
57 | margin-top: -70px;
58 | text-align: right;
59 | color: #888;
60 | padding: 10px;
61 | font-size: 14px;
62 | }
63 |
64 | div.footer a {
65 | color: #888;
66 | text-decoration: underline;
67 | }
68 |
69 | div.related {
70 | line-height: 32px;
71 | color: #888;
72 | }
73 |
74 | div.related ul {
75 | padding: 0 0 0 10px;
76 | }
77 |
78 | div.related a {
79 | color: #444;
80 | }
81 |
82 | div.sphinxsidebar {
83 | font-size: 14px;
84 | line-height: 1.5;
85 | }
86 |
87 | div.sphinxsidebarwrapper {
88 | padding: 0 20px;
89 | }
90 |
91 | div.sphinxsidebarwrapper p.logo {
92 | padding: 20px 0 10px 0;
93 | margin: 0;
94 | text-align: center;
95 | }
96 |
97 | div.sphinxsidebar h3,
98 | div.sphinxsidebar h4 {
99 | font-family: 'Garamond', 'Georgia', serif;
100 | color: #222;
101 | font-size: 24px;
102 | font-weight: normal;
103 | margin: 20px 0 5px 0;
104 | padding: 0;
105 | }
106 |
107 | div.sphinxsidebar h4 {
108 | font-size: 20px;
109 | }
110 |
111 | div.sphinxsidebar h3 a {
112 | color: #444;
113 | }
114 |
115 | div.sphinxsidebar p {
116 | color: #555;
117 | margin: 10px 0;
118 | }
119 |
120 | div.sphinxsidebar ul {
121 | margin: 10px 0;
122 | padding: 0;
123 | color: #000;
124 | }
125 |
126 | div.sphinxsidebar a {
127 | color: #444;
128 | text-decoration: none;
129 | }
130 |
131 | div.sphinxsidebar a:hover {
132 | text-decoration: underline;
133 | }
134 |
135 | div.sphinxsidebar input {
136 | border: 1px solid #ccc;
137 | font-family: 'Georgia', serif;
138 | font-size: 1em;
139 | }
140 |
141 | /* -- body styles ----------------------------------------------------------- */
142 |
143 | a {
144 | color: #004B6B;
145 | text-decoration: underline;
146 | }
147 |
148 | a:hover {
149 | color: #6D4100;
150 | text-decoration: underline;
151 | }
152 |
153 | div.body {
154 | padding-bottom: 40px; /* saved for footer */
155 | }
156 |
157 | div.body h1,
158 | div.body h2,
159 | div.body h3,
160 | div.body h4,
161 | div.body h5,
162 | div.body h6 {
163 | font-family: 'Garamond', 'Georgia', serif;
164 | font-weight: normal;
165 | margin: 30px 0px 10px 0px;
166 | padding: 0;
167 | }
168 |
169 | div.body h1 { margin-top: 0; padding-top: 20px; font-size: 240%; }
170 | div.body h2 { font-size: 180%; }
171 | div.body h3 { font-size: 150%; }
172 | div.body h4 { font-size: 130%; }
173 | div.body h5 { font-size: 100%; }
174 | div.body h6 { font-size: 100%; }
175 |
176 | a.headerlink {
177 | color: white;
178 | padding: 0 4px;
179 | text-decoration: none;
180 | }
181 |
182 | a.headerlink:hover {
183 | color: #444;
184 | background: #eaeaea;
185 | }
186 |
187 | div.body p, div.body dd, div.body li {
188 | line-height: 1.4em;
189 | }
190 |
191 | div.admonition {
192 | background: #fafafa;
193 | margin: 20px -30px;
194 | padding: 10px 30px;
195 | border-top: 1px solid #ccc;
196 | border-bottom: 1px solid #ccc;
197 | }
198 |
199 | div.admonition p.admonition-title {
200 | font-family: 'Garamond', 'Georgia', serif;
201 | font-weight: normal;
202 | font-size: 24px;
203 | margin: 0 0 10px 0;
204 | padding: 0;
205 | line-height: 1;
206 | }
207 |
208 | div.admonition p.last {
209 | margin-bottom: 0;
210 | }
211 |
212 | div.highlight{
213 | background-color: white;
214 | }
215 |
216 | dt:target, .highlight {
217 | background: #FAF3E8;
218 | }
219 |
220 | div.note {
221 | background-color: #eee;
222 | border: 1px solid #ccc;
223 | }
224 |
225 | div.seealso {
226 | background-color: #ffc;
227 | border: 1px solid #ff6;
228 | }
229 |
230 | div.topic {
231 | background-color: #eee;
232 | }
233 |
234 | div.warning {
235 | background-color: #ffe4e4;
236 | border: 1px solid #f66;
237 | }
238 |
239 | p.admonition-title {
240 | display: inline;
241 | }
242 |
243 | p.admonition-title:after {
244 | content: ":";
245 | }
246 |
247 | pre, tt {
248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
249 | font-size: 0.9em;
250 | }
251 |
252 | img.screenshot {
253 | }
254 |
255 | tt.descname, tt.descclassname {
256 | font-size: 0.95em;
257 | }
258 |
259 | tt.descname {
260 | padding-right: 0.08em;
261 | }
262 |
263 | img.screenshot {
264 | -moz-box-shadow: 2px 2px 4px #eee;
265 | -webkit-box-shadow: 2px 2px 4px #eee;
266 | box-shadow: 2px 2px 4px #eee;
267 | }
268 |
269 | table.docutils {
270 | border: 1px solid #888;
271 | -moz-box-shadow: 2px 2px 4px #eee;
272 | -webkit-box-shadow: 2px 2px 4px #eee;
273 | box-shadow: 2px 2px 4px #eee;
274 | }
275 |
276 | table.docutils td, table.docutils th {
277 | border: 1px solid #888;
278 | padding: 0.25em 0.7em;
279 | }
280 |
281 | table.field-list, table.footnote {
282 | border: none;
283 | -moz-box-shadow: none;
284 | -webkit-box-shadow: none;
285 | box-shadow: none;
286 | }
287 |
288 | table.footnote {
289 | margin: 15px 0;
290 | width: 100%;
291 | border: 1px solid #eee;
292 | }
293 |
294 | table.field-list th {
295 | padding: 0 0.8em 0 0;
296 | }
297 |
298 | table.field-list td {
299 | padding: 0;
300 | }
301 |
302 | table.footnote td {
303 | padding: 0.5em;
304 | }
305 |
306 | dl {
307 | margin: 0;
308 | padding: 0;
309 | }
310 |
311 | dl dd {
312 | margin-left: 30px;
313 | }
314 |
315 | pre {
316 | background: #eee;
317 | padding: 7px 30px;
318 | margin: 15px -30px;
319 | line-height: 1.3em;
320 | }
321 |
322 | dl pre {
323 | margin-left: -60px;
324 | padding-left: 60px;
325 | }
326 |
327 | dl dl pre {
328 | margin-left: -90px;
329 | padding-left: 90px;
330 | }
331 |
332 | tt {
333 | background-color: #ecf0f3;
334 | color: #222;
335 | /* padding: 1px 2px; */
336 | }
337 |
338 | tt.xref, a tt {
339 | background-color: #FBFBFB;
340 | text-decoration: none!important;
341 | }
342 |
343 | a:hover tt {
344 | background: #EEE;
345 | }
346 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # flask-script documentation build configuration file, created by
4 | # sphinx-quickstart on Wed Jun 23 08:26:41 2010.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | sys.path.insert(0, os.path.abspath('..'))
20 | sys.path.append(os.path.abspath('_themes'))
21 |
22 | # -- General configuration -----------------------------------------------------
23 |
24 | # If your documentation needs a minimal Sphinx version, state it here.
25 | #needs_sphinx = '1.0'
26 |
27 | # Add any Sphinx extension module names here, as strings. They can be extensions
28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
30 |
31 | # Add any paths that contain templates here, relative to this directory.
32 | templates_path = ['_templates']
33 |
34 | # The suffix of source filenames.
35 | source_suffix = '.rst'
36 |
37 | # The encoding of source files.
38 | #source_encoding = 'utf-8-sig'
39 |
40 | # The master toctree document.
41 | master_doc = 'index'
42 |
43 | # General information about the project.
44 | project = u'Flask-Script'
45 | copyright = u'2010, Dan Jacob'
46 |
47 | # The version info for the project you're documenting, acts as replacement for
48 | # |version| and |release|, also used in various other places throughout the
49 | # built documents.
50 | #
51 | # The short X.Y version.
52 | version = '0.4'
53 | # The full version, including alpha/beta/rc tags.
54 | release = '0.4.0'
55 |
56 | # The language for content autogenerated by Sphinx. Refer to documentation
57 | # for a list of supported languages.
58 | #language = None
59 |
60 | # There are two options for replacing |today|: either, you set today to some
61 | # non-false value, then it is used:
62 | #today = ''
63 | # Else, today_fmt is used as the format for a strftime call.
64 | #today_fmt = '%B %d, %Y'
65 |
66 | # List of patterns, relative to source directory, that match files and
67 | # directories to ignore when looking for source files.
68 | exclude_patterns = ['_build']
69 |
70 | # The reST default role (used for this markup: `text`) to use for all documents.
71 | #default_role = None
72 |
73 | # If true, '()' will be appended to :func: etc. cross-reference text.
74 | #add_function_parentheses = True
75 |
76 | # If true, the current module name will be prepended to all description
77 | # unit titles (such as .. function::).
78 | #add_module_names = True
79 |
80 | # If true, sectionauthor and moduleauthor directives will be shown in the
81 | # output. They are ignored by default.
82 | #show_authors = False
83 |
84 | # The name of the Pygments (syntax highlighting) style to use.
85 | #pygments_style = 'sphinx'
86 |
87 | # A list of ignored prefixes for module index sorting.
88 | #modindex_common_prefix = []
89 |
90 |
91 | # -- Options for HTML output ---------------------------------------------------
92 |
93 | # The theme to use for HTML and HTML Help pages. See the documentation for
94 | # a list of builtin themes.
95 | html_theme = 'flask_small'
96 |
97 | html_theme_options = {
98 | 'index_logo': 'flask-script.png',
99 | 'github_fork': 'smurfix/flask-script'
100 | }
101 | # Theme options are theme-specific and customize the look and feel of a theme
102 | # further. For a list of options available for each theme, see the
103 | # documentation.
104 | #html_theme_options = {}
105 |
106 | # Add any paths that contain custom themes here, relative to this directory.
107 | html_theme_path = ['_themes']
108 |
109 | # The name for this set of Sphinx documents. If None, it defaults to
110 | # " v documentation".
111 | #html_title = None
112 |
113 | # A shorter title for the navigation bar. Default is the same as html_title.
114 | #html_short_title = None
115 |
116 | # The name of an image file (relative to this directory) to place at the top
117 | # of the sidebar.
118 | #html_logo = None
119 |
120 | # The name of an image file (within the static path) to use as favicon of the
121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
122 | # pixels large.
123 | #html_favicon = None
124 |
125 | # Add any paths that contain custom static files (such as style sheets) here,
126 | # relative to this directory. They are copied after the builtin static files,
127 | # so a file named "default.css" will overwrite the builtin "default.css".
128 | html_static_path = ['_static']
129 |
130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
131 | # using the given strftime format.
132 | #html_last_updated_fmt = '%b %d, %Y'
133 |
134 | # If true, SmartyPants will be used to convert quotes and dashes to
135 | # typographically correct entities.
136 | #html_use_smartypants = True
137 |
138 | # Custom sidebar templates, maps document names to template names.
139 | #html_sidebars = {}
140 |
141 | # Additional templates that should be rendered to pages, maps page names to
142 | # template names.
143 | #html_additional_pages = {}
144 |
145 | # If false, no module index is generated.
146 | #html_domain_indices = True
147 |
148 | # If false, no index is generated.
149 | #html_use_index = True
150 |
151 | # If true, the index is split into individual pages for each letter.
152 | #html_split_index = False
153 |
154 | # If true, links to the reST sources are added to the pages.
155 | #html_show_sourcelink = True
156 |
157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
158 | #html_show_sphinx = True
159 |
160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
161 | #html_show_copyright = True
162 |
163 | # If true, an OpenSearch description file will be output, and all pages will
164 | # contain a tag referring to it. The value of this option must be the
165 | # base URL from which the finished HTML is served.
166 | #html_use_opensearch = ''
167 |
168 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
169 | #html_file_suffix = ''
170 |
171 | # Output file base name for HTML help builder.
172 | htmlhelp_basename = 'flask-scriptdoc'
173 |
174 |
175 | # -- Options for LaTeX output --------------------------------------------------
176 |
177 | # The paper size ('letter' or 'a4').
178 | #latex_paper_size = 'letter'
179 |
180 | # The font size ('10pt', '11pt' or '12pt').
181 | #latex_font_size = '10pt'
182 |
183 | # Grouping the document tree into LaTeX files. List of tuples
184 | # (source start file, target name, title, author, documentclass [howto/manual]).
185 | latex_documents = [
186 | ('index', 'flask-script.tex', u'Flask-Script Documentation',
187 | u'Dan Jacob', 'manual'),
188 | ]
189 |
190 | # The name of an image file (relative to this directory) to place at the top of
191 | # the title page.
192 | #latex_logo = None
193 |
194 | # For "manual" documents, if this is true, then toplevel headings are parts,
195 | # not chapters.
196 | #latex_use_parts = False
197 |
198 | # If true, show page references after internal links.
199 | #latex_show_pagerefs = False
200 |
201 | # If true, show URL addresses after external links.
202 | #latex_show_urls = False
203 |
204 | # Additional stuff for the LaTeX preamble.
205 | #latex_preamble = ''
206 |
207 | # Documents to append as an appendix to all manuals.
208 | #latex_appendices = []
209 |
210 | # If false, no module index is generated.
211 | #latex_domain_indices = True
212 |
213 |
214 | # -- Options for manual page output --------------------------------------------
215 |
216 | # One entry per manual page. List of tuples
217 | # (source start file, name, description, authors, manual section).
218 | man_pages = [
219 | ('index', 'flask-script', u'Flask-Script Documentation',
220 | [u'Dan Jacob'], 1)
221 | ]
222 |
--------------------------------------------------------------------------------
/flask_script/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import absolute_import
3 |
4 | import os
5 | import re
6 | import sys
7 | import types
8 | import warnings
9 | from gettext import gettext as _
10 | from collections import OrderedDict
11 |
12 | import argparse
13 |
14 | from flask import Flask
15 | from flask._compat import text_type
16 |
17 | from ._compat import iteritems
18 | from .commands import Group, Option, Command, Server, Shell
19 | from .cli import prompt, prompt_pass, prompt_bool, prompt_choices
20 |
21 | __all__ = ["Command", "Shell", "Server", "Manager", "Group", "Option",
22 | "prompt", "prompt_pass", "prompt_bool", "prompt_choices"]
23 |
24 | safe_actions = (argparse._StoreAction,
25 | argparse._StoreConstAction,
26 | argparse._StoreTrueAction,
27 | argparse._StoreFalseAction,
28 | argparse._AppendAction,
29 | argparse._AppendConstAction,
30 | argparse._CountAction)
31 |
32 |
33 | try:
34 | import argcomplete
35 | ARGCOMPLETE_IMPORTED = True
36 | except ImportError:
37 | ARGCOMPLETE_IMPORTED = False
38 |
39 | def add_help(parser, help_args):
40 | if not help_args:
41 | return
42 | parser.add_argument(*help_args,
43 | action='help', default=argparse.SUPPRESS, help=_('show this help message and exit'))
44 |
45 | class Manager(object):
46 | """
47 | Controller class for handling a set of commands.
48 |
49 | Typical usage::
50 |
51 | class Print(Command):
52 |
53 | def run(self):
54 | print "hello"
55 |
56 | app = Flask(__name__)
57 |
58 | manager = Manager(app)
59 | manager.add_command("print", Print())
60 |
61 | if __name__ == "__main__":
62 | manager.run()
63 |
64 | On command line::
65 |
66 | python manage.py print
67 | > hello
68 |
69 | :param app: Flask instance, or callable returning a Flask instance.
70 | :param with_default_commands: load commands **runserver** and **shell**
71 | by default.
72 | :param disable_argcomplete: disable automatic loading of argcomplete.
73 |
74 | """
75 | help_args = ('-?','--help')
76 |
77 | def __init__(self, app=None, with_default_commands=None, usage=None,
78 | help=None, description=None, disable_argcomplete=False):
79 |
80 | self.app = app
81 |
82 | self.subparser_kwargs = dict()
83 |
84 | self._commands = OrderedDict()
85 | self._options = list()
86 |
87 | self.usage = usage
88 | self.help = help if help is not None else usage
89 | self.description = description if description is not None else usage
90 | self.disable_argcomplete = disable_argcomplete
91 | self.with_default_commands = with_default_commands
92 |
93 | self.parent = None
94 |
95 | def add_default_commands(self):
96 | """
97 | Adds the shell and runserver default commands. To override these,
98 | simply add your own equivalents using add_command or decorators.
99 | """
100 |
101 | if "shell" not in self._commands:
102 | self.add_command("shell", Shell())
103 | if "runserver" not in self._commands:
104 | self.add_command("runserver", Server())
105 |
106 | def add_option(self, *args, **kwargs):
107 | """
108 | Adds a global option. This is useful if you want to set variables
109 | applying to the application setup, rather than individual commands.
110 |
111 | For this to work, the manager must be initialized with a factory
112 | function rather than a Flask instance. Otherwise any options you set
113 | will be ignored.
114 |
115 | The arguments are then passed to your function, e.g.::
116 |
117 | def create_my_app(config=None):
118 | app = Flask(__name__)
119 | if config:
120 | app.config.from_pyfile(config)
121 |
122 | return app
123 |
124 | manager = Manager(create_my_app)
125 | manager.add_option("-c", "--config", dest="config", required=False)
126 | @manager.command
127 | def mycommand(app):
128 | app.do_something()
129 |
130 | and are invoked like this::
131 |
132 | > python manage.py -c dev.cfg mycommand
133 |
134 | Any manager options passed on the command line will not be passed to
135 | the command.
136 |
137 | Arguments for this function are the same as for the Option class.
138 | """
139 |
140 | self._options.append(Option(*args, **kwargs))
141 |
142 | def __call__(self, app=None, **kwargs):
143 | """
144 | This procedure is called with the App instance (if this is a
145 | sub-Manager) and any options.
146 |
147 | If your sub-Manager does not override this, any values for options will get lost.
148 | """
149 | if app is None:
150 | app = self.app
151 | if app is None:
152 | raise Exception("There is no app here. This is unlikely to work.")
153 |
154 | if isinstance(app, Flask):
155 | if kwargs:
156 | warnings.warn("Options will be ignored.")
157 | return app
158 |
159 | app = app(**kwargs)
160 | self.app = app
161 | return app
162 |
163 | def create_app(self, *args, **kwargs):
164 | warnings.warn("create_app() is deprecated; use __call__().", warnings.DeprecationWarning)
165 | return self(*args,**kwargs)
166 |
167 | def create_parser(self, prog, func_stack=(), parent=None):
168 | """
169 | Creates an ArgumentParser instance from options returned
170 | by get_options(), and subparser for the given commands.
171 | """
172 | prog = os.path.basename(prog)
173 | func_stack=func_stack+(self,)
174 |
175 | options_parser = argparse.ArgumentParser(add_help=False)
176 | for option in self.get_options():
177 | options_parser.add_argument(*option.args, **option.kwargs)
178 |
179 | parser = argparse.ArgumentParser(prog=prog, usage=self.usage,
180 | description=self.description,
181 | parents=[options_parser],
182 | add_help=False)
183 | add_help(parser, self.help_args)
184 |
185 | self._patch_argparser(parser)
186 |
187 | subparsers = parser.add_subparsers(**self.subparser_kwargs)
188 |
189 | for name, command in self._commands.items():
190 | usage = getattr(command, 'usage', None)
191 | help = getattr(command, 'help', None)
192 | if help is None: help = command.__doc__
193 | description = getattr(command, 'description', None)
194 | if description is None: description = command.__doc__
195 |
196 | command_parser = command.create_parser(name, func_stack=func_stack, parent=self)
197 |
198 | subparser = subparsers.add_parser(name, usage=usage, help=help,
199 | description=description,
200 | parents=[command_parser],
201 | add_help=False)
202 |
203 | if isinstance(command, Manager):
204 | self._patch_argparser(subparser)
205 |
206 | ## enable autocomplete only for parent parser when argcomplete is
207 | ## imported and it is NOT disabled in constructor
208 | if parent is None and ARGCOMPLETE_IMPORTED \
209 | and not self.disable_argcomplete:
210 | argcomplete.autocomplete(parser, always_complete_options=True)
211 |
212 | self.parser = parser
213 | return parser
214 |
215 | # def foo(self, app, *args, **kwargs):
216 | # print(args)
217 |
218 | def _patch_argparser(self, parser):
219 | """
220 | Patches the parser to print the full help if no arguments are supplied
221 | """
222 |
223 | def _parse_known_args(self, arg_strings, *args, **kw):
224 | if not arg_strings:
225 | self.print_help()
226 | self.exit(2)
227 |
228 | return self._parse_known_args2(arg_strings, *args, **kw)
229 |
230 | parser._parse_known_args2 = parser._parse_known_args
231 | parser._parse_known_args = types.MethodType(_parse_known_args, parser)
232 |
233 | def get_options(self):
234 | return self._options
235 |
236 | def add_command(self, *args, **kwargs):
237 | """
238 | Adds command to registry.
239 |
240 | :param command: Command instance
241 | :param name: Name of the command (optional)
242 | :param namespace: Namespace of the command (optional; pass as kwarg)
243 | """
244 |
245 | if len(args) == 1:
246 | command = args[0]
247 | name = None
248 |
249 | else:
250 | name, command = args
251 |
252 | if name is None:
253 | if hasattr(command, 'name'):
254 | name = command.name
255 |
256 | else:
257 | name = type(command).__name__.lower()
258 | name = re.sub(r'command$', '', name)
259 |
260 | if isinstance(command, Manager):
261 | command.parent = self
262 |
263 | if isinstance(command, type):
264 | command = command()
265 |
266 | namespace = kwargs.get('namespace')
267 | if not namespace:
268 | namespace = getattr(command, 'namespace', None)
269 |
270 | if namespace:
271 | if namespace not in self._commands:
272 | self.add_command(namespace, Manager())
273 |
274 | self._commands[namespace]._commands[name] = command
275 |
276 | else:
277 | self._commands[name] = command
278 |
279 | def command(self, func):
280 | """
281 | Decorator to add a command function to the registry.
282 |
283 | :param func: command function.Arguments depend on the
284 | options.
285 |
286 | """
287 |
288 | command = Command(func)
289 | self.add_command(func.__name__, command)
290 |
291 | return func
292 |
293 | def option(self, *args, **kwargs):
294 | """
295 | Decorator to add an option to a function. Automatically registers the
296 | function - do not use together with ``@command``. You can add as many
297 | ``@option`` calls as you like, for example::
298 |
299 | @option('-n', '--name', dest='name')
300 | @option('-u', '--url', dest='url')
301 | def hello(name, url):
302 | print "hello", name, url
303 |
304 | Takes the same arguments as the ``Option`` constructor.
305 | """
306 |
307 | option = Option(*args, **kwargs)
308 |
309 | def decorate(func):
310 | name = func.__name__
311 |
312 | if name not in self._commands:
313 |
314 | command = Command()
315 | command.run = func
316 | command.__doc__ = func.__doc__
317 | command.option_list = []
318 |
319 | self.add_command(name, command)
320 |
321 | self._commands[name].option_list.append(option)
322 | return func
323 | return decorate
324 |
325 | def shell(self, func):
326 | """
327 | Decorator that wraps function in shell command. This is equivalent to::
328 |
329 | def _make_context(app):
330 | return dict(app=app)
331 |
332 | manager.add_command("shell", Shell(make_context=_make_context))
333 |
334 | The decorated function should take a single "app" argument, and return
335 | a dict.
336 |
337 | For more sophisticated usage use the Shell class.
338 | """
339 |
340 | self.add_command('shell', Shell(make_context=func))
341 |
342 | return func
343 |
344 | def set_defaults(self):
345 | if self.with_default_commands is None:
346 | self.with_default_commands = self.parent is None
347 | if self.with_default_commands:
348 | self.add_default_commands()
349 | self.with_default_commands = False
350 |
351 | def handle(self, prog, args=None):
352 | self.set_defaults()
353 | app_parser = self.create_parser(prog)
354 |
355 | args = list(args or [])
356 | app_namespace, remaining_args = app_parser.parse_known_args(args)
357 |
358 | # get the handle function and remove it from parsed options
359 | kwargs = app_namespace.__dict__
360 | func_stack = kwargs.pop('func_stack', None)
361 | if not func_stack:
362 | app_parser.error('too few arguments')
363 |
364 | last_func = func_stack[-1]
365 | if remaining_args and not getattr(last_func, 'capture_all_args', False):
366 | app_parser.error('too many arguments')
367 |
368 | args = []
369 | for handle in func_stack:
370 |
371 | # get only safe config options
372 | config_keys = [action.dest for action in handle.parser._actions
373 | if handle is last_func or action.__class__ in safe_actions]
374 |
375 | # pass only safe app config keys
376 | config = dict((k, v) for k, v in iteritems(kwargs)
377 | if k in config_keys)
378 |
379 | # remove application config keys from handle kwargs
380 | kwargs = dict((k, v) for k, v in iteritems(kwargs)
381 | if k not in config_keys)
382 |
383 | if handle is last_func and getattr(last_func, 'capture_all_args', False):
384 | args.append(remaining_args)
385 | try:
386 | res = handle(*args, **config)
387 | except TypeError as err:
388 | err.args = ("{0}: {1}".format(handle,str(err)),)
389 | raise
390 |
391 | args = [res]
392 |
393 | assert not kwargs
394 | return res
395 |
396 | def run(self, commands=None, default_command=None):
397 | """
398 | Prepares manager to receive command line input. Usually run
399 | inside "if __name__ == "__main__" block in a Python script.
400 |
401 | :param commands: optional dict of commands. Appended to any commands
402 | added using add_command().
403 |
404 | :param default_command: name of default command to run if no
405 | arguments passed.
406 | """
407 |
408 | if commands:
409 | self._commands.update(commands)
410 |
411 | # Make sure all of this is Unicode
412 | argv = list(text_type(arg) for arg in sys.argv)
413 | if default_command is not None and len(argv) == 1:
414 | argv.append(default_command)
415 |
416 | try:
417 | result = self.handle(argv[0], argv[1:])
418 | except SystemExit as e:
419 | result = e.code
420 |
421 | sys.exit(result or 0)
422 |
--------------------------------------------------------------------------------
/flask_script/commands.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import absolute_import,print_function
3 |
4 | import os
5 | import sys
6 | import code
7 | import warnings
8 | import string
9 | import inspect
10 |
11 | import argparse
12 |
13 | from flask import _request_ctx_stack
14 |
15 | from .cli import prompt, prompt_pass, prompt_bool, prompt_choices
16 | from ._compat import izip, text_type
17 |
18 |
19 | class InvalidCommand(Exception):
20 | """\
21 | This is a generic error for "bad" commands.
22 | It is not used in Flask-Script itself, but you should throw
23 | this error (or one derived from it) in your command handlers,
24 | and your main code should display this error's message without
25 | a stack trace.
26 |
27 | This way, we maintain interoperability if some other plug-in code
28 | supplies Flask-Script hooks.
29 | """
30 | pass
31 |
32 | class Group(object):
33 | """
34 | Stores argument groups and mutually exclusive groups for
35 | `ArgumentParser.add_argument_group `
36 | or `ArgumentParser.add_mutually_exclusive_group `.
37 |
38 | Note: The title and description params cannot be used with the exclusive
39 | or required params.
40 |
41 | :param options: A list of Option classes to add to this group
42 | :param title: A string to use as the title of the argument group
43 | :param description: A string to use as the description of the argument
44 | group
45 | :param exclusive: A boolean indicating if this is an argument group or a
46 | mutually exclusive group
47 | :param required: A boolean indicating if this mutually exclusive group
48 | must have an option selected
49 | """
50 |
51 | def __init__(self, *options, **kwargs):
52 | self.option_list = options
53 |
54 | self.title = kwargs.pop("title", None)
55 | self.description = kwargs.pop("description", None)
56 | self.exclusive = kwargs.pop("exclusive", None)
57 | self.required = kwargs.pop("required", None)
58 |
59 | if ((self.title or self.description) and
60 | (self.required or self.exclusive)):
61 | raise TypeError("title and/or description cannot be used with "
62 | "required and/or exclusive.")
63 |
64 | super(Group, self).__init__(**kwargs)
65 |
66 | def get_options(self):
67 | """
68 | By default, returns self.option_list. Override if you
69 | need to do instance-specific configuration.
70 | """
71 | return self.option_list
72 |
73 |
74 | class Option(object):
75 | """
76 | Stores positional and optional arguments for `ArgumentParser.add_argument
77 | `_.
78 |
79 | :param name_or_flags: Either a name or a list of option strings,
80 | e.g. foo or -f, --foo
81 | :param action: The basic type of action to be taken when this argument
82 | is encountered at the command-line.
83 | :param nargs: The number of command-line arguments that should be consumed.
84 | :param const: A constant value required by some action and nargs selections.
85 | :param default: The value produced if the argument is absent from
86 | the command-line.
87 | :param type: The type to which the command-line arg should be converted.
88 | :param choices: A container of the allowable values for the argument.
89 | :param required: Whether or not the command-line option may be omitted
90 | (optionals only).
91 | :param help: A brief description of what the argument does.
92 | :param metavar: A name for the argument in usage messages.
93 | :param dest: The name of the attribute to be added to the object
94 | returned by parse_args().
95 | """
96 |
97 | def __init__(self, *args, **kwargs):
98 | self.args = args
99 | self.kwargs = kwargs
100 |
101 |
102 | class Command(object):
103 | """
104 | Base class for creating commands.
105 |
106 | :param func: Initialize this command by introspecting the function.
107 | """
108 |
109 | option_list = ()
110 | help_args = None
111 |
112 | def __init__(self, func=None):
113 | if func is None:
114 | if not self.option_list:
115 | self.option_list = []
116 | return
117 |
118 | args, varargs, keywords, defaults = inspect.getargspec(func)
119 | if inspect.ismethod(func):
120 | args = args[1:]
121 |
122 | options = []
123 |
124 | # first arg is always "app" : ignore
125 |
126 | defaults = defaults or []
127 | kwargs = dict(izip(*[reversed(l) for l in (args, defaults)]))
128 |
129 | for arg in args:
130 |
131 | if arg in kwargs:
132 |
133 | default = kwargs[arg]
134 |
135 | if isinstance(default, bool):
136 | options.append(Option('-%s' % arg[0],
137 | '--%s' % arg,
138 | action="store_true",
139 | dest=arg,
140 | required=False,
141 | default=default))
142 | else:
143 | options.append(Option('-%s' % arg[0],
144 | '--%s' % arg,
145 | dest=arg,
146 | type=text_type,
147 | required=False,
148 | default=default))
149 |
150 | else:
151 | options.append(Option(arg, type=text_type))
152 |
153 | self.run = func
154 | self.__doc__ = func.__doc__
155 | self.option_list = options
156 |
157 | @property
158 | def description(self):
159 | description = self.__doc__ or ''
160 | return description.strip()
161 |
162 | def add_option(self, option):
163 | """
164 | Adds Option to option list.
165 | """
166 | self.option_list.append(option)
167 |
168 | def get_options(self):
169 | """
170 | By default, returns self.option_list. Override if you
171 | need to do instance-specific configuration.
172 | """
173 | return self.option_list
174 |
175 | def create_parser(self, *args, **kwargs):
176 | func_stack = kwargs.pop('func_stack',())
177 | parent = kwargs.pop('parent',None)
178 | parser = argparse.ArgumentParser(*args, add_help=False, **kwargs)
179 | help_args = self.help_args
180 | while help_args is None and parent is not None:
181 | help_args = parent.help_args
182 | parent = getattr(parent,'parent',None)
183 |
184 | if help_args:
185 | from flask_script import add_help
186 | add_help(parser,help_args)
187 |
188 | for option in self.get_options():
189 | if isinstance(option, Group):
190 | if option.exclusive:
191 | group = parser.add_mutually_exclusive_group(
192 | required=option.required,
193 | )
194 | else:
195 | group = parser.add_argument_group(
196 | title=option.title,
197 | description=option.description,
198 | )
199 | for opt in option.get_options():
200 | group.add_argument(*opt.args, **opt.kwargs)
201 | else:
202 | parser.add_argument(*option.args, **option.kwargs)
203 |
204 | parser.set_defaults(func_stack=func_stack+(self,))
205 |
206 | self.parser = parser
207 | self.parent = parent
208 | return parser
209 |
210 | def __call__(self, app=None, *args, **kwargs):
211 | """
212 | Handles the command with the given app.
213 | Default behaviour is to call ``self.run`` within a test request context.
214 | """
215 | with app.test_request_context():
216 | return self.run(*args, **kwargs)
217 |
218 | def run(self):
219 | """
220 | Runs a command. This must be implemented by the subclass. Should take
221 | arguments as configured by the Command options.
222 | """
223 | raise NotImplementedError
224 |
225 | class Shell(Command):
226 | """
227 | Runs a Python shell inside Flask application context.
228 |
229 | :param banner: banner appearing at top of shell when started
230 | :param make_context: a callable returning a dict of variables
231 | used in the shell namespace. By default
232 | returns a dict consisting of just the app.
233 | :param use_ptipython: use PtIPython shell if available, ignore if not.
234 | The PtIPython shell can be turned off in command
235 | line by passing the **--no-ptipython** flag.
236 | :param use_ptpython: use PtPython shell if available, ignore if not.
237 | The PtPython shell can be turned off in command
238 | line by passing the **--no-ptpython** flag.
239 | :param use_bpython: use BPython shell if available, ignore if not.
240 | The BPython shell can be turned off in command
241 | line by passing the **--no-bpython** flag.
242 | :param use_ipython: use IPython shell if available, ignore if not.
243 | The IPython shell can be turned off in command
244 | line by passing the **--no-ipython** flag.
245 | """
246 |
247 | banner = ''
248 |
249 | help = description = 'Runs a Python shell inside Flask application context.'
250 |
251 | def __init__(self, banner=None, make_context=None, use_ipython=True,
252 | use_bpython=True, use_ptipython=True, use_ptpython=True):
253 |
254 | self.banner = banner or self.banner
255 | self.use_ipython = use_ipython
256 | self.use_bpython = use_bpython
257 | self.use_ptipython = use_ptipython
258 | self.use_ptpython = use_ptpython
259 |
260 | if make_context is None:
261 | make_context = lambda: dict(app=_request_ctx_stack.top.app)
262 |
263 | self.make_context = make_context
264 |
265 | def get_options(self):
266 | return (
267 | Option('--no-ipython',
268 | action="store_true",
269 | dest='no_ipython',
270 | default=not(self.use_ipython),
271 | help="Do not use the IPython shell"),
272 | Option('--no-bpython',
273 | action="store_true",
274 | dest='no_bpython',
275 | default=not(self.use_bpython),
276 | help="Do not use the BPython shell"),
277 | Option('--no-ptipython',
278 | action="store_true",
279 | dest='no_ptipython',
280 | default=not(self.use_ptipython),
281 | help="Do not use the PtIPython shell"),
282 | Option('--no-ptpython',
283 | action="store_true",
284 | dest='no_ptpython',
285 | default=not(self.use_ptpython),
286 | help="Do not use the PtPython shell"),
287 | )
288 |
289 | def get_context(self):
290 | """
291 | Returns a dict of context variables added to the shell namespace.
292 | """
293 | return self.make_context()
294 |
295 | def run(self, no_ipython, no_bpython, no_ptipython, no_ptpython):
296 | """
297 | Runs the shell.
298 | If no_ptipython is False or use_ptipython is True, then a PtIPython shell is run (if installed).
299 | If no_ptpython is False or use_ptpython is True, then a PtPython shell is run (if installed).
300 | If no_bpython is False or use_bpython is True, then a BPython shell is run (if installed).
301 | If no_ipython is False or use_python is True then a IPython shell is run (if installed).
302 | """
303 |
304 | context = self.get_context()
305 |
306 | if not no_ptipython:
307 | # Try PtIPython
308 | try:
309 | from ptpython.ipython import embed
310 | history_filename = os.path.expanduser('~/.ptpython_history')
311 | embed(banner1=self.banner, user_ns=context, history_filename=history_filename)
312 | return
313 | except ImportError:
314 | pass
315 |
316 | if not no_ptpython:
317 | # Try PtPython
318 | try:
319 | from ptpython.repl import embed
320 | history_filename = os.path.expanduser('~/.ptpython_history')
321 | embed(globals=context, history_filename=history_filename)
322 | return
323 | except ImportError:
324 | pass
325 |
326 | if not no_bpython:
327 | # Try BPython
328 | try:
329 | from bpython import embed
330 | embed(banner=self.banner, locals_=context)
331 | return
332 | except ImportError:
333 | pass
334 |
335 | if not no_ipython:
336 | # Try IPython
337 | try:
338 | from IPython import embed
339 | embed(banner1=self.banner, user_ns=context)
340 | return
341 | except ImportError:
342 | pass
343 |
344 | # Use basic python shell
345 | code.interact(self.banner, local=context)
346 |
347 |
348 | class Server(Command):
349 | """
350 | Runs the Flask development server i.e. app.run()
351 |
352 | :param host: server host
353 | :param port: server port
354 | :param use_debugger: Flag whether to default to using the Werkzeug debugger.
355 | This can be overriden in the command line
356 | by passing the **-d** or **-D** flag.
357 | Defaults to False, for security.
358 |
359 | :param use_reloader: Flag whether to use the auto-reloader.
360 | Default to True when debugging.
361 | This can be overriden in the command line by
362 | passing the **-r**/**-R** flag.
363 | :param threaded: should the process handle each request in a separate
364 | thread?
365 | :param processes: number of processes to spawn
366 | :param passthrough_errors: disable the error catching. This means that the server will die on errors but it can be useful to hook debuggers in (pdb etc.)
367 | :param ssl_crt: path to ssl certificate file
368 | :param ssl_key: path to ssl key file
369 | :param options: :func:`werkzeug.run_simple` options.
370 | """
371 |
372 | help = description = 'Runs the Flask development server i.e. app.run()'
373 |
374 | def __init__(self, host='127.0.0.1', port=5000, use_debugger=None,
375 | use_reloader=None, threaded=False, processes=1,
376 | passthrough_errors=False, ssl_crt=None, ssl_key=None, **options):
377 |
378 | self.port = port
379 | self.host = host
380 | self.use_debugger = use_debugger
381 | self.use_reloader = use_reloader if use_reloader is not None else use_debugger
382 | self.server_options = options
383 | self.threaded = threaded
384 | self.processes = processes
385 | self.passthrough_errors = passthrough_errors
386 | self.ssl_crt = ssl_crt
387 | self.ssl_key = ssl_key
388 |
389 | def get_options(self):
390 |
391 | options = (
392 | Option('-h', '--host',
393 | dest='host',
394 | default=self.host),
395 |
396 | Option('-p', '--port',
397 | dest='port',
398 | type=int,
399 | default=self.port),
400 |
401 | Option('--threaded',
402 | dest='threaded',
403 | action='store_true',
404 | default=self.threaded),
405 |
406 | Option('--processes',
407 | dest='processes',
408 | type=int,
409 | default=self.processes),
410 |
411 | Option('--passthrough-errors',
412 | action='store_true',
413 | dest='passthrough_errors',
414 | default=self.passthrough_errors),
415 |
416 | Option('-d', '--debug',
417 | action='store_true',
418 | dest='use_debugger',
419 | help='enable the Werkzeug debugger (DO NOT use in production code)',
420 | default=self.use_debugger),
421 | Option('-D', '--no-debug',
422 | action='store_false',
423 | dest='use_debugger',
424 | help='disable the Werkzeug debugger',
425 | default=self.use_debugger),
426 |
427 | Option('-r', '--reload',
428 | action='store_true',
429 | dest='use_reloader',
430 | help='monitor Python files for changes (not 100%% safe for production use)',
431 | default=self.use_reloader),
432 | Option('-R', '--no-reload',
433 | action='store_false',
434 | dest='use_reloader',
435 | help='do not monitor Python files for changes',
436 | default=self.use_reloader),
437 | Option('--ssl-crt',
438 | dest='ssl_crt',
439 | type=str,
440 | help='Path to ssl certificate',
441 | default=self.ssl_crt),
442 | Option('--ssl-key',
443 | dest='ssl_key',
444 | type=str,
445 | help='Path to ssl key',
446 | default=self.ssl_key),
447 | )
448 |
449 | return options
450 |
451 | def __call__(self, app, host, port, use_debugger, use_reloader,
452 | threaded, processes, passthrough_errors, ssl_crt, ssl_key):
453 | # we don't need to run the server in request context
454 | # so just run it directly
455 |
456 | if use_debugger is None:
457 | use_debugger = app.debug
458 | if use_debugger is None:
459 | use_debugger = True
460 | if sys.stderr.isatty():
461 | print("Debugging is on. DANGER: Do not allow random users to connect to this server.", file=sys.stderr)
462 | if use_reloader is None:
463 | use_reloader = use_debugger
464 |
465 | if None in [ssl_crt, ssl_key]:
466 | ssl_context = None
467 | else:
468 | ssl_context = (ssl_crt, ssl_key)
469 |
470 | app.run(host=host,
471 | port=port,
472 | debug=use_debugger,
473 | use_debugger=use_debugger,
474 | use_reloader=use_reloader,
475 | threaded=threaded,
476 | processes=processes,
477 | passthrough_errors=passthrough_errors,
478 | ssl_context=ssl_context,
479 | **self.server_options)
480 |
481 |
482 | class Clean(Command):
483 | "Remove *.pyc and *.pyo files recursively starting at current directory"
484 | def run(self):
485 | for dirpath, dirnames, filenames in os.walk('.'):
486 | for filename in filenames:
487 | if filename.endswith('.pyc') or filename.endswith('.pyo'):
488 | full_pathname = os.path.join(dirpath, filename)
489 | print('Removing %s' % full_pathname)
490 | os.remove(full_pathname)
491 |
492 |
493 | class ShowUrls(Command):
494 | """
495 | Displays all of the url matching routes for the project
496 | """
497 | def __init__(self, order='rule'):
498 | self.order = order
499 |
500 | def get_options(self):
501 | return (
502 | Option('url',
503 | nargs='?',
504 | help='Url to test (ex. /static/image.png)'),
505 | Option('--order',
506 | dest='order',
507 | default=self.order,
508 | help='Property on Rule to order by (default: %s)' % self.order)
509 | )
510 |
511 | def run(self, url, order):
512 | from flask import current_app
513 | from werkzeug.exceptions import NotFound, MethodNotAllowed
514 |
515 | rows = []
516 | column_length = 0
517 | column_headers = ('Rule', 'Endpoint', 'Arguments')
518 |
519 | if url:
520 | try:
521 | rule, arguments = current_app.url_map \
522 | .bind('localhost') \
523 | .match(url, return_rule=True)
524 | rows.append((rule.rule, rule.endpoint, arguments))
525 | column_length = 3
526 | except (NotFound, MethodNotAllowed) as e:
527 | rows.append(("<%s>" % e, None, None))
528 | column_length = 1
529 | else:
530 | rules = sorted(current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order))
531 | for rule in rules:
532 | rows.append((rule.rule, rule.endpoint, None))
533 | column_length = 2
534 |
535 | str_template = ''
536 | table_width = 0
537 |
538 | if column_length >= 1:
539 | max_rule_length = max(len(r[0]) for r in rows)
540 | max_rule_length = max_rule_length if max_rule_length > 4 else 4
541 | str_template += '%-' + str(max_rule_length) + 's'
542 | table_width += max_rule_length
543 |
544 | if column_length >= 2:
545 | max_endpoint_length = max(len(str(r[1])) for r in rows)
546 | # max_endpoint_length = max(rows, key=len)
547 | max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8
548 | str_template += ' %-' + str(max_endpoint_length) + 's'
549 | table_width += 2 + max_endpoint_length
550 |
551 | if column_length >= 3:
552 | max_arguments_length = max(len(str(r[2])) for r in rows)
553 | max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9
554 | str_template += ' %-' + str(max_arguments_length) + 's'
555 | table_width += 2 + max_arguments_length
556 |
557 | print(str_template % (column_headers[:column_length]))
558 | print('-' * table_width)
559 |
560 | for row in rows:
561 | print(str_template % row[:column_length])
562 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Flask-Script
2 | ======================================
3 |
4 | .. module:: Flask-Script
5 |
6 |
7 | .. warning::
8 |
9 | While the maintainers are willing to merge PR's, they are not actively developing features. As of Flask 0.11, Flask includes `a built-in CLI tool`__, and that may fit your needs better.
10 | :+1: 2
11 |
12 |
13 | __ http://flask.pocoo.org/docs/latest/cli/
14 |
15 |
16 | The **Flask-Script** extension provides support for writing external scripts in Flask. This includes running a development server, a customised Python shell, scripts to set up your database, cronjobs, and other command-line tasks that belong outside the web application itself.
17 |
18 | **Flask-Script** works in a similar way to Flask itself. You define and add commands that can be called from the command line to a ``Manager`` instance::
19 |
20 | # manage.py
21 |
22 | from flask_script import Manager
23 |
24 | from myapp import app
25 |
26 | manager = Manager(app)
27 |
28 | @manager.command
29 | def hello():
30 | print "hello"
31 |
32 | if __name__ == "__main__":
33 | manager.run()
34 |
35 | Once you define your script commands, you can then run them on the command line::
36 |
37 | python manage.py hello
38 | > hello
39 |
40 | Source code and issue tracking at `GitHub`_.
41 |
42 | Installing Flask-Script
43 | ------------------------
44 |
45 | Install with **pip** and **easy_install**::
46 |
47 | pip install Flask-Script
48 |
49 | or download the latest version from version control::
50 |
51 | git clone https://github.com/smurfix/flask-script.git
52 | cd flask-script
53 | python setup.py develop
54 |
55 | If you are using **virtualenv**, it is assumed that you are installing **Flask-Script**
56 | in the same virtualenv as your Flask application(s).
57 |
58 | Creating and running commands
59 | -----------------------------
60 |
61 | The first step is to create a Python module to run your script commands in. You can call it
62 | anything you like, for our examples we'll call it ``manage.py``.
63 |
64 | You don't have to place all your commands in the same file; for example, in a larger project
65 | with lots of commands you might want to split them into a number of files with related commands.
66 |
67 | In your ``manage.py`` file you have to create a ``Manager`` instance. The ``Manager`` class
68 | keeps track of all the commands and handles how they are called from the command line::
69 |
70 | from flask_script import Manager
71 |
72 | app = Flask(__name__)
73 | # configure your app
74 |
75 | manager = Manager(app)
76 |
77 | if __name__ == "__main__":
78 | manager.run()
79 |
80 | Calling ``manager.run()`` prepares your ``Manager`` instance to receive input from the command line.
81 |
82 | The ``Manager`` requires a single argument, a **Flask** instance. This may also be a function or other callable
83 | that returns a **Flask** instance instead, if you want to use a factory pattern.
84 |
85 | The next step is to create and add your commands. There are three methods for creating commands:
86 |
87 | * subclassing the ``Command`` class
88 | * using the ``@command`` decorator
89 | * using the ``@option`` decorator
90 |
91 | To take a very simple example, we want to create a ``hello`` command that just prints out "hello world". It
92 | doesn't take any arguments so is very straightforward::
93 |
94 | from flask_script import Command
95 |
96 | class Hello(Command):
97 | "prints hello world"
98 |
99 | def run(self):
100 | print "hello world"
101 |
102 | Now the command needs to be added to our ``Manager`` instance, like the one created above::
103 |
104 | manager.add_command('hello', Hello())
105 |
106 | This of course needs to be called before ``manager.run``. Now in our command line::
107 |
108 | python manage.py hello
109 | > hello world
110 |
111 | You can also pass the ``Command`` instance in a dict to ``manager.run()``::
112 |
113 | manager.run({'hello' : Hello()})
114 |
115 | The ``Command`` class must define a ``run`` method. The positional and optional arguments
116 | depend on the command-line arguments you pass to the ``Command`` (see below).
117 |
118 | To get a list of available commands and their descriptions, just run with no command::
119 |
120 | python manage.py
121 |
122 | To get help text for a particular command::
123 |
124 | python manage.py runserver -?
125 |
126 | This will print usage plus the docstring of the ``Command``.
127 |
128 | This first method is probably the most flexible, but it's also the most verbose. For simpler commands you can use
129 | the ``@command`` decorator, which belongs to the ``Manager`` instance::
130 |
131 | @manager.command
132 | def hello():
133 | "Just say hello"
134 | print "hello"
135 |
136 | Commands created this way are run in exactly the same way as those created with the ``Command`` class::
137 |
138 | python manage.py hello
139 | > hello
140 |
141 | As with the ``Command`` class, the docstring you use for the function will appear when you run with the ``-?`` or ``--help`` option::
142 |
143 | python manage.py -?
144 | > Just say hello
145 |
146 | Finally, the ``@option`` decorator, again belonging to ``Manager`` can be used when you want more sophisticated
147 | control over your commands::
148 |
149 | @manager.option('-n', '--name', help='Your name')
150 | def hello(name):
151 | print "hello", name
152 |
153 | The ``@option`` decorator is explained in more detail below.
154 |
155 | *New in version 2.0*
156 |
157 | Help was previously available with ``--help`` and ``-h``. This had a couple
158 | of less-than-ideal consequences, among them the inability to use ``-h`` as
159 | a shortcut for ``--host`` or similar options.
160 |
161 | *New in version 2.0.2*
162 |
163 | If you want to restore the original meaning of ``-h``, set your manager's
164 | ``help_args`` attribute to a list of argument strings you want to be
165 | considered helpful::
166 |
167 | manager = Manager()
168 | manager.help_args = ('-h', '-?', '--help')
169 |
170 | You can override this list in sub-commands and -managers::
171 |
172 | def talker(host='localhost'):
173 | pass
174 | ccmd = ConnectCmd(talker)
175 | ccmd.help_args = ('-?', '--help')
176 | manager.add_command("connect", ccmd)
177 | manager.run()
178 |
179 | so that ``manager -h`` prints help, while ``manager connect -h fubar.example.com``
180 | connects to a remote host.
181 |
182 | Adding arguments to commands
183 | ----------------------------
184 |
185 | Most commands take a number of named or positional arguments that you pass in the command line.
186 |
187 | Taking the above examples, rather than just print "hello world" we would like to be able to print some
188 | arbitrary name, like this::
189 |
190 | python manage.py hello --name=Joe
191 | hello Joe
192 |
193 | or alternatively::
194 |
195 | python manage.py hello -n Joe
196 |
197 | To facilitate this you use the ``option_list`` attribute of the ``Command`` class::
198 |
199 | from flask_script import Command, Manager, Option
200 |
201 | class Hello(Command):
202 |
203 | option_list = (
204 | Option('--name', '-n', dest='name'),
205 | )
206 |
207 | def run(self, name):
208 | print "hello %s" % name
209 |
210 | Positional and optional arguments are stored as ``Option`` instances - see the :ref:`api` below for details.
211 |
212 | Alternatively, you can define a ``get_options`` method for your ``Command`` class. This is useful if you want to be able
213 | to return options at runtime based on for example per-instance attributes::
214 |
215 | class Hello(Command):
216 |
217 | def __init__(self, default_name='Joe'):
218 | self.default_name=default_name
219 |
220 | def get_options(self):
221 | return [
222 | Option('-n', '--name', dest='name', default=self.default_name),
223 | ]
224 |
225 | def run(self, name):
226 | print "hello", name
227 |
228 | If you are using the ``@command`` decorator, it's much easier - the options are extracted automatically from your function arguments. This is an example of a positional argument::
229 |
230 | @manager.command
231 | def hello(name):
232 | print "hello", name
233 |
234 | You then invoke this on the command line like so::
235 |
236 | > python manage.py hello Joe
237 | hello Joe
238 |
239 | Or you can do optional arguments::
240 |
241 | @manager.command
242 | def hello(name="Fred")
243 | print "hello", name
244 |
245 | These can be called like so::
246 |
247 | > python manage.py hello --name=Joe
248 | hello Joe
249 |
250 | alternatively::
251 |
252 | > python manage.py hello -n Joe
253 | hello Joe
254 |
255 | The short form ``-n`` is formed from the first letter of the argument, so "name" > "-n". Therefore it's a good idea for your
256 | optional argument variable names to begin with different letters.
257 |
258 | *New in version 2.0*
259 |
260 | Note also that if your optional argument is a boolean, for example::
261 |
262 | @manager.command
263 | def verify(verified=False):
264 | """
265 | Checks if verified
266 | """
267 | print "VERIFIED?", "YES" if verified else "NO"
268 |
269 | You can just call it like this::
270 |
271 | > python manage.py verify
272 | VERIFIED? NO
273 |
274 | > python manage.py verify -v
275 | VERIFIED? YES
276 |
277 | > python manage.py verify --verified
278 | VERIFIED? YES
279 |
280 | The ``@command`` decorator is fine for simple operations, but often you need the flexibility. For more sophisticated options it's better to use the ``@option`` decorator::
281 |
282 | @manager.option('-n', '--name', dest='name', default='joe')
283 | def hello(name):
284 | print "hello", name
285 |
286 | You can add as many options as you want::
287 |
288 | @manager.option('-n', '--name', dest='name', default='joe')
289 | @manager.option('-u', '--url', dest='url', default=None)
290 | def hello(name, url):
291 | if url is None:
292 | print "hello", name
293 | else:
294 | print "hello", name, "from", url
295 |
296 | This can be called like so::
297 |
298 | > python manage.py hello -n Joe -u reddit.com
299 | hello Joe from reddit.com
300 |
301 | or alternatively::
302 |
303 | > python manage.py hello --name=Joe --url=reddit.com
304 | hello Joe from reddit.com
305 |
306 | Adding options to the manager
307 | -----------------------------
308 |
309 | Options can also be passed to the ``Manager`` instance. This is allows you to set up options that are passed to the application rather
310 | than a single command. For example, you might want to have a flag to set the configuration file for your application. Suppose you create
311 | your application with a factory function::
312 |
313 | def create_app(config=None):
314 |
315 | app = Flask(__name__)
316 | if config is not None:
317 | app.config.from_pyfile(config)
318 | # configure your app...
319 | return app
320 |
321 | You want to be able to define the ``config`` argument on the command line - for example, if you have a command to set up your database, you
322 | most certainly want to use different configuration files for production and development.
323 |
324 | In order to pass that ``config`` argument, use the ``add_option()`` method of your ``Manager`` instance. It takes the same arguments
325 | as ``Option``::
326 |
327 | manager.add_option('-c', '--config', dest='config', required=False)
328 |
329 | As with any other **Flask-Script** configuration you can call this anywhere in your script module, but it must be called before your ``manager.run()`` call.
330 |
331 | Suppose you have this command::
332 |
333 | @manager.command
334 | def hello(name):
335 | uppercase = app.config.get('USE_UPPERCASE', False)
336 | if uppercase:
337 | name = name.upper()
338 | print "hello", name
339 |
340 | You can now run the following::
341 |
342 | > python manage.py -c dev.cfg hello joe
343 | hello JOE
344 |
345 | Assuming the ``USE_UPPERCASE`` setting is **True** in your dev.cfg file.
346 |
347 | Notice also that the "config" option is **not** passed to the command. In
348 | fact, this usage::
349 |
350 | > python manage.py hello joe -c dev.cfg
351 |
352 | will show an error message because the ``-c`` option does not belong to the
353 | ``hello`` command.
354 |
355 | You can attach same-named options to different levels; this allows you to
356 | add an option to your app setup code without checking whether it conflicts with
357 | a command::
358 |
359 | @manager.option('-n', '--name', dest='name', default='joe')
360 | @manager.option('-c', '--clue', dest='clue', default='clue')
361 | def hello(name, clue):
362 | uppercase = app.config.get('USE_UPPERCASE', False)
363 | if uppercase:
364 | name = name.upper()
365 | clue = clue.upper()
366 | print "hello {0}, get a {1}!".format(name, clue)
367 |
368 | > python manage.py -c dev.cfg hello -c cookie -n frank
369 | hello FRANK, get a COOKIE!
370 |
371 | Note that the destination variables (command arguments, corresponding to
372 | ``dest`` values) must still be different; this is a limitation of Python's
373 | argument parser.
374 |
375 | In order for manager options to work you must pass a factory function, rather than a Flask instance, to your
376 | ``Manager`` constructor. A simple but complete example is available in `this gist `_.
377 |
378 | *New in version 2.0*
379 |
380 | Before version 2, options and command names could be interspersed freely.
381 | The author decided to discontinue this practice for a number of reasons;
382 | the problem with the most impact was that it was not possible to do::
383 |
384 | > python manage.py connect -d DEST
385 | > python manage.py import -d DIR
386 |
387 | as these options collided.
388 |
389 | Getting user input
390 | ------------------
391 |
392 | **Flask-Script** comes with a set of helper functions for grabbing user input from the command line. For example::
393 |
394 | from flask_script import Manager, prompt_bool
395 |
396 | from myapp import app
397 | from myapp.models import db
398 |
399 | manager = Manager(app)
400 |
401 | @manager.command
402 | def dropdb():
403 | if prompt_bool(
404 | "Are you sure you want to lose all your data"):
405 | db.drop_all()
406 |
407 | It then runs like this::
408 |
409 | > python manage.py dropdb
410 | Are you sure you want to lose all your data ? [N]
411 |
412 | See the :ref:`api` below for details on the various prompt functions.
413 |
414 | Default commands
415 | ----------------
416 |
417 | runserver
418 | +++++++++
419 |
420 | **Flask-Script** has a couple of ready commands you can add and customise: ``Server`` and ``Shell``.
421 |
422 | The ``Server`` command runs the **Flask** development server.::
423 |
424 | from flask_script import Server, Manager
425 | from myapp import create_app
426 |
427 | manager = Manager(create_app)
428 | manager.add_command("runserver", Server())
429 |
430 | if __name__ == "__main__":
431 | manager.run()
432 |
433 | and then run the command::
434 |
435 | python manage.py runserver
436 |
437 | The ``Server`` command has a number of command-line arguments - run ``python manage.py runserver -?`` for details on these. You can redefine the defaults in the constructor::
438 |
439 | server = Server(host="0.0.0.0", port=9000)
440 |
441 | Needless to say the development server is not intended for production use.
442 |
443 | *New in version 2.0.5*
444 |
445 | The most common use-case for ``runserver`` is to run a debug server for
446 | investigating problems. Therefore the default, if it is *not* set in the
447 | configuration file, is to enable debugging and auto-reloading.
448 |
449 | Unfortunately, Flask currently (as of May 2014) defaults to set the DEBUG
450 | configuration parameter to ``False``. Until this is changed, you can
451 | safely add ``DEBUG=None`` to your Flask configuration. Flask-Script's
452 | ``runserver`` will then turn on debugging, but everything else will treat
453 | it as being turned off.
454 |
455 | To prevent misunderstandings -- after all, debug mode is a serious security
456 | hole --, a warning is printed when Flask-Script treats a ``None`` default
457 | value as if it were set to ``True``. You can turn on debugging explicitly
458 | to get rid of this warning.
459 |
460 | shell
461 | +++++
462 |
463 | The ``Shell`` command starts a Python shell. You can pass in a ``make_context`` argument, which must be a ``callable`` returning a ``dict``. By default, this is just a dict returning the your Flask application instance::
464 |
465 | from flask_script import Shell, Manager
466 |
467 | from myapp import app
468 | from myapp import models
469 | from myapp.models import db
470 |
471 | def _make_context():
472 | return dict(app=app, db=db, models=models)
473 |
474 | manager = Manager(create_app)
475 | manager.add_command("shell", Shell(make_context=_make_context))
476 |
477 | This is handy if you want to include a bunch of defaults in your shell to save typing lots of ``import`` statements.
478 |
479 | The ``Shell`` command will use `IPython `_ if it is installed, otherwise it defaults to the standard Python shell. You can disable this behaviour in two ways: by passing the ``use_ipython`` argument to the ``Shell`` constructor, or passing the flag ``--no-ipython`` in the command line::
480 |
481 | shell = Shell(use_ipython=False)
482 |
483 | There is also a ``shell`` decorator which you can use with a context function::
484 |
485 | @manager.shell
486 | def make_shell_context():
487 | return dict(app=app, db=db, models=models)
488 |
489 | This enables a ``shell`` command with the defaults enabled::
490 |
491 | > python manage.py shell
492 |
493 | The default commands ``shell`` and ``runserver`` are included by default, with the default options for these commands. If you wish to
494 | replace them with different commands simply override with ``add_command()`` or the decorators. If you pass ``with_default_commands=False``
495 | to the ``Manager`` constructor these commands will not be loaded::
496 |
497 | manager = Manager(app, with_default_commands=False)
498 |
499 | Sub-Managers
500 | ------------
501 | A Sub-Manager is an instance of ``Manager`` added as a command to another Manager
502 |
503 | To create a submanager::
504 |
505 | def sub_opts(app, **kwargs):
506 | pass
507 | sub_manager = Manager(sub_opts)
508 |
509 | manager = Manager(self.app)
510 | manager.add_command("sub_manager", sub_manager)
511 |
512 | If you attach options to the sub_manager, the ``sub_opts`` procedure will
513 | receive their values. Your application is passed in ``app`` for
514 | convenience.
515 |
516 | If ``sub_opts`` returns a value other than ``None``, this value will replace
517 | the ``app`` value that's passed on. This way, you can implement a
518 | sub-manager which replaces the whole app. One use case is to create a
519 | separate administrative application for improved security::
520 |
521 | def gen_admin(app, **kwargs):
522 | from myweb.admin import MyAdminApp
523 | ## easiest but possibly incomplete way to copy your settings
524 | return MyAdminApp(config=app.config, **kwargs)
525 | sub_manager = Manager(gen_admin)
526 |
527 | manager = Manager(MyApp)
528 | manager.add_command("admin", sub_manager)
529 |
530 | > python manage.py runserver
531 | [ starts your normal server ]
532 | > python manage.py admin runserver
533 | [ starts an administrative server ]
534 |
535 | You can cascade sub-managers, i.e. add one sub-manager to another.
536 |
537 | A sub-manager does not get default commands added to itself (by default)
538 |
539 | *New in version 0.5.0.*
540 |
541 | Note to extension developers
542 | ----------------------------
543 | Extension developers can easily create convenient sub-manager instance within their extensions to make it easy for a user to consume all the available commands of an extension.
544 |
545 | Here is an example how a database extension could provide (ex. database.py)::
546 |
547 | manager = Manager(usage="Perform database operations")
548 |
549 | @manager.command
550 | def drop():
551 | "Drops database tables"
552 | if prompt_bool("Are you sure you want to lose all your data"):
553 | db.drop_all()
554 |
555 |
556 | @manager.command
557 | def create(default_data=True, sample_data=False):
558 | "Creates database tables from sqlalchemy models"
559 | db.create_all()
560 | populate(default_data, sample_data)
561 |
562 |
563 | @manager.command
564 | def recreate(default_data=True, sample_data=False):
565 | "Recreates database tables (same as issuing 'drop' and then 'create')"
566 | drop()
567 | create(default_data, sample_data)
568 |
569 |
570 | @manager.command
571 | def populate(default_data=False, sample_data=False):
572 | "Populate database with default data"
573 | from fixtures import dbfixture
574 |
575 | if default_data:
576 | from fixtures.default_data import all
577 | default_data = dbfixture.data(*all)
578 | default_data.setup()
579 |
580 | if sample_data:
581 | from fixtures.sample_data import all
582 | sample_data = dbfixture.data(*all)
583 | sample_data.setup()
584 |
585 |
586 | Then the user can register the sub-manager to their primary Manager (within manage.py)::
587 |
588 | manager = Manager(app)
589 |
590 | from flask_database import manager as database_manager
591 | manager.add_command("database", database_manager)
592 |
593 | The commands will then be available::
594 |
595 | > python manage.py database
596 |
597 | Please provide a command:
598 |
599 | Perform database operations
600 | create Creates database tables from sqlalchemy models
601 | drop Drops database tables
602 | populate Populate database with default data
603 | recreate Recreates database tables (same as issuing 'drop' and then 'create')
604 |
605 | Error handling
606 | --------------
607 |
608 | Users do not like to see stack traces, but developers want them for bug reports.
609 |
610 | Therefore, ``flask_script.commands`` provides an `InvalidCommand` error
611 | class which is not supposed to print a stack trace when reported.
612 |
613 | In your command handler::
614 |
615 | from flask_script.commands import InvalidCommand
616 |
617 | [… if some command verification fails …]
618 | class MyCommand(Command):
619 | def run(self, foo=None, bar=None):
620 | if foo and bar:
621 | raise InvalidCommand("Options foo and bar are incompatible")
622 |
623 | In your main loop::
624 |
625 | try:
626 | MyManager().run()
627 | except InvalidCommand as err:
628 | print(err, file=sys.stderr)
629 | sys.exit(1)
630 |
631 | This way, you maintain interoperability if some plug-in code supplies
632 | Flask-Script hooks you'd like to use, or vice versa.
633 |
634 | Accessing local proxies
635 | -----------------------
636 |
637 | The ``Manager`` runs the command inside a `Flask test context `_. This means that you can access request-local proxies where appropriate, such as ``current_app``, which may be used by extensions.
638 |
639 | .. _api:
640 |
641 | API
642 | ---
643 |
644 | .. module:: flask_script
645 |
646 | .. autoclass:: Manager
647 | :members: run, add_option, add_command, command, option, shell
648 |
649 | .. autoclass:: Command
650 | :members: run, get_options
651 |
652 | .. autoclass:: Shell
653 |
654 | .. autoclass:: Server
655 |
656 | .. autoclass:: Option
657 |
658 | .. autoclass:: Group
659 |
660 | .. autofunction:: prompt
661 |
662 | .. autofunction:: prompt_bool
663 |
664 | .. autofunction:: prompt_pass
665 |
666 | .. autofunction:: prompt_choices
667 |
668 | .. _Flask: http://flask.pocoo.org
669 | .. _GitHub: http://github.com/smurfix/flask-script
670 |
--------------------------------------------------------------------------------
/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | import sys
5 | import unittest
6 |
7 | from flask import Flask
8 | from flask_script._compat import StringIO, text_type
9 | from flask_script import Command, Manager, Option, prompt, prompt_bool, prompt_choices
10 |
11 | from pytest import raises
12 |
13 |
14 | class Catcher(object):
15 | """Helper decorator to test raw_input."""
16 | ## see: http://stackoverflow.com/questions/13480632/python-stringio-selectively-place-data-into-stdin
17 |
18 | def __init__(self, handler):
19 | self.handler = handler
20 | self.inputs = []
21 |
22 | def __enter__(self):
23 | self.__stdin = sys.stdin
24 | self.__stdout = sys.stdout
25 | sys.stdin = self
26 | sys.stdout = self
27 |
28 | def __exit__(self, type, value, traceback):
29 | sys.stdin = self.__stdin
30 | sys.stdout = self.__stdout
31 |
32 | def write(self, value):
33 | self.__stdout.write(value)
34 | result = self.handler(value)
35 | if result is not None:
36 | self.inputs.append(result)
37 |
38 | def readline(self):
39 | return self.inputs.pop()
40 |
41 | def getvalue(self):
42 | return self.__stdout.getvalue()
43 |
44 | def truncate(self, pos):
45 | return self.__stdout.truncate(pos)
46 |
47 |
48 | def run(command_line, manager_run):
49 | '''
50 | Runs a manager command line, returns exit code
51 | '''
52 | sys.argv = command_line.split()
53 | exit_code = None
54 | try:
55 | manager_run()
56 | except SystemExit as e:
57 | exit_code = e.code
58 |
59 | return exit_code
60 |
61 |
62 | class SimpleCommand(Command):
63 | 'simple command'
64 |
65 | def run(self):
66 | print('OK')
67 |
68 |
69 | class NamedCommand(Command):
70 | 'named command'
71 |
72 | def run(self):
73 | print('OK')
74 |
75 |
76 | class ExplicitNamedCommand(Command):
77 | 'named command'
78 |
79 | name = 'named'
80 |
81 | def run(self):
82 | print('OK')
83 |
84 |
85 | class NamespacedCommand(Command):
86 | 'namespaced command'
87 |
88 | namespace = 'ns'
89 |
90 | def run(self):
91 | print('OK')
92 |
93 |
94 | class CommandWithArgs(Command):
95 | 'command with args'
96 |
97 | option_list = (
98 | Option('name'),
99 | )
100 |
101 | def run(self, name):
102 | print(name)
103 |
104 |
105 | class CommandWithOptionalArg(Command):
106 | 'command with optional arg'
107 |
108 | option_list = (
109 | Option('-n','--name', required=False),
110 | )
111 |
112 | def run(self, name="NotGiven"):
113 | print("OK name="+str(name))
114 |
115 |
116 | class CommandWithOptions(Command):
117 | 'command with options'
118 |
119 | option_list = (
120 | Option('-n', '--name',
121 | help='name to pass in',
122 | dest='name'),
123 | )
124 |
125 | def run(self, name):
126 | print(name)
127 |
128 |
129 | class CommandWithDynamicOptions(Command):
130 | 'command with options'
131 |
132 | def __init__(self, default_name='Joe'):
133 | self.default_name = default_name
134 |
135 | def get_options(self):
136 |
137 | return (
138 | Option('-n', '--name',
139 | help='name to pass in',
140 | dest='name',
141 | default=self.default_name),
142 | )
143 |
144 | def run(self, name):
145 | print(name)
146 |
147 |
148 | class CommandWithCatchAll(Command):
149 | 'command with catch all args'
150 |
151 | capture_all_args = True
152 |
153 | def get_options(self):
154 | return (Option('--foo', dest='foo',
155 | action='store_true'),)
156 |
157 | def run(self, remaining_args, foo):
158 | print(remaining_args)
159 |
160 |
161 | class EmptyContext(object):
162 | def __enter__(self):
163 | pass
164 | def __exit__(self, a,b,c):
165 | pass
166 |
167 | class AppForTesting(object):
168 | def __init__(self, verbose=False):
169 | self.verbose = verbose
170 | def test_request_context(self):
171 | return EmptyContext()
172 | def __call__(self,**kw):
173 | if self.verbose:
174 | print("APP "+" ".join("%s=%s" % (k,v) for k,v in kw.items()))
175 | return self
176 |
177 |
178 | class TestManager:
179 |
180 | def setup(self):
181 |
182 | self.app = AppForTesting()
183 |
184 | def test_with_default_commands(self):
185 |
186 | manager = Manager(self.app)
187 | manager.set_defaults()
188 |
189 | assert 'runserver' in manager._commands
190 | assert 'shell' in manager._commands
191 |
192 | def test_without_default_commands(self):
193 |
194 | manager = Manager(self.app, with_default_commands=False)
195 | manager.set_defaults()
196 |
197 | assert 'runserver' not in manager._commands
198 | assert 'shell' not in manager._commands
199 |
200 | def test_add_command(self):
201 |
202 | manager = Manager(self.app)
203 | manager.add_command('simple', SimpleCommand())
204 |
205 | assert isinstance(manager._commands['simple'], SimpleCommand)
206 |
207 | def test_add_named_command(self):
208 |
209 | manager = Manager(self.app)
210 | manager.add_command(NamedCommand())
211 |
212 | assert 'named' in manager._commands
213 | assert isinstance(manager._commands['named'], NamedCommand)
214 |
215 | def test_add_explicit_named_command(self):
216 |
217 | manager = Manager(self.app)
218 | manager.add_command(ExplicitNamedCommand())
219 |
220 | name = ExplicitNamedCommand.name
221 | assert name in manager._commands
222 | assert isinstance(manager._commands[name], ExplicitNamedCommand)
223 |
224 | def test_add_namespaced_command(self):
225 |
226 | manager = Manager(self.app)
227 | manager.add_command('one', NamespacedCommand())
228 | manager.add_command('two', NamespacedCommand())
229 |
230 | assert 'ns' in manager._commands
231 | assert isinstance(manager._commands['ns'], Manager)
232 | ns = manager._commands['ns']
233 | assert isinstance(ns._commands['one'], NamespacedCommand)
234 | assert isinstance(ns._commands['two'], NamespacedCommand)
235 |
236 | def test_add_namespaced_simple_command(self):
237 |
238 | manager = Manager(self.app)
239 | manager.add_command('hello', SimpleCommand(), namespace='ns')
240 | manager.add_command('world', SimpleCommand(), namespace='ns')
241 |
242 | assert 'ns' in manager._commands
243 | assert isinstance(manager._commands['ns'], Manager)
244 | ns = manager._commands['ns']
245 | assert isinstance(ns._commands['hello'], SimpleCommand)
246 | assert isinstance(ns._commands['world'], SimpleCommand)
247 |
248 | def test_add_command_class(self):
249 |
250 | manager = Manager(self.app)
251 | manager.add_command('simple', SimpleCommand)
252 |
253 | assert isinstance(manager._commands['simple'], SimpleCommand)
254 |
255 | def test_simple_command_decorator(self, capsys):
256 |
257 | manager = Manager(self.app)
258 |
259 | @manager.command
260 | def hello():
261 | print('hello')
262 |
263 | assert 'hello' in manager._commands
264 |
265 | code = run('manage.py hello', manager.run)
266 | out, err = capsys.readouterr()
267 | assert 'hello' in out
268 |
269 | def test_simple_command_decorator_with_pos_arg(self, capsys):
270 |
271 | manager = Manager(self.app)
272 |
273 | @manager.command
274 | def hello(name):
275 | print('hello ' + name)
276 |
277 | assert 'hello' in manager._commands
278 |
279 | code = run('manage.py hello joe', manager.run)
280 | out, err = capsys.readouterr()
281 | assert 'hello joe' in out
282 |
283 | def test_method_command_decorator_with_pos_arg(self, capsys):
284 |
285 | manager = Manager(self.app)
286 |
287 | class SomeTest(object):
288 | def hello(self,name):
289 | print('hello ' + name)
290 | sometest = SomeTest()
291 | manager.command(sometest.hello)
292 |
293 | assert 'hello' in manager._commands
294 |
295 | code = run('manage.py hello joe', lambda: manager.run())
296 | out, err = capsys.readouterr()
297 | assert 'hello joe' in out
298 |
299 | def test_command_decorator_with_options(self, capsys):
300 |
301 | manager = Manager(self.app)
302 |
303 | @manager.command
304 | def hello(name='fred'):
305 | 'Prints your name'
306 | print('hello ' + name)
307 |
308 | assert 'hello' in manager._commands
309 |
310 | code = run('manage.py hello --name=joe', manager.run)
311 | out, err = capsys.readouterr()
312 | assert 'hello joe' in out
313 |
314 | code = run('manage.py hello -n joe', manager.run)
315 | out, err = capsys.readouterr()
316 | assert 'hello joe' in out
317 |
318 | code = run('manage.py hello -?', manager.run)
319 | out, err = capsys.readouterr()
320 | assert 'Prints your name' in out
321 |
322 | code = run('manage.py hello --help', manager.run)
323 | out, err = capsys.readouterr()
324 | assert 'Prints your name' in out
325 |
326 | def test_no_help(self, capsys):
327 | """
328 | Tests that erasing --help really works.
329 | """
330 |
331 | manager = Manager(self.app)
332 | manager.help_args = ()
333 |
334 | @manager.command
335 | def hello(name='fred'):
336 | 'Prints your name'
337 | print('hello ' + name)
338 | assert 'hello' in manager._commands
339 |
340 | code = run('manage.py --help hello', manager.run)
341 | out, err = capsys.readouterr()
342 | print(out)
343 | assert 'too many arguments' in err
344 |
345 | code = run('manage.py hello --help', manager.run)
346 | out, err = capsys.readouterr()
347 | print(out)
348 | assert 'too many arguments' in err
349 |
350 | def test_command_decorator_with_boolean_options(self, capsys):
351 |
352 | manager = Manager(self.app)
353 |
354 | @manager.command
355 | def verify(verified=False):
356 | 'Checks if verified'
357 | print('VERIFIED ? ' + 'YES' if verified else 'NO')
358 |
359 | assert 'verify' in manager._commands
360 |
361 | code = run('manage.py verify --verified', manager.run)
362 | out, err = capsys.readouterr()
363 | assert 'YES' in out
364 |
365 | code = run('manage.py verify -v', manager.run)
366 | out, err = capsys.readouterr()
367 | assert 'YES' in out
368 |
369 | code = run('manage.py verify', manager.run)
370 | out, err = capsys.readouterr()
371 | assert 'NO' in out
372 |
373 | code = run('manage.py verify -?', manager.run)
374 | out, err = capsys.readouterr()
375 | assert 'Checks if verified' in out
376 |
377 | def test_simple_command_decorator_with_pos_arg_and_options(self, capsys):
378 |
379 | manager = Manager(self.app)
380 |
381 | @manager.command
382 | def hello(name, url=None):
383 | if url:
384 | assert type(url) is text_type
385 | print('hello ' + name + ' from ' + url)
386 | else:
387 | assert type(name) is text_type
388 | print('hello ' + name)
389 |
390 | assert 'hello' in manager._commands
391 |
392 | code = run('manage.py hello joe', manager.run)
393 | out, err = capsys.readouterr()
394 | assert 'hello joe' in out
395 |
396 | code = run('manage.py hello joe --url=reddit.com', manager.run)
397 | out, err = capsys.readouterr()
398 | assert 'hello joe from reddit.com' in out
399 |
400 | def test_command_decorator_with_additional_options(self, capsys):
401 |
402 | manager = Manager(self.app)
403 |
404 | @manager.option('-n', '--name', dest='name', help='Your name')
405 | def hello(name):
406 | print('hello ' + name)
407 |
408 | assert 'hello' in manager._commands
409 |
410 | code = run('manage.py hello --name=joe', manager.run)
411 | out, err = capsys.readouterr()
412 | assert 'hello joe' in out
413 |
414 | code = run('manage.py hello -?', manager.run)
415 | out, err = capsys.readouterr()
416 | assert 'Your name' in out
417 |
418 | @manager.option('-n', '--name', dest='name', help='Your name')
419 | @manager.option('-u', '--url', dest='url', help='Your URL')
420 | def hello_again(name, url=None):
421 | if url:
422 | print('hello ' + name + ' from ' + url)
423 | else:
424 | print('hello ' + name)
425 |
426 | assert 'hello_again' in manager._commands
427 |
428 | code = run('manage.py hello_again --name=joe', manager.run)
429 | out, err = capsys.readouterr()
430 | assert 'hello joe' in out
431 |
432 | code = run('manage.py hello_again --name=joe --url=reddit.com', manager.run)
433 | out, err = capsys.readouterr()
434 | assert 'hello joe from reddit.com' in out
435 |
436 | def test_global_option_provided_before_and_after_command(self, capsys):
437 |
438 | manager = Manager(self.app)
439 | manager.add_option('-c', '--config', dest='config_name', required=False, default='Development')
440 | manager.add_command('simple', SimpleCommand())
441 |
442 | assert isinstance(manager._commands['simple'], SimpleCommand)
443 |
444 | code = run('manage.py -c Development simple', manager.run)
445 | out, err = capsys.readouterr()
446 | assert code == 0
447 | assert 'OK' in out
448 |
449 | code = run('manage.py simple -c Development', manager.run)
450 | out, err = capsys.readouterr()
451 | assert code == 2
452 | assert 'OK' not in out
453 |
454 | def test_global_option_value(self, capsys):
455 |
456 | def create_app(config_name='Empty'):
457 | print(config_name)
458 | return self.app
459 |
460 | manager = Manager(create_app)
461 | manager.add_option('-c', '--config', dest='config_name', required=False, default='Development')
462 | manager.add_command('simple', SimpleCommand())
463 |
464 | assert isinstance(manager._commands['simple'], SimpleCommand)
465 |
466 | code = run('manage.py simple', manager.run)
467 | out, err = capsys.readouterr()
468 | assert code == 0
469 | assert 'Empty' not in out # config_name is overwritten by default option value
470 | assert 'Development' in out
471 | assert 'OK' in out
472 |
473 | def test_get_usage(self):
474 |
475 | manager = Manager(self.app)
476 | manager.add_command('simple', SimpleCommand())
477 |
478 | usage = manager.create_parser('manage.py').format_help()
479 | assert 'simple command' in usage
480 |
481 | def test_get_usage_with_specified_usage(self):
482 |
483 | manager = Manager(self.app, usage='hello')
484 | manager.add_command('simple', SimpleCommand())
485 |
486 | usage = manager.create_parser('manage.py').format_help()
487 | assert 'simple command' in usage
488 | assert 'hello' in usage
489 |
490 | def test_run_existing_command(self, capsys):
491 |
492 | manager = Manager(self.app)
493 | manager.add_command('simple', SimpleCommand())
494 | code = run('manage.py simple', manager.run)
495 | out, err = capsys.readouterr()
496 | assert 'OK' in out
497 |
498 | def test_run_non_existant_command(self, capsys):
499 |
500 | manager = Manager(self.app)
501 | run('manage.py simple', manager.run)
502 | out, err = capsys.readouterr()
503 | assert 'invalid choice' in err
504 |
505 | def test_run_existing(self, capsys):
506 |
507 | manager = Manager(self.app)
508 | manager.add_command('simple', SimpleCommand())
509 |
510 | code = run('manage.py simple', manager.run)
511 | out, err = capsys.readouterr()
512 | assert 0 == code
513 | assert 'OK' in out
514 |
515 | def test_run_existing_bind_later(self, capsys):
516 |
517 | manager = Manager(self.app)
518 |
519 | code = run('manage.py simple', lambda: manager.run({'simple': SimpleCommand()}))
520 | out, err = capsys.readouterr()
521 | assert code == 0
522 | assert 'OK' in out
523 |
524 | def test_run_not_existing(self, capsys):
525 |
526 | manager = Manager(self.app)
527 |
528 | code = run('manage.py simple', manager.run)
529 | out, err = capsys.readouterr()
530 | assert code == 2
531 | assert 'OK' not in out
532 |
533 | def test_run_no_name(self, capsys):
534 |
535 | manager = Manager(self.app)
536 | manager.add_command('simple', SimpleCommand())
537 |
538 | code = run('manage.py', manager.run)
539 | out, err = capsys.readouterr()
540 | assert code == 2
541 | assert 'simple command' in out
542 |
543 | def test_run_good_options(self, capsys):
544 |
545 | manager = Manager(self.app)
546 | manager.add_command('simple', CommandWithOptions())
547 |
548 | code = run('manage.py simple --name=Joe', manager.run)
549 | out, err = capsys.readouterr()
550 | assert code == 0
551 | assert 'Joe' in out
552 |
553 | def test_run_dynamic_options(self, capsys):
554 |
555 | manager = Manager(self.app)
556 | manager.add_command('simple', CommandWithDynamicOptions('Fred'))
557 |
558 | code = run('manage.py simple', manager.run)
559 | out, err = capsys.readouterr()
560 | assert code == 0
561 | assert 'Fred' in out
562 |
563 | def test_run_catch_all(self, capsys):
564 | manager = Manager(self.app)
565 | manager.add_command('catch', CommandWithCatchAll())
566 |
567 | code = run('manage.py catch pos1 --foo pos2 --bar', manager.run)
568 | out, err = capsys.readouterr()
569 | out_list = [o.strip('u\'') for o in out.strip('[]\n').split(', ')]
570 | assert code == 0
571 | assert ['pos1', 'pos2', '--bar'] == out_list
572 |
573 | def test_run_bad_options(self, capsys):
574 | manager = Manager(self.app)
575 | manager.add_command('simple', CommandWithOptions())
576 |
577 | code = run('manage.py simple --foo=bar', manager.run)
578 | assert code == 2
579 |
580 | def test_init_with_flask_instance(self):
581 | manager = Manager(self.app)
582 | assert callable(manager.app)
583 |
584 | def test_init_with_callable(self):
585 | manager = Manager(lambda: self.app)
586 | assert callable(manager.app)
587 |
588 | def test_raise_index_error(self):
589 |
590 | manager = Manager(self.app)
591 |
592 | @manager.command
593 | def error():
594 | raise IndexError()
595 |
596 | with raises(IndexError):
597 | run('manage.py error', manager.run)
598 |
599 | def test_run_with_default_command(self, capsys):
600 | manager = Manager(self.app)
601 | manager.add_command('simple', SimpleCommand())
602 |
603 | code = run('manage.py', lambda: manager.run(default_command='simple'))
604 | out, err = capsys.readouterr()
605 | assert code == 0
606 | assert 'OK' in out
607 |
608 | def test_command_with_prompt(self, capsys):
609 |
610 | manager = Manager(self.app)
611 |
612 | @manager.command
613 | def hello():
614 | print(prompt(name='hello'))
615 |
616 | @Catcher
617 | def hello_john(msg):
618 | if re.search("hello", msg):
619 | return 'john'
620 |
621 | with hello_john:
622 | code = run('manage.py hello', manager.run)
623 | out, err = capsys.readouterr()
624 | assert 'hello: john' in out
625 |
626 | def test_command_with_default_prompt(self, capsys):
627 |
628 | manager = Manager(self.app)
629 |
630 | @manager.command
631 | def hello():
632 | print(prompt(name='hello', default='romeo'))
633 |
634 | @Catcher
635 | def hello(msg):
636 | if re.search("hello", msg):
637 | return '\n' # just hit enter
638 |
639 | with hello:
640 | code = run('manage.py hello', manager.run)
641 | out, err = capsys.readouterr()
642 | assert 'hello [romeo]: romeo' in out
643 |
644 | @Catcher
645 | def hello_juliette(msg):
646 | if re.search("hello", msg):
647 | return 'juliette'
648 |
649 | with hello_juliette:
650 | code = run('manage.py hello', manager.run)
651 | out, err = capsys.readouterr()
652 | assert 'hello [romeo]: juliette' in out
653 |
654 |
655 | def test_command_with_prompt_bool(self, capsys):
656 |
657 | manager = Manager(self.app)
658 |
659 | @manager.command
660 | def hello():
661 | print(prompt_bool(name='correct', default=True, yes_choices=['y'],
662 | no_choices=['n']) and 'yes' or 'no')
663 |
664 | @Catcher
665 | def correct_default(msg):
666 | if re.search("correct", msg):
667 | return '\n' # just hit enter
668 |
669 | @Catcher
670 | def correct_y(msg):
671 | if re.search("correct", msg):
672 | return 'y'
673 |
674 | @Catcher
675 | def correct_n(msg):
676 | if re.search("correct", msg):
677 | return 'n'
678 |
679 | with correct_default:
680 | code = run('manage.py hello', manager.run)
681 | out, err = capsys.readouterr()
682 | assert 'correct [y]: yes' in out
683 |
684 | with correct_y:
685 | code = run('manage.py hello', manager.run)
686 | out, err = capsys.readouterr()
687 | assert 'correct [y]: yes' in out
688 |
689 | with correct_n:
690 | code = run('manage.py hello', manager.run)
691 | out, err = capsys.readouterr()
692 | assert 'correct [y]: no' in out
693 |
694 | def test_command_with_prompt_choices(self, capsys):
695 |
696 | manager = Manager(self.app)
697 |
698 | @manager.command
699 | def hello():
700 | print(prompt_choices(name='hello', choices=['peter', 'john', 'sam']))
701 |
702 | @Catcher
703 | def hello_john(msg):
704 | if re.search("hello", msg):
705 | return 'john'
706 |
707 | with hello_john:
708 | code = run('manage.py hello', manager.run)
709 | out, err = capsys.readouterr()
710 | assert 'hello - (peter, john, sam): john' in out
711 |
712 | def test_command_with_default_prompt_choices(self, capsys):
713 |
714 | manager = Manager(self.app)
715 |
716 | @manager.command
717 | def hello():
718 | print(prompt_choices(name='hello', choices=['peter', 'charlie', 'sam'], default="john"))
719 |
720 | @Catcher
721 | def hello_john(msg):
722 | if re.search("hello", msg):
723 | return '\n'
724 |
725 | with hello_john:
726 | code = run('manage.py hello', manager.run)
727 | out, err = capsys.readouterr()
728 | assert 'hello - (peter, charlie, sam) [john]: john' in out
729 |
730 | @Catcher
731 | def hello_charlie(msg):
732 | if re.search("hello", msg):
733 | return 'charlie'
734 |
735 | with hello_charlie:
736 | code = run('manage.py hello', manager.run)
737 | out, err = capsys.readouterr()
738 | assert 'hello - (peter, charlie, sam) [john]: charlie' in out
739 |
740 | class TestSubManager:
741 |
742 | def setup(self):
743 |
744 | self.app = AppForTesting()
745 |
746 | def test_add_submanager(self):
747 |
748 | sub_manager = Manager()
749 |
750 | manager = Manager(self.app)
751 | manager.add_command('sub_manager', sub_manager)
752 |
753 | assert isinstance(manager._commands['sub_manager'], Manager)
754 | assert sub_manager.parent == manager
755 | assert sub_manager.get_options() == manager.get_options()
756 |
757 | def test_run_submanager_command(self, capsys):
758 |
759 | sub_manager = Manager()
760 | sub_manager.add_command('simple', SimpleCommand())
761 |
762 | manager = Manager(self.app)
763 | manager.add_command('sub_manager', sub_manager)
764 |
765 | code = run('manage.py sub_manager simple', manager.run)
766 | out, err = capsys.readouterr()
767 | assert code == 0
768 | assert 'OK' in out
769 |
770 | def test_submanager_has_options(self, capsys):
771 |
772 | sub_manager = Manager()
773 | sub_manager.add_command('simple', SimpleCommand())
774 |
775 | manager = Manager(self.app)
776 | manager.add_command('sub_manager', sub_manager)
777 | manager.add_option('-c', '--config', dest='config', required=False)
778 |
779 | code = run('manage.py sub_manager simple', manager.run)
780 | out, err = capsys.readouterr()
781 | assert code == 0
782 | assert 'OK' in out
783 |
784 | code = run('manage.py -c Development sub_manager simple', manager.run)
785 | out, err = capsys.readouterr()
786 | assert code == 0
787 | assert 'OK' in out
788 |
789 |
790 | def test_submanager_separate_options(self, capsys):
791 |
792 | sub_manager = Manager(AppForTesting(verbose=True), with_default_commands=False)
793 | sub_manager.add_command('opt', CommandWithOptionalArg())
794 | sub_manager.add_option('-n', '--name', dest='name_sub', required=False)
795 |
796 | manager = Manager(AppForTesting(verbose=True), with_default_commands=False)
797 | manager.add_command('sub_manager', sub_manager)
798 | manager.add_option('-n', '--name', dest='name_main', required=False)
799 |
800 | code = run('manage.py -n MyMainName sub_manager -n MySubName opt -n MyName', manager.run)
801 | out, err = capsys.readouterr()
802 | assert code == 0
803 | assert 'APP name_main=MyMainName' in out
804 | assert 'APP name_sub=MySubName' in out
805 | assert 'OK name=MyName' in out
806 |
807 | def test_manager_usage_with_submanager(self, capsys):
808 |
809 | sub_manager = Manager(usage='Example sub-manager')
810 |
811 | manager = Manager(self.app)
812 | manager.add_command('sub_manager', sub_manager)
813 |
814 | code = run('manage.py -?', manager.run)
815 | out, err = capsys.readouterr()
816 | assert code == 0
817 | assert 'Example sub-manager' in out
818 |
819 | def test_submanager_usage_and_help_and_description(self, capsys):
820 |
821 | sub_manager = Manager(usage='sub_manager [--foo]',
822 | help='shorter desc for submanager',
823 | description='longer desc for submanager')
824 | sub_manager.add_command('simple', SimpleCommand())
825 |
826 | manager = Manager(self.app)
827 | manager.add_command('sub_manager', sub_manager)
828 |
829 | code = run('manage.py -?', manager.run)
830 | out, err = capsys.readouterr()
831 | assert code == 0
832 | assert 'sub_manager [--foo]' not in out
833 | assert 'shorter desc for submanager' in out
834 | assert 'longer desc for submanager' not in out
835 |
836 | code = run('manage.py sub_manager', manager.run)
837 | out, err = capsys.readouterr()
838 | assert code == 2
839 | assert 'sub_manager [--foo]' in out
840 | assert 'shorter desc for submanager' not in out
841 | assert 'longer desc for submanager' in out
842 | assert 'simple command' in out
843 |
844 | code = run('manage.py sub_manager -?', manager.run)
845 | out, err = capsys.readouterr()
846 | assert code == 0
847 | assert 'sub_manager [--foo]' in out
848 | assert 'shorter desc for submanager' not in out
849 | assert 'longer desc for submanager' in out
850 | assert 'simple command' in out
851 |
852 | code = run('manage.py sub_manager simple -?', manager.run)
853 | out, err = capsys.readouterr()
854 | assert code == 0
855 | assert 'sub_manager [--foo] simple [-?]' in out
856 | assert 'simple command' in out
857 |
858 | def test_submanager_has_no_default_commands(self):
859 |
860 | sub_manager = Manager()
861 |
862 | manager = Manager()
863 | manager.add_command('sub_manager', sub_manager)
864 | manager.set_defaults()
865 |
866 | assert 'runserver' not in sub_manager._commands
867 | assert 'shell' not in sub_manager._commands
868 |
--------------------------------------------------------------------------------