├── .coveragerc ├── .dockerignore ├── .editorconfig ├── .gitignore ├── .lgtm ├── .travis.yml ├── AUTHORS ├── CHANGES ├── CONTRIBUTING.rst ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── MANIFEST.in ├── README.rst ├── RELEASE-NOTES.rst ├── docker-compose.yml ├── docs ├── Makefile ├── conf.py ├── index.rst └── requirements.txt ├── flask_cli ├── __init__.py ├── _compat.py ├── app.py ├── cli.py ├── ext.py ├── helpers.py └── version.py ├── pytest.ini ├── requirements.devel.txt ├── run-tests.sh ├── setup.cfg ├── setup.py ├── tests ├── app.py ├── multiapp.py ├── test_cli.py └── test_ext.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = flask_cli 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | *.pyc 3 | __pycache__/ 4 | .tox 5 | .cache 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | 10 | # Python files 11 | [*.py] 12 | indent_size = 4 13 | # isort plugin configuration 14 | multi_line_output = 2 15 | default_section = THIRDPARTY 16 | 17 | # RST files (used by sphinx) 18 | [*.rst] 19 | indent_size = 4 20 | 21 | # CSS, HTML, JS, JSON, YML 22 | [*.{css,html,js,json,yml}] 23 | indent_size = 2 24 | 25 | # Matches the exact files either package.json or .travis.yml 26 | [{package.json,.travis.yml}] 27 | indent_size = 2 28 | 29 | # Dockerfile 30 | [Dockerfile] 31 | indent_size = 4 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /.lgtm: -------------------------------------------------------------------------------- 1 | approvals = 1 2 | pattern = "(?i)LGTM" 3 | self_approval_off = false 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | sudo: false 10 | 11 | language: python 12 | 13 | python: 14 | - "2.7" 15 | - "3.3" 16 | - "3.4" 17 | - "3.5" 18 | 19 | env: 20 | - REQUIREMENTS=lowest 21 | - REQUIREMENTS=latest 22 | - REQUIREMENTS=devel 23 | 24 | cache: 25 | - pip 26 | 27 | before_install: 28 | - "travis_retry pip install --upgrade pip setuptools py" 29 | - "travis_retry pip install twine wheel coveralls requirements-builder" 30 | - "requirements-builder --level=min setup.py > .travis-lowest-requirements.txt" 31 | - "requirements-builder --level=pypi setup.py > .travis-latest-requirements.txt" 32 | - "requirements-builder --level=dev --req requirements.devel.txt setup.py > .travis-devel-requirements.txt" 33 | 34 | install: 35 | - "travis_retry pip install -r .travis-${REQUIREMENTS}-requirements.txt" 36 | - "travis_retry pip install -e .[all]" 37 | 38 | script: 39 | - "./run-tests.sh" 40 | 41 | after_success: 42 | - coveralls 43 | 44 | notifications: 45 | email: false 46 | 47 | deploy: 48 | provider: pypi 49 | user: lnielsen 50 | password: 51 | secure: cKcrzBcAPn5pQqhJAX+ct5L7dNDL3Y4NKt+KksWfag5YN2SCTkAEcm0JvKxP4pURG0AOXqqWjY1ydvj7XkafEOWq4lKjtJy/Eb1ZY8dO5RJOQ1fjICvtVxcW/6qQKz5TESd6CafXis0E3kmqrRAHWnFZATEhA0KnnvoCoGByZUOn9gg527dA8tv4MatY9xYQGOuryKWq/KKgD92Z9EhwwzB+26naXlpI6GQiMoxJA5br4Idc9Y5uHbJVGSxTWkQYbXo8GmYatwGmswPfJXZqJt37K8yLdYTCZku7FQ/mBNrj1OQutQh40MX72Zd3o5GEHswFLKvRcPjX4jA6kjBcGKC/Lxn2oDCKA8HhLpCUwQlhuZpXvp2Ghq8df7ZIZvsuO/KOVAske7e0L0uGcs244jY00uTFtK9X/Xtj72tBD4On/GeWXOgRW6tlwo6NsJOdR1HjiR6F3xX62IUJBXGE5obmbNTF+XciWTiY1aTKlSSRbu+M0q6jD+1DFl0ttuvdh9zMctdDkCJMqncEU5z9sDX6U08iahdHF70eCL6dRvl5aD6flbVMVOXt7z3WZPqWzLDIOCuy1JjgMyO2l++AB4lhSCdxEaOjGlLIyIBLfLnsDMtqrKavNszq4h01qlh3tiQNW9KXxXuxCTx4lr5YdQ/y6P5mhSoQBNfpZpVuRr4= 52 | distributions: "sdist bdist_wheel" 53 | on: 54 | tags: true 55 | python: "2.7" 56 | condition: $REQUIREMENTS = latest 57 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Authors 2 | ------- 3 | 4 | Contact us at `info@inveniosoftware.org `_ 5 | 6 | * Lars Holm Nielsen 7 | 8 | This package includes code from Flask, which has not been developed by the 9 | authors. Please see LICENSE for further details. 10 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | Version 0.4.0 (released 2016-06-28) 5 | 6 | - Adds a --version switch to Flask CLI. 7 | 8 | - Updates help to `> set FLASK_APP=hello.py`. 9 | 10 | - Adds plugin support to the CLI. 11 | 12 | - Updates docs to the new CLI patterns. 13 | 14 | - Removes Python 2.6 and adds Python 3.5 to Tox configuration to match 15 | Travis config. 16 | 17 | - Implements simplified CLI interface. 18 | 19 | Version 0.3.0 (released 2016-05-17) 20 | 21 | - Werkzeug should not block propagated exceptions from Flask. 22 | 23 | - Fixes issue with the parameter passed to the CLI's AppGroup. 24 | 25 | - Adds shell context processor example. 26 | 27 | - Fixes missing import in Python example in README. 28 | 29 | - Fixes broken tests. 30 | 31 | Version 0.2.1 (released 2015-08-03) 32 | 33 | - Adds support for specifying shell context processors. 34 | 35 | - Fixes bug with shell command due to missing support for shell context 36 | processors. 37 | 38 | Version 0.2.0 (released 2015-07-31) 39 | 40 | - Adds FlaskCLI extension to support app.cli. 41 | 42 | - Changes default imports from flask_cli.cli to flask_cli and relies on Flask's 43 | classes when v1.0 is installed. 44 | 45 | Version 0.1.0 (released 2015-07-31) 46 | 47 | - Initial public release 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Bug reports, feature requests, and other contributions are welcome. 5 | If you find a demonstrable problem that is caused by the code of this 6 | library, please: 7 | 8 | 1. Search for `already reported problems 9 | `_. 10 | 2. Check if the issue has been fixed or is still reproducible on the 11 | latest `master` branch. 12 | 3. Create an issue with **a test case**. 13 | 14 | If you create a feature branch, you can run the tests to ensure everything is 15 | operating correctly: 16 | 17 | .. code-block:: console 18 | 19 | $ ./run-tests.sh 20 | 21 | You can also test your feature branch using Docker: 22 | 23 | .. code-block:: console 24 | 25 | $ docker-compose build 26 | $ docker-compose run --rm web /code/run-tests.sh 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This file is part of Flask-CLI 2 | # Copyright (C) 2015 CERN. 3 | # 4 | # Flask-CLI is free software; you can redistribute it and/or modify it under 5 | # the terms of the Revised BSD License; see LICENSE file for more details. 6 | 7 | # Use Python-2.7: 8 | FROM python:2.7 9 | 10 | # Install some prerequisites ahead of `setup.py` in order to profit 11 | # from the docker build cache: 12 | RUN pip install coveralls \ 13 | ipython \ 14 | pydocstyle \ 15 | pytest \ 16 | pytest-cache \ 17 | pytest-cov \ 18 | Sphinx 19 | 20 | # Add sources to `code` and work there: 21 | WORKDIR /code 22 | ADD . /code 23 | 24 | # Install flask-cli: 25 | RUN pip install -e . 26 | 27 | # Run container as user `flask-cli` with UID `1000`, which should match 28 | # current host user in most situations: 29 | RUN adduser --uid 1000 --disabled-password --gecos '' flaskcli && \ 30 | chown -R flaskcli:flaskcli /code 31 | 32 | # Run test suite instead of starting the application: 33 | USER flaskcli 34 | CMD ["python", "setup.py", "test"] 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Flask-CLI is free software; you can redistribute it and/or modify it 2 | under the terms of the Revised BSD License quoted below. 3 | 4 | Copyright (C) 2015 CERN. 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the name of the copyright holder nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 30 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 32 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 33 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 34 | DAMAGE. 35 | 36 | In applying this license, CERN does not waive the privileges and immunities 37 | granted to it by virtue of its status as an Intergovernmental Organization or 38 | submit itself to any jurisdiction. 39 | 40 | ---- 41 | 42 | The following files are under different license/copyright:: 43 | 44 | flask_cli/cli.py 45 | flask_cli/_compat.py 46 | flask_cli/app.py 47 | 48 | Copyright (c) 2015 by Armin Ronacher and contributors. 49 | 50 | Some rights reserved. 51 | 52 | Redistribution and use in source and binary forms of the software as well 53 | as documentation, with or without modification, are permitted provided 54 | that the following conditions are met: 55 | 56 | * Redistributions of source code must retain the above copyright 57 | notice, this list of conditions and the following disclaimer. 58 | 59 | * Redistributions in binary form must reproduce the above 60 | copyright notice, this list of conditions and the following 61 | disclaimer in the documentation and/or other materials provided 62 | with the distribution. 63 | 64 | * The names of the contributors may not be used to endorse or 65 | promote products derived from this software without specific 66 | prior written permission. 67 | 68 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 69 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 70 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 71 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 72 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 73 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 74 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 75 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 76 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 77 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 78 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 79 | DAMAGE. 80 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Lars Holm Nielsen (@lnielsen) 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | include .lgtm 10 | include LICENSE 11 | include MAINTAINERS AUTHORS CHANGES README.rst CONTRIBUTING.rst RELEASE-NOTES.rst 12 | include .coveragerc run-tests.sh pytest.ini tox.ini .editorconfig 13 | include docs/*.rst docs/*.py docs/Makefile docs/requirements.txt 14 | include tests/*.py 15 | include .dockerignore docker-compose.yml Dockerfile 16 | include requirements.devel.txt 17 | recursive-include docs/_themes *.py *.css *.css_t *.conf *.html README 18 | recursive-include docs/_templates *.html 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Flask-CLI 3 | =========== 4 | 5 | About 6 | ===== 7 | 8 | Flask-CLI is a backport of Flask 1.0 new click integration to Flask 0.10. 9 | Do not install this package if you use Flask 1.0+. 10 | 11 | Installation 12 | ============ 13 | 14 | Flask-CLI is on PyPI so all you need is: :: 15 | 16 | pip install flask-cli 17 | 18 | 19 | Usage 20 | ===== 21 | 22 | Initialize the extension like this: 23 | 24 | .. code-block:: python 25 | 26 | from flask import Flask 27 | from flask_cli import FlaskCLI 28 | app = Flask('myapp') 29 | FlaskCLI(app) 30 | 31 | @app.cli.command() 32 | def mycmd(): 33 | click.echo("Test") 34 | 35 | @app.shell_context_processor 36 | def myctx(): 37 | return {'myvar': 'value'} 38 | 39 | Import from this library instead of ``flask.cli``: 40 | 41 | .. code-block:: python 42 | 43 | from flask_cli import FlaskGroup 44 | 45 | Documentation 46 | ============= 47 | 48 | Documentation is readable at https://flask-cli.readthedocs.io/ or can be 49 | build using Sphinx: :: 50 | 51 | pip install Sphinx 52 | python setup.py build_sphinx 53 | 54 | Testing 55 | ======= 56 | 57 | Running the test suite is as simple as: :: 58 | 59 | python setup.py test 60 | -------------------------------------------------------------------------------- /RELEASE-NOTES.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Flask-CLI v0.4.0 3 | ================== 4 | 5 | Flask-CLI v0.4.0 was released on June 28, 2016. 6 | 7 | About 8 | ----- 9 | 10 | Flask-CLI is a backport of Flask v0.11+ new Click integration to v0.10. 11 | 12 | Installation 13 | ------------ 14 | 15 | $ pip install flask-cli==0.4.0 16 | 17 | What's new 18 | ---------- 19 | 20 | - Adds a --version switch to Flask CLI. 21 | 22 | - Updates help to `> set FLASK_APP=hello.py`. 23 | 24 | - Adds plugin support to the CLI. 25 | 26 | - Updates docs to the new CLI patterns. 27 | 28 | - Removes Python 2.6 and adds Python 3.5 to Tox configuration to match 29 | Travis config. 30 | 31 | - Implements simplified CLI interface. 32 | 33 | Documentation 34 | ------------- 35 | 36 | https://flask-cli.readthedocs.io/ 37 | 38 | Homepage 39 | -------- 40 | 41 | https://github.com/inveniosoftware/flask-cli 42 | 43 | Happy hacking and thanks for flying Flask-CLI. 44 | 45 | | Invenio Development Team 46 | | Email: info@inveniosoftware.org 47 | | Twitter: https://twitter.com/inveniosoftware 48 | | GitHub: https://github.com/inveniosoftware 49 | | URL: http://inveniosoftware.org 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This file is part of Flask-CLI 2 | # Copyright (C) 2015 CERN. 3 | # 4 | # Flask-CLI is free software; you can redistribute it and/or modify it under 5 | # the terms of the Revised BSD License; see LICENSE file for more details. 6 | 7 | web: 8 | build: . 9 | command: python setup.py test 10 | volumes: 11 | - .:/code 12 | -------------------------------------------------------------------------------- /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 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-CLI.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-CLI.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-CLI" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-CLI" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Flask-CLI documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jul 31 13:10:12 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import re 17 | import shlex 18 | import sys 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.viewcode', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | # templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'Flask-CLI' 55 | copyright = u'2015, CERN' 56 | author = u'Invenio Software' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | # Get the version string. Cannot be done with import! 64 | with open(os.path.join('..', 'flask_cli', 'version.py'), 'rt') as f: 65 | version = re.search( 66 | '__version__\s*=\s*"(?P.*)"\n', 67 | f.read() 68 | ).group('version') 69 | 70 | # The full version, including alpha/beta/rc tags. 71 | release = version 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # There are two options for replacing |today|: either, you set today to some 81 | # non-false value, then it is used: 82 | #today = '' 83 | # Else, today_fmt is used as the format for a strftime call. 84 | #today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | exclude_patterns = ['_build'] 89 | 90 | # The reST default role (used for this markup: `text`) to use for all 91 | # documents. 92 | #default_role = None 93 | 94 | # If true, '()' will be appended to :func: etc. cross-reference text. 95 | #add_function_parentheses = True 96 | 97 | # If true, the current module name will be prepended to all description 98 | # unit titles (such as .. function::). 99 | #add_module_names = True 100 | 101 | # If true, sectionauthor and moduleauthor directives will be shown in the 102 | # output. They are ignored by default. 103 | #show_authors = False 104 | 105 | # The name of the Pygments (syntax highlighting) style to use. 106 | pygments_style = 'sphinx' 107 | 108 | # A list of ignored prefixes for module index sorting. 109 | #modindex_common_prefix = [] 110 | 111 | # If true, keep warnings as "system message" paragraphs in the built documents. 112 | #keep_warnings = False 113 | 114 | # If true, `todo` and `todoList` produce output, else they produce nothing. 115 | todo_include_todos = False 116 | 117 | 118 | # -- Options for HTML output ---------------------------------------------- 119 | 120 | # The theme to use for HTML and HTML Help pages. See the documentation for 121 | # a list of builtin themes. 122 | html_theme = 'alabaster' 123 | 124 | # Theme options are theme-specific and customize the look and feel of a theme 125 | # further. For a list of options available for each theme, see the 126 | # documentation. 127 | html_theme_options = { 128 | 'description': 'Flask-CLI is a backport of Flask 1.0 new click integration to Flask 0.10.', 129 | 'github_user': 'inveniosoftware', 130 | 'github_repo': 'flask-cli', 131 | 'github_button': False, 132 | 'github_banner': True, 133 | 'show_powered_by': False, 134 | 'extra_nav_links': { 135 | 'Flask-CLI@GitHub': 'http://github.com/inveniosoftware/flask-cli/', 136 | 'Flask-CLI@PyPI': 'http://pypi.python.org/pypi/Flask-CLI/', 137 | } 138 | } 139 | 140 | # Add any paths that contain custom themes here, relative to this directory. 141 | #html_theme_path = [] 142 | 143 | # The name for this set of Sphinx documents. If None, it defaults to 144 | # " v documentation". 145 | #html_title = None 146 | 147 | # A shorter title for the navigation bar. Default is the same as html_title. 148 | #html_short_title = None 149 | 150 | # The name of an image file (relative to this directory) to place at the top 151 | # of the sidebar. 152 | #html_logo = None 153 | 154 | # The name of an image file (within the static path) to use as favicon of the 155 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 156 | # pixels large. 157 | #html_favicon = None 158 | 159 | # Add any paths that contain custom static files (such as style sheets) here, 160 | # relative to this directory. They are copied after the builtin static files, 161 | # so a file named "default.css" will overwrite the builtin "default.css". 162 | #html_static_path = ['_static'] 163 | 164 | # Add any extra paths that contain custom files (such as robots.txt or 165 | # .htaccess) here, relative to this directory. These files are copied 166 | # directly to the root of the documentation. 167 | #html_extra_path = [] 168 | 169 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 170 | # using the given strftime format. 171 | #html_last_updated_fmt = '%b %d, %Y' 172 | 173 | # If true, SmartyPants will be used to convert quotes and dashes to 174 | # typographically correct entities. 175 | #html_use_smartypants = True 176 | 177 | # Custom sidebar templates, maps document names to template names. 178 | #html_sidebars = {} 179 | 180 | # Additional templates that should be rendered to pages, maps page names to 181 | # template names. 182 | #html_additional_pages = {} 183 | 184 | # If false, no module index is generated. 185 | #html_domain_indices = True 186 | 187 | # If false, no index is generated. 188 | #html_use_index = True 189 | 190 | # If true, the index is split into individual pages for each letter. 191 | #html_split_index = False 192 | 193 | # If true, links to the reST sources are added to the pages. 194 | #html_show_sourcelink = True 195 | 196 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 197 | #html_show_sphinx = True 198 | 199 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 200 | #html_show_copyright = True 201 | 202 | # If true, an OpenSearch description file will be output, and all pages will 203 | # contain a tag referring to it. The value of this option must be the 204 | # base URL from which the finished HTML is served. 205 | #html_use_opensearch = '' 206 | 207 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 208 | #html_file_suffix = None 209 | 210 | # Language to be used for generating the HTML full-text search index. 211 | # Sphinx supports the following languages: 212 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 213 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 214 | #html_search_language = 'en' 215 | 216 | # A dictionary with options for the search language support, empty by default. 217 | # Now only 'ja' uses this config value 218 | #html_search_options = {'type': 'default'} 219 | 220 | # The name of a javascript file (relative to the configuration directory) that 221 | # implements a search results scorer. If empty, the default will be used. 222 | #html_search_scorer = 'scorer.js' 223 | 224 | # Output file base name for HTML help builder. 225 | htmlhelp_basename = 'Flask-CLIdoc' 226 | 227 | # -- Options for LaTeX output --------------------------------------------- 228 | 229 | latex_elements = { 230 | # The paper size ('letterpaper' or 'a4paper'). 231 | #'papersize': 'letterpaper', 232 | 233 | # The font size ('10pt', '11pt' or '12pt'). 234 | #'pointsize': '10pt', 235 | 236 | # Additional stuff for the LaTeX preamble. 237 | #'preamble': '', 238 | 239 | # Latex figure (float) alignment 240 | #'figure_align': 'htbp', 241 | } 242 | 243 | # Grouping the document tree into LaTeX files. List of tuples 244 | # (source start file, target name, title, 245 | # author, documentclass [howto, manual, or own class]). 246 | latex_documents = [ 247 | (master_doc, 'Flask-CLI.tex', u'Flask-CLI Documentation', 248 | u'Invenio Software', 'manual'), 249 | ] 250 | 251 | # The name of an image file (relative to this directory) to place at the top of 252 | # the title page. 253 | #latex_logo = None 254 | 255 | # For "manual" documents, if this is true, then toplevel headings are parts, 256 | # not chapters. 257 | #latex_use_parts = False 258 | 259 | # If true, show page references after internal links. 260 | #latex_show_pagerefs = False 261 | 262 | # If true, show URL addresses after external links. 263 | #latex_show_urls = False 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #latex_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #latex_domain_indices = True 270 | 271 | 272 | # -- Options for manual page output --------------------------------------- 273 | 274 | # One entry per manual page. List of tuples 275 | # (source start file, name, description, authors, manual section). 276 | man_pages = [ 277 | (master_doc, 'flask-cli', u'Flask-CLI Documentation', 278 | [author], 1) 279 | ] 280 | 281 | # If true, show URL addresses after external links. 282 | #man_show_urls = False 283 | 284 | 285 | # -- Options for Texinfo output ------------------------------------------- 286 | 287 | # Grouping the document tree into Texinfo files. List of tuples 288 | # (source start file, target name, title, author, 289 | # dir menu entry, description, category) 290 | texinfo_documents = [ 291 | (master_doc, 'Flask-CLI', u'Flask-CLI Documentation', 292 | author, 'Flask-CLI', 'One line description of project.', 293 | 'Miscellaneous'), 294 | ] 295 | 296 | # Documents to append as an appendix to all manuals. 297 | #texinfo_appendices = [] 298 | 299 | # If false, no module index is generated. 300 | #texinfo_domain_indices = True 301 | 302 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 303 | #texinfo_show_urls = 'footnote' 304 | 305 | # If true, do not generate a @detailmenu in the "Top" node's menu. 306 | #texinfo_no_detailmenu = False 307 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Flask-CLI 3 | ================ 4 | .. currentmodule:: flask_cli 5 | 6 | .. raw:: html 7 | 8 |

9 | 10 | travis-ci badge 12 | 13 | 14 | coveralls.io badge 16 | 17 |

18 | 19 | 20 | .. automodule:: flask_cli 21 | 22 | Installation 23 | ============ 24 | 25 | The Flask-CLI package is on PyPI so all you need is: 26 | 27 | .. code-block:: console 28 | 29 | $ pip install flask-cli 30 | 31 | Usage 32 | ===== 33 | 34 | Initialize the extension like this: 35 | 36 | .. code-block:: python 37 | 38 | import click 39 | from flask import Flask 40 | from flask_cli import FlaskCLI 41 | app = Flask('myapp') 42 | FlaskCLI(app) 43 | 44 | @app.cli.command() 45 | def mycmd(): 46 | click.echo("Test") 47 | 48 | CLI Plugins 49 | ----------- 50 | 51 | Flask extensions can always patch the `Flask.cli` instance with more 52 | commands if they want. However there is a second way to add CLI plugins 53 | to Flask which is through `setuptools`. If you make a Python package that 54 | should export a Flask command line plugin you can ship a `setup.py` file 55 | that declares an entrypoint that points to a click command: 56 | 57 | Example `setup.py`:: 58 | 59 | from setuptools import setup 60 | 61 | setup( 62 | name='flask-my-extension', 63 | ... 64 | entry_points=''' 65 | [flask.commands] 66 | my-command=mypackage.commands:cli 67 | ''', 68 | ) 69 | 70 | Inside `mypackage/comamnds.py` you can then export a Click object:: 71 | 72 | import click 73 | 74 | @click.command() 75 | def cli(): 76 | """This is an example command.""" 77 | 78 | Once that package is installed in the same virtualenv as Flask itself you 79 | can run ``flask my-command`` to invoke your command. This is useful to 80 | provide extra functionality that Flask itself cannot ship. 81 | 82 | Import from this library instead of ``flask.cli``, e.g.: 83 | 84 | .. code-block:: python 85 | 86 | from flask_cli import FlaskGroup 87 | 88 | .. include:: ../CHANGES 89 | 90 | .. include:: ../CONTRIBUTING.rst 91 | 92 | License 93 | ======= 94 | 95 | .. include:: ../LICENSE 96 | 97 | .. include:: ../AUTHORS 98 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -e .[docs,tests] 2 | -------------------------------------------------------------------------------- /flask_cli/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Flask-CLI is a backport of Flask 1.0 new click integration to v0.10. 11 | 12 | Do not install this package if you use Flask 1.0+. 13 | 14 | Full documentation of Flask's new click command line integration can be found 15 | at: http://flask.pocoo.org/docs/dev/cli/. 16 | """ 17 | 18 | from __future__ import absolute_import, print_function 19 | 20 | try: 21 | from flask.cli import AppGroup, DispatchingApp, FlaskGroup, \ 22 | NoAppException, ScriptInfo, cli, \ 23 | find_best_app, locate_app, main, pass_script_info, \ 24 | prepare_exec_for_file, run_command, \ 25 | shell_command, with_appcontext 26 | except ImportError: 27 | from flask_cli.cli import AppGroup, DispatchingApp, FlaskGroup, \ 28 | NoAppException, ScriptInfo, cli, \ 29 | find_best_app, locate_app, main, pass_script_info, \ 30 | prepare_exec_for_file, run_command, \ 31 | shell_command, with_appcontext 32 | 33 | from .ext import FlaskCLI 34 | from .helpers import get_debug_flag 35 | from .version import __version__ 36 | 37 | __all__ = ( 38 | '__version__', 'FlaskCLI', 'AppGroup', 'DispatchingApp', 'FlaskGroup', 39 | 'NoAppException', 'ScriptInfo', 'cli', 'get_debug_flag', 40 | 'find_best_app', 'locate_app', 'main', 'pass_script_info', 41 | 'prepare_exec_for_file', 'run_command', 42 | 'shell_command', 'with_appcontext', 43 | ) 44 | 45 | if __name__ == '__main__': 46 | main(as_module=True) 47 | -------------------------------------------------------------------------------- /flask_cli/_compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | flask._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) 2015 by Armin Ronacher. 11 | :license: BSD, see LICENSE for more details. 12 | """ 13 | import sys 14 | 15 | PY2 = sys.version_info[0] == 2 16 | _identity = lambda x: x 17 | 18 | 19 | if not PY2: 20 | iteritems = lambda d: iter(d.items()) 21 | 22 | def reraise(tp, value, tb=None): 23 | if value.__traceback__ is not tb: 24 | raise value.with_traceback(tb) 25 | raise value 26 | else: 27 | iteritems = lambda d: d.iteritems() 28 | exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') 29 | -------------------------------------------------------------------------------- /flask_cli/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | flask.app 4 | ~~~~~~~~~ 5 | 6 | This module implements the central WSGI application object. 7 | 8 | :copyright: (c) 2015 by Armin Ronacher. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | from functools import update_wrapper 13 | 14 | from flask.globals import g 15 | 16 | 17 | def setupmethod(f): 18 | """Wraps a method so that it performs a check in debug mode if the 19 | first request was already handled. 20 | """ 21 | def wrapper_func(self, *args, **kwargs): 22 | if self.debug and self._got_first_request: 23 | raise AssertionError('A setup function was called after the ' 24 | 'first request was handled. This usually indicates a bug ' 25 | 'in the application where a module was not imported ' 26 | 'and decorators or other functionality was called too late.\n' 27 | 'To fix this make sure to import all your view modules, ' 28 | 'database models and everything related at a central place ' 29 | 'before the application starts serving requests.') 30 | return f(self, *args, **kwargs) 31 | return update_wrapper(wrapper_func, f) 32 | 33 | 34 | def make_shell_context(self): 35 | """Returns the shell context for an interactive shell for this 36 | application. This runs all the registered shell context 37 | processors. 38 | 39 | .. versionadded:: 1.0 40 | """ 41 | rv = {'app': self, 'g': g} 42 | for processor in self.shell_context_processors: 43 | rv.update(processor()) 44 | return rv 45 | 46 | @setupmethod 47 | def shell_context_processor(self, f): 48 | """Registers a shell context processor function. 49 | 50 | .. versionadded:: 1.0 51 | """ 52 | self.shell_context_processors.append(f) 53 | return f 54 | -------------------------------------------------------------------------------- /flask_cli/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | flask.cli 4 | ~~~~~~~~~ 5 | 6 | A simple command line application to run flask apps. 7 | 8 | :copyright: (c) 2015 by Armin Ronacher. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | import os 13 | import sys 14 | from functools import update_wrapper 15 | from threading import Lock, Thread 16 | 17 | import click 18 | from flask import __version__ as __flask_version__ 19 | 20 | from ._compat import iteritems, reraise 21 | from .helpers import get_debug_flag 22 | 23 | 24 | class NoAppException(click.UsageError): 25 | """Raised if an application cannot be found or loaded.""" 26 | 27 | 28 | def find_best_app(module): 29 | """Given a module instance this tries to find the best possible 30 | application in the module or raises an exception. 31 | """ 32 | from flask import Flask 33 | 34 | # Search for the most common names first. 35 | for attr_name in 'app', 'application': 36 | app = getattr(module, attr_name, None) 37 | if app is not None and isinstance(app, Flask): 38 | return app 39 | 40 | # Otherwise find the only object that is a Flask instance. 41 | matches = [v for k, v in iteritems(module.__dict__) 42 | if isinstance(v, Flask)] 43 | 44 | if len(matches) == 1: 45 | return matches[0] 46 | raise NoAppException('Failed to find application in module "%s". Are ' 47 | 'you sure it contains a Flask application? Maybe ' 48 | 'you wrapped it in a WSGI middleware or you are ' 49 | 'using a factory function.' % module.__name__) 50 | 51 | 52 | def prepare_exec_for_file(filename): 53 | """Given a filename this will try to calculate the python path, add it 54 | to the search path and return the actual module name that is expected. 55 | """ 56 | module = [] 57 | 58 | # Chop off file extensions or package markers 59 | if os.path.split(filename)[1] == '__init__.py': 60 | filename = os.path.dirname(filename) 61 | elif filename.endswith('.py'): 62 | filename = filename[:-3] 63 | else: 64 | raise NoAppException('The file provided (%s) does exist but is not a ' 65 | 'valid Python file. This means that it cannot ' 66 | 'be used as application. Please change the ' 67 | 'extension to .py' % filename) 68 | filename = os.path.realpath(filename) 69 | 70 | dirpath = filename 71 | while 1: 72 | dirpath, extra = os.path.split(dirpath) 73 | module.append(extra) 74 | if not os.path.isfile(os.path.join(dirpath, '__init__.py')): 75 | break 76 | 77 | sys.path.insert(0, dirpath) 78 | return '.'.join(module[::-1]) 79 | 80 | 81 | def locate_app(app_id): 82 | """Attempts to locate the application.""" 83 | __traceback_hide__ = True 84 | if ':' in app_id: 85 | module, app_obj = app_id.split(':', 1) 86 | else: 87 | module = app_id 88 | app_obj = None 89 | 90 | __import__(module) 91 | mod = sys.modules[module] 92 | if app_obj is None: 93 | app = find_best_app(mod) 94 | else: 95 | app = getattr(mod, app_obj, None) 96 | if app is None: 97 | raise RuntimeError('Failed to find application in module "%s"' 98 | % module) 99 | 100 | return app 101 | 102 | 103 | def find_default_import_path(): 104 | app = os.environ.get('FLASK_APP') 105 | if app is None: 106 | return 107 | if os.path.isfile(app): 108 | return prepare_exec_for_file(app) 109 | return app 110 | 111 | 112 | def get_version(ctx, param, value): 113 | if not value or ctx.resilient_parsing: 114 | return 115 | message = 'Flask %(version)s\nPython %(python_version)s' 116 | click.echo(message % { 117 | 'version': __flask_version__, 118 | 'python_version': sys.version, 119 | }, color=ctx.color) 120 | ctx.exit() 121 | 122 | version_option = click.Option(['--version'], 123 | help='Show the flask version', 124 | expose_value=False, 125 | callback=get_version, 126 | is_flag=True, is_eager=True) 127 | 128 | 129 | class DispatchingApp(object): 130 | """Special application that dispatches to a flask application which 131 | is imported by name in a background thread. If an error happens 132 | it is is recorded and shows as part of the WSGI handling which in case 133 | of the Werkzeug debugger means that it shows up in the browser. 134 | """ 135 | 136 | def __init__(self, loader, use_eager_loading=False): 137 | self.loader = loader 138 | self._app = None 139 | self._lock = Lock() 140 | self._bg_loading_exc_info = None 141 | if use_eager_loading: 142 | self._load_unlocked() 143 | else: 144 | self._load_in_background() 145 | 146 | def _load_in_background(self): 147 | def _load_app(): 148 | __traceback_hide__ = True 149 | with self._lock: 150 | try: 151 | self._load_unlocked() 152 | except Exception: 153 | self._bg_loading_exc_info = sys.exc_info() 154 | t = Thread(target=_load_app, args=()) 155 | t.start() 156 | 157 | def _flush_bg_loading_exception(self): 158 | __traceback_hide__ = True 159 | exc_info = self._bg_loading_exc_info 160 | if exc_info is not None: 161 | self._bg_loading_exc_info = None 162 | reraise(*exc_info) 163 | 164 | def _load_unlocked(self): 165 | __traceback_hide__ = True 166 | self._app = rv = self.loader() 167 | self._bg_loading_exc_info = None 168 | return rv 169 | 170 | def __call__(self, environ, start_response): 171 | __traceback_hide__ = True 172 | if self._app is not None: 173 | return self._app(environ, start_response) 174 | self._flush_bg_loading_exception() 175 | with self._lock: 176 | if self._app is not None: 177 | rv = self._app 178 | else: 179 | rv = self._load_unlocked() 180 | return rv(environ, start_response) 181 | 182 | 183 | class ScriptInfo(object): 184 | """Help object to deal with Flask applications. This is usually not 185 | necessary to interface with as it's used internally in the dispatching 186 | to click. In future versions of Flask this object will most likely play 187 | a bigger role. Typically it's created automatically by the 188 | :class:`FlaskGroup` but you can also manually create it and pass it 189 | onwards as click object. 190 | """ 191 | def __init__(self, app_import_path=None, create_app=None): 192 | if create_app is None: 193 | if app_import_path is None: 194 | app_import_path = find_default_import_path() 195 | self.app_import_path = app_import_path 196 | else: 197 | app_import_path = None 198 | 199 | #: Optionally the import path for the Flask application. 200 | self.app_import_path = app_import_path 201 | #: Optionally a function that is passed the script info to create 202 | #: the instance of the application. 203 | self.create_app = create_app 204 | #: A dictionary with arbitrary data that can be associated with 205 | #: this script info. 206 | self.data = {} 207 | self._loaded_app = None 208 | 209 | def load_app(self): 210 | """Loads the Flask app (if not yet loaded) and returns it. Calling 211 | this multiple times will just result in the already loaded app to 212 | be returned. 213 | """ 214 | __traceback_hide__ = True 215 | if self._loaded_app is not None: 216 | return self._loaded_app 217 | if self.create_app is not None: 218 | rv = self.create_app(self) 219 | else: 220 | if not self.app_import_path: 221 | raise NoAppException( 222 | 'Could not locate Flask application. You did not provide ' 223 | 'the FLASK_APP environment variable.\n\nFor more ' 224 | 'information see ' 225 | 'http://flask.pocoo.org/docs/latest/quickstart/') 226 | rv = locate_app(self.app_import_path) 227 | debug = get_debug_flag() 228 | if debug is not None: 229 | rv.debug = debug 230 | self._loaded_app = rv 231 | return rv 232 | 233 | 234 | pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) 235 | 236 | 237 | def with_appcontext(f): 238 | """Wraps a callback so that it's guaranteed to be executed with the 239 | script's application context. If callbacks are registered directly 240 | to the ``app.cli`` object then they are wrapped with this function 241 | by default unless it's disabled. 242 | """ 243 | @click.pass_context 244 | def decorator(__ctx, *args, **kwargs): 245 | with __ctx.ensure_object(ScriptInfo).load_app().app_context(): 246 | return __ctx.invoke(f, *args, **kwargs) 247 | return update_wrapper(decorator, f) 248 | 249 | 250 | class AppGroup(click.Group): 251 | """This works similar to a regular click :class:`~click.Group` but it 252 | changes the behavior of the :meth:`command` decorator so that it 253 | automatically wraps the functions in :func:`with_appcontext`. 254 | 255 | Not to be confused with :class:`FlaskGroup`. 256 | """ 257 | 258 | def command(self, *args, **kwargs): 259 | """This works exactly like the method of the same name on a regular 260 | :class:`click.Group` but it wraps callbacks in :func:`with_appcontext` 261 | unless it's disabled by passing ``with_appcontext=False``. 262 | """ 263 | wrap_for_ctx = kwargs.pop('with_appcontext', True) 264 | def decorator(f): 265 | if wrap_for_ctx: 266 | f = with_appcontext(f) 267 | return click.Group.command(self, *args, **kwargs)(f) 268 | return decorator 269 | 270 | def group(self, *args, **kwargs): 271 | """This works exactly like the method of the same name on a regular 272 | :class:`click.Group` but it defaults the group class to 273 | :class:`AppGroup`. 274 | """ 275 | kwargs.setdefault('cls', AppGroup) 276 | return click.Group.group(self, *args, **kwargs) 277 | 278 | 279 | class FlaskGroup(AppGroup): 280 | """Special subclass of the :class:`AppGroup` group that supports 281 | loading more commands from the configured Flask app. Normally a 282 | developer does not have to interface with this class but there are 283 | some very advanced use cases for which it makes sense to create an 284 | instance of this. 285 | 286 | For information as of why this is useful see :ref:`custom-scripts`. 287 | 288 | :param add_default_commands: if this is True then the default run and 289 | shell commands wil be added. 290 | :param add_version_option: adds the :option:`--version` option. 291 | :param create_app: an optional callback that is passed the script info 292 | and returns the loaded app. 293 | """ 294 | 295 | def __init__(self, add_default_commands=True, create_app=None, 296 | add_version_option=True, **extra): 297 | params = list(extra.pop('params', None) or ()) 298 | 299 | if add_version_option: 300 | params.append(version_option) 301 | 302 | AppGroup.__init__(self, params=params, **extra) 303 | self.create_app = create_app 304 | 305 | if add_default_commands: 306 | self.add_command(run_command) 307 | self.add_command(shell_command) 308 | 309 | self._loaded_plugin_commands = False 310 | 311 | def _load_plugin_commands(self): 312 | if self._loaded_plugin_commands: 313 | return 314 | try: 315 | import pkg_resources 316 | except ImportError: 317 | self._loaded_plugin_commands = True 318 | return 319 | 320 | for ep in pkg_resources.iter_entry_points('flask.commands'): 321 | self.add_command(ep.load(), ep.name) 322 | self._loaded_plugin_commands = True 323 | 324 | def get_command(self, ctx, name): 325 | self._load_plugin_commands() 326 | 327 | # We load built-in commands first as these should always be the 328 | # same no matter what the app does. If the app does want to 329 | # override this it needs to make a custom instance of this group 330 | # and not attach the default commands. 331 | # 332 | # This also means that the script stays functional in case the 333 | # application completely fails. 334 | rv = AppGroup.get_command(self, ctx, name) 335 | if rv is not None: 336 | return rv 337 | 338 | info = ctx.ensure_object(ScriptInfo) 339 | try: 340 | rv = info.load_app().cli.get_command(ctx, name) 341 | if rv is not None: 342 | return rv 343 | except NoAppException: 344 | pass 345 | 346 | def list_commands(self, ctx): 347 | self._load_plugin_commands() 348 | 349 | # The commands available is the list of both the application (if 350 | # available) plus the builtin commands. 351 | rv = set(click.Group.list_commands(self, ctx)) 352 | info = ctx.ensure_object(ScriptInfo) 353 | try: 354 | rv.update(info.load_app().cli.list_commands(ctx)) 355 | except Exception: 356 | # Here we intentionally swallow all exceptions as we don't 357 | # want the help page to break if the app does not exist. 358 | # If someone attempts to use the command we try to create 359 | # the app again and this will give us the error. 360 | pass 361 | return sorted(rv) 362 | 363 | def main(self, *args, **kwargs): 364 | obj = kwargs.get('obj') 365 | if obj is None: 366 | obj = ScriptInfo(create_app=self.create_app) 367 | kwargs['obj'] = obj 368 | kwargs.setdefault('auto_envvar_prefix', 'FLASK') 369 | return AppGroup.main(self, *args, **kwargs) 370 | 371 | 372 | @click.command('run', short_help='Runs a development server.') 373 | @click.option('--host', '-h', default='127.0.0.1', 374 | help='The interface to bind to.') 375 | @click.option('--port', '-p', default=5000, 376 | help='The port to bind to.') 377 | @click.option('--reload/--no-reload', default=None, 378 | help='Enable or disable the reloader. By default the reloader ' 379 | 'is active if debug is enabled.') 380 | @click.option('--debugger/--no-debugger', default=None, 381 | help='Enable or disable the debugger. By default the debugger ' 382 | 'is active if debug is enabled.') 383 | @click.option('--eager-loading/--lazy-loader', default=None, 384 | help='Enable or disable eager loading. By default eager ' 385 | 'loading is enabled if the reloader is disabled.') 386 | @click.option('--with-threads/--without-threads', default=False, 387 | help='Enable or disable multithreading.') 388 | @pass_script_info 389 | def run_command(info, host, port, reload, debugger, eager_loading, 390 | with_threads): 391 | """Runs a local development server for the Flask application. 392 | 393 | This local server is recommended for development purposes only but it 394 | can also be used for simple intranet deployments. By default it will 395 | not support any sort of concurrency at all to simplify debugging. This 396 | can be changed with the --with-threads option which will enable basic 397 | multithreading. 398 | 399 | The reloader and debugger are by default enabled if the debug flag of 400 | Flask is enabled and disabled otherwise. 401 | """ 402 | from werkzeug.serving import run_simple 403 | 404 | debug = get_debug_flag() 405 | if reload is None: 406 | reload = bool(debug) 407 | if debugger is None: 408 | debugger = bool(debug) 409 | if eager_loading is None: 410 | eager_loading = not reload 411 | 412 | app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) 413 | 414 | # Extra startup messages. This depends a but on Werkzeug internals to 415 | # not double execute when the reloader kicks in. 416 | if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': 417 | # If we have an import path we can print it out now which can help 418 | # people understand what's being served. If we do not have an 419 | # import path because the app was loaded through a callback then 420 | # we won't print anything. 421 | if info.app_import_path is not None: 422 | print(' * Serving Flask app "%s"' % info.app_import_path) 423 | if debug is not None: 424 | print(' * Forcing debug mode %s' % (debug and 'on' or 'off')) 425 | 426 | run_simple(host, port, app, use_reloader=reload, 427 | use_debugger=debugger, threaded=with_threads) 428 | 429 | 430 | @click.command('shell', short_help='Runs a shell in the app context.') 431 | @with_appcontext 432 | def shell_command(): 433 | """Runs an interactive Python shell in the context of a given 434 | Flask application. The application will populate the default 435 | namespace of this shell according to it's configuration. 436 | 437 | This is useful for executing small snippets of management code 438 | without having to manually configuring the application. 439 | """ 440 | import code 441 | from flask.globals import _app_ctx_stack 442 | app = _app_ctx_stack.top.app 443 | banner = 'Python %s on %s\nApp: %s%s\nInstance: %s' % ( 444 | sys.version, 445 | sys.platform, 446 | app.import_name, 447 | app.debug and ' [debug]' or '', 448 | app.instance_path, 449 | ) 450 | ctx = {} 451 | 452 | # Support the regular Python interpreter startup script if someone 453 | # is using it. 454 | startup = os.environ.get('PYTHONSTARTUP') 455 | if startup and os.path.isfile(startup): 456 | with open(startup, 'r') as f: 457 | eval(compile(f.read(), startup, 'exec'), ctx) 458 | 459 | ctx.update(app.make_shell_context()) 460 | 461 | code.interact(banner=banner, local=ctx) 462 | 463 | 464 | cli = FlaskGroup(help="""\ 465 | This shell command acts as general utility script for Flask applications. 466 | 467 | It loads the application configured (either through the FLASK_APP environment 468 | variable) and then provides commands either provided by the application or 469 | Flask itself. 470 | 471 | The most useful commands are the "run" and "shell" command. 472 | 473 | Example usage: 474 | 475 | \b 476 | %(prefix)s%(cmd)s FLASK_APP=hello.py 477 | %(prefix)s%(cmd)s FLASK_DEBUG=1 478 | %(prefix)sflask run 479 | """ % { 480 | 'cmd': os.name == 'posix' and 'export' or 'set', 481 | 'prefix': os.name == 'posix' and '$ ' or '', 482 | }) 483 | 484 | 485 | def main(as_module=False): 486 | this_module = __package__ + '.cli' 487 | args = sys.argv[1:] 488 | 489 | if as_module: 490 | if sys.version_info >= (2, 7): 491 | name = 'python -m ' + this_module.rsplit('.', 1)[0] 492 | else: 493 | name = 'python -m ' + this_module 494 | 495 | # This module is always executed as "python -m flask.run" and as such 496 | # we need to ensure that we restore the actual command line so that 497 | # the reloader can properly operate. 498 | sys.argv = ['-m', this_module] + sys.argv[1:] 499 | else: 500 | name = None 501 | 502 | cli.main(args=args, prog_name=name) 503 | 504 | 505 | if __name__ == '__main__': 506 | main(as_module=True) 507 | -------------------------------------------------------------------------------- /flask_cli/ext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Flask extension to enable CLI.""" 11 | 12 | import types 13 | 14 | from . import AppGroup 15 | 16 | 17 | class FlaskCLI(object): 18 | """Flask-CLI extension. 19 | 20 | Initialization of the extension: 21 | 22 | >>> from flask import Flask 23 | >>> from flask_cli import FlaskCLI 24 | >>> app = Flask('myapp') 25 | >>> FlaskCLI(app) 26 | 27 | or alternatively using the factory pattern: 28 | 29 | >>> app = Flask('myapp') 30 | >>> ext = FlaskCLI() 31 | >>> ext.init_app(app) 32 | """ 33 | 34 | def __init__(self, app=None): 35 | """Initialize the Flask-CLI.""" 36 | if app is not None: 37 | self.init_app(app) 38 | 39 | def init_app(self, app): 40 | """Initialize a Flask application.""" 41 | # Follow the Flask guidelines on usage of app.extensions 42 | if not hasattr(app, 'extensions'): 43 | app.extensions = {} 44 | if 'flask-cli' in app.extensions: 45 | raise RuntimeError("Flask-CLI application already initialized") 46 | app.extensions['flask-cli'] = self 47 | self.setup_pre10(app) 48 | 49 | def setup_pre10(self, app): 50 | """Setup Flask pre-1.0 application object.""" 51 | if hasattr(app, 'cli'): 52 | return 53 | 54 | from flask_cli.app import make_shell_context, shell_context_processor 55 | app.cli = AppGroup(app.name) 56 | app.shell_context_processors = [] 57 | app.make_shell_context = types.MethodType(make_shell_context, app) 58 | app.shell_context_processor = types.MethodType( 59 | shell_context_processor, app) 60 | -------------------------------------------------------------------------------- /flask_cli/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | from flask.helpers import get_debug_flag 4 | except ImportError: 5 | 6 | def get_debug_flag(default=None): 7 | val = os.environ.get('FLASK_DEBUG') 8 | if not val: 9 | return default 10 | return val not in ('0', 'false', 'no') 11 | -------------------------------------------------------------------------------- /flask_cli/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2016 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Version information for Flask-CLI. 11 | 12 | This file is imported by ``flask_cli.__init__``, and parsed by 13 | ``setup.py`` as well as ``docs/conf.py``. 14 | """ 15 | 16 | from __future__ import absolute_import, print_function 17 | 18 | # Do not change the format of this next line. Doing so risks breaking 19 | # setup.py and docs/conf.py 20 | 21 | __version__ = "0.4.1.dev20160629" 22 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | [pytest] 10 | addopts = --ignore=docs --cov=flask_cli --cov-report=term-missing 11 | -------------------------------------------------------------------------------- /requirements.devel.txt: -------------------------------------------------------------------------------- 1 | -e git+https://github.com/mitsuhiko/flask.git#egg=Flask 2 | -e git+https://github.com/mitsuhiko/click.git#egg=click 3 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | pydocstyle flask_cli/__init__.py flask_cli/ext.py flask_cli/version.py \ 10 | flask_cli/version.py tests/*.py && \ 11 | # isort -rc -c -df **/*.py && \ 12 | check-manifest --ignore ".travis-*" && \ 13 | sphinx-build -qnNW docs docs/_build/html && \ 14 | python setup.py test && \ 15 | sphinx-build -qnNW -b doctest docs docs/_build/doctest 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | [wheel] 10 | universal = 1 11 | 12 | [pytest] 13 | norecursedirs = .* *.egg *.egg-info env* artwork docs 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | """Backport of Flask 1.0 new click integration.""" 10 | 11 | import os 12 | import re 13 | import sys 14 | 15 | from setuptools import setup 16 | from setuptools.command.test import test as TestCommand 17 | 18 | 19 | class PyTest(TestCommand): 20 | """Integration of PyTest with setuptools.""" 21 | 22 | user_options = [('pytest-args=', 'a', 'Arguments to pass to py.test')] 23 | 24 | def initialize_options(self): 25 | """Initialize options.""" 26 | TestCommand.initialize_options(self) 27 | try: 28 | from ConfigParser import ConfigParser 29 | except ImportError: 30 | from configparser import ConfigParser 31 | config = ConfigParser() 32 | config.read("pytest.ini") 33 | self.pytest_args = config.get("pytest", "addopts").split(" ") 34 | 35 | def finalize_options(self): 36 | """Finalize options.""" 37 | TestCommand.finalize_options(self) 38 | if hasattr(self, '_test_args'): 39 | self.test_suite = '' 40 | else: 41 | self.test_args = [] 42 | self.test_suite = True 43 | 44 | def run_tests(self): 45 | """Run tests.""" 46 | # import here, cause outside the eggs aren't loaded 47 | import pytest 48 | errno = pytest.main(self.pytest_args) 49 | sys.exit(errno) 50 | 51 | # Get the version string. Cannot be done with import! 52 | with open(os.path.join('flask_cli', 'version.py'), 'rt') as f: 53 | version = re.search( 54 | '__version__\s*=\s*"(?P.*)"\n', 55 | f.read() 56 | ).group('version') 57 | 58 | 59 | tests_require = [ 60 | 'check-manifest>=0.25', 61 | 'coverage>=4.0', 62 | 'isort>=4.2.2', 63 | 'pydocstyle>=1.0.0', 64 | 'pytest-cache>=1.0', 65 | 'pytest-cov>=1.8.0', 66 | 'pytest-pep8>=1.0.6', 67 | 'pytest>=2.8.0', 68 | ] 69 | 70 | extras_require = { 71 | 'docs': [ 72 | 'Sphinx>=1.3', 73 | ], 74 | 'tests': tests_require, 75 | } 76 | 77 | extras_require['all'] = [] 78 | for reqs in extras_require.values(): 79 | extras_require['all'].extend(reqs) 80 | 81 | setup( 82 | name='Flask-CLI', 83 | version=version, 84 | url='http://github.com/inveniosoftware/flask-cli/', 85 | license='BSD', 86 | author='Invenio Collaboration', 87 | author_email='info@inveniosoftware.org', 88 | description=__doc__, 89 | long_description=open('README.rst').read(), 90 | packages=['flask_cli', ], 91 | include_package_data=True, 92 | zip_safe=False, 93 | platforms='any', 94 | install_requires=[ 95 | 'Flask>=0.10', 96 | 'click>=2.0', 97 | ], 98 | extras_require=extras_require, 99 | tests_require=tests_require, 100 | cmdclass={'test': PyTest}, 101 | classifiers=[ 102 | 'Programming Language :: Python :: 2', 103 | 'Programming Language :: Python :: 2.6', 104 | 'Programming Language :: Python :: 2.7', 105 | 'Programming Language :: Python :: 3', 106 | 'Programming Language :: Python :: 3.3', 107 | 'Programming Language :: Python :: 3.4', 108 | 'Programming Language :: Python :: 3.5', 109 | 'Topic :: Utilities', 110 | 'Development Status :: 5 - Production/Stable', 111 | ], 112 | entry_points={ 113 | 'console_scripts': [ 114 | 'flask = flask_cli.cli:main', 115 | ] 116 | }, 117 | ) 118 | -------------------------------------------------------------------------------- /tests/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Flask application fixture.""" 11 | 12 | from __future__ import absolute_import, print_function 13 | 14 | from flask import Flask 15 | 16 | testapp = Flask('testapp') 17 | -------------------------------------------------------------------------------- /tests/multiapp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Flask applications fixture.""" 11 | 12 | from __future__ import absolute_import, print_function 13 | 14 | from flask import Flask 15 | 16 | app1 = Flask('app1') 17 | app2 = Flask('app2') 18 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Tests for Flask backport.""" 11 | 12 | from __future__ import absolute_import, print_function 13 | 14 | import click 15 | import pytest 16 | from click.testing import CliRunner 17 | from flask import Flask, current_app 18 | 19 | from flask_cli.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ 20 | find_best_app, locate_app, with_appcontext, prepare_exec_for_file 21 | from flask_cli.ext import FlaskCLI 22 | 23 | 24 | def test_cli_name(): 25 | """Test the name of the CLI.""" 26 | from app import testapp 27 | FlaskCLI(testapp) 28 | assert testapp.cli.name == testapp.name 29 | 30 | 31 | def test_find_best_app(): 32 | """Test of find_best_app.""" 33 | class mod: 34 | app = Flask('appname') 35 | assert find_best_app(mod) == mod.app 36 | 37 | class mod: 38 | application = Flask('appname') 39 | assert find_best_app(mod) == mod.application 40 | 41 | class mod: 42 | myapp = Flask('appname') 43 | assert find_best_app(mod) == mod.myapp 44 | 45 | class mod: 46 | myapp = Flask('appname') 47 | myapp2 = Flask('appname2') 48 | 49 | pytest.raises(NoAppException, find_best_app, mod) 50 | 51 | 52 | def test_prepare_exec_for_file(): 53 | """Test of prepare_exec_for_file.""" 54 | assert prepare_exec_for_file('test.py') == 'test' 55 | assert prepare_exec_for_file('/usr/share/__init__.py') == 'share' 56 | with pytest.raises(NoAppException): 57 | prepare_exec_for_file('test.txt') 58 | 59 | 60 | def test_locate_app(): 61 | """Test of locate_app.""" 62 | assert locate_app("app").name == "testapp" 63 | assert locate_app("app:testapp").name == "testapp" 64 | assert locate_app("multiapp:app1").name == "app1" 65 | pytest.raises(RuntimeError, locate_app, "app:notanapp") 66 | 67 | 68 | def test_scriptinfo(): 69 | """Test of ScriptInfo.""" 70 | obj = ScriptInfo(app_import_path="app:testapp") 71 | assert obj.load_app().name == "testapp" 72 | assert obj.load_app().name == "testapp" 73 | 74 | def create_app(info): 75 | return Flask("createapp") 76 | 77 | obj = ScriptInfo(create_app=create_app) 78 | app = obj.load_app() 79 | assert app.name == "createapp" 80 | assert obj.load_app() == app 81 | 82 | 83 | def test_with_appcontext(): 84 | """Test of with_appcontext.""" 85 | @click.command() 86 | @with_appcontext 87 | def testcmd(): 88 | click.echo(current_app.name) 89 | 90 | obj = ScriptInfo(create_app=lambda info: Flask("testapp")) 91 | 92 | runner = CliRunner() 93 | result = runner.invoke(testcmd, obj=obj) 94 | assert result.exit_code == 0 95 | assert result.output == 'testapp\n' 96 | 97 | 98 | def test_appgroup(): 99 | """Test of with_appcontext.""" 100 | @click.group(cls=AppGroup) 101 | def cli(): 102 | pass 103 | 104 | @cli.command(with_appcontext=True) 105 | def test(): 106 | click.echo(current_app.name) 107 | 108 | @cli.group() 109 | def subgroup(): 110 | pass 111 | 112 | @subgroup.command(with_appcontext=True) 113 | def test2(): 114 | click.echo(current_app.name) 115 | 116 | obj = ScriptInfo(create_app=lambda info: Flask("testappgroup")) 117 | 118 | runner = CliRunner() 119 | result = runner.invoke(cli, ['test'], obj=obj) 120 | assert result.exit_code == 0 121 | assert result.output == 'testappgroup\n' 122 | 123 | result = runner.invoke(cli, ['subgroup', 'test2'], obj=obj) 124 | assert result.exit_code == 0 125 | assert result.output == 'testappgroup\n' 126 | 127 | 128 | def test_flaskgroup(): 129 | """Test FlaskGroup.""" 130 | def create_app(info): 131 | return Flask("flaskgroup") 132 | 133 | @click.group(cls=FlaskGroup, create_app=create_app) 134 | def cli(**params): 135 | pass 136 | 137 | @cli.command() 138 | def test(): 139 | click.echo(current_app.name) 140 | 141 | runner = CliRunner() 142 | result = runner.invoke(cli, ['test']) 143 | assert result.exit_code == 0 144 | assert result.output == 'flaskgroup\n' 145 | -------------------------------------------------------------------------------- /tests/test_ext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-AppFactory is free software; you can redistribute it and/or 7 | # modify it under the terms of the Revised BSD License; see LICENSE 8 | # file for more details. 9 | 10 | """Tests for extension.""" 11 | 12 | from __future__ import absolute_import, print_function 13 | 14 | import click 15 | import pytest 16 | from click.testing import CliRunner 17 | from flask import Flask, current_app 18 | 19 | from flask_cli import FlaskCLI, ScriptInfo 20 | 21 | 22 | def test_ext_init(): 23 | """Test of find_best_app.""" 24 | app = Flask('exttest') 25 | FlaskCLI(app) 26 | assert isinstance(app.cli, click.Group) 27 | 28 | app = Flask('exttest') 29 | ext = FlaskCLI() 30 | ext.init_app(app) 31 | assert isinstance(app.cli, click.Group) 32 | assert app.shell_context_processors == [] 33 | pytest.raises(RuntimeError, ext.init_app, app) 34 | 35 | 36 | def test_ext_shelcontext(): 37 | """Test creating commands.""" 38 | app = Flask('exttest') 39 | cli = FlaskCLI(app) 40 | 41 | @app.shell_context_processor 42 | def myshellctx(): 43 | return {'cli': cli} 44 | 45 | assert len(app.shell_context_processors) == 1 46 | rv = app.make_shell_context() 47 | assert rv['app'] == app 48 | assert rv['cli'] == cli 49 | assert 'g' in rv 50 | 51 | 52 | def test_ext_cmd(): 53 | """Test creating commands.""" 54 | app = Flask('exttest') 55 | FlaskCLI(app) 56 | 57 | @app.cli.command() 58 | def test1(): 59 | click.echo("TEST") 60 | 61 | @app.cli.command(with_appcontext=True) 62 | def test2(): 63 | click.echo(current_app.name) 64 | 65 | obj = ScriptInfo(create_app=lambda info: app) 66 | runner = CliRunner() 67 | result = runner.invoke(test1, obj=obj) 68 | assert result.exit_code == 0 69 | assert result.output == 'TEST\n' 70 | 71 | obj = ScriptInfo(create_app=lambda info: app) 72 | runner = CliRunner() 73 | result = runner.invoke(test2, obj=obj) 74 | assert result.exit_code == 0 75 | assert result.output == 'exttest\n' 76 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of Flask-CLI 4 | # Copyright (C) 2015 CERN. 5 | # 6 | # Flask-CLI is free software; you can redistribute it and/or modify it under 7 | # the terms of the Revised BSD License; see LICENSE file for more details. 8 | 9 | [tox] 10 | envlist = {py27,py33,py34,py35}-{lowest,latest,devel} 11 | 12 | [testenv] 13 | commands = 14 | python setup.py test 15 | 16 | deps= 17 | pytest 18 | 19 | lowest: Flask==0.10 20 | lowest: click==2.0 21 | latest: Flask 22 | latest: click 23 | devel: git+https://github.com/mitsuhiko/flask.git 24 | devel: git+https://github.com/mitsuhiko/click.git 25 | --------------------------------------------------------------------------------