Use Click instead of Flask-Script for CLI commands

- Update Flask to 0.11 (with built-in Click integration)
- Remove manage.py
- Add autoapp.py per Flask doc's new recommendations
  for using Click with an app factory
- Define cookiecutter-flask's commands in cli.py
- Register shell context and shell commands as part
  of factory-based app creation
- Update README instructions with examples for running
  commands in new Click style
master
Jeremy Epstein 8 years ago
parent b600aa2907
commit 3d8f8c3c9e
  1. 8
      README.rst
  2. 16
      {{cookiecutter.app_name}}/README.rst
  3. 77
      {{cookiecutter.app_name}}/manage.py
  4. 3
      {{cookiecutter.app_name}}/requirements/dev.txt
  5. 9
      {{cookiecutter.app_name}}/requirements/prod.txt
  6. 17
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py
  7. 10
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/autoapp.py
  8. 17
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/cli.py
  9. 2
      {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py

@ -30,7 +30,7 @@ Features
- Flask-Bcrypt for password hashing
- Procfile for deploying to a PaaS (e.g. Heroku)
- pytest and Factory-Boy for testing (example tests included)
- A simple ``manage.py`` script.
- Flask's Click CLI configured with simple commands
- CSS and JS minification using Flask-Assets
- Optional bower support for frontend package management
- Caching using Flask-Cache
@ -70,6 +70,12 @@ BSD licensed.
Changelog
---------
0.10.0 (08/24/2016)
*******************
- Update to Flask 0.11.
- Use Click instead of Flask-Script for CLI commands.
0.9.0 (03/06/2016)
******************

@ -42,6 +42,10 @@ Once you have installed your DBMS, run the following to create your app's databa
Deployment
----------
Before running shell commands, set the ``FLASK_APP`` environment variable ::
export FLASK_APP="{{cookiecutter.app_name}}.autoapp"
In your production environment, make sure the ``{{cookiecutter.app_name|upper}}_ENV`` environment variable is set to ``"prod"``.
@ -50,9 +54,9 @@ Shell
To open the interactive shell, run ::
python manage.py shell
flask shell
By default, you will have access to ``app``, ``db``, and the ``User`` model.
By default, you will have access to ``app``, ``db``, ``g``, and the ``User`` model.
Running Tests
@ -60,7 +64,7 @@ Running Tests
To run all tests, run ::
python manage.py test
flask test
Migrations
@ -69,13 +73,13 @@ Migrations
Whenever a database migration needs to be made. Run the following commands:
::
python manage.py db migrate
flask db migrate
This will generate a new migration script. Then run:
::
python manage.py db upgrade
flask db upgrade
To apply the migration.
For a full migration command reference, run ``python manage.py db --help``.
For a full migration command reference, run ``flask db --help``.

@ -1,77 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Management script."""
import os
from glob import glob
from subprocess import call
from flask_migrate import Migrate, MigrateCommand
from flask_script import Command, Manager, Option, Server, Shell
from flask_script.commands import Clean, ShowUrls
from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.database import db
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
from {{cookiecutter.app_name}}.user.models import User
CONFIG = ProdConfig if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod' else DevConfig
HERE = os.path.abspath(os.path.dirname(__file__))
TEST_PATH = os.path.join(HERE, 'tests')
app = create_app(CONFIG)
manager = Manager(app)
migrate = Migrate(app, db)
def _make_context():
"""Return context dict for a shell session so you can access app, db, and the User model by default."""
return {'app': app, 'db': db, 'User': User}
@manager.command
def test():
"""Run the tests."""
import pytest
exit_code = pytest.main([TEST_PATH, '--verbose'])
return exit_code
class Lint(Command):
"""Lint and check code style with flake8 and isort."""
def get_options(self):
"""Command line options."""
return (
Option('-f', '--fix-imports', action='store_true', dest='fix_imports', default=False,
help='Fix imports using isort, before linting'),
)
def run(self, fix_imports):
"""Run command."""
skip = ['requirements']
root_files = glob('*.py')
root_directories = [name for name in next(os.walk('.'))[1] if not name.startswith('.')]
files_and_directories = [arg for arg in root_files + root_directories if arg not in skip]
def execute_tool(description, *args):
"""Execute a checking tool with its arguments."""
command_line = list(args) + files_and_directories
print('{}: {}'.format(description, ' '.join(command_line)))
rv = call(command_line)
if rv is not 0:
exit(rv)
if fix_imports:
execute_tool('Fixing import order', 'isort', '-rc')
execute_tool('Checking code style', 'flake8')
manager.add_command('server', Server())
manager.add_command('shell', Shell(make_context=_make_context))
manager.add_command('db', MigrateCommand)
manager.add_command('urls', ShowUrls())
manager.add_command('clean', Clean())
manager.add_command('lint', Lint())
if __name__ == '__main__':
manager.run()

@ -6,9 +6,6 @@ pytest==2.9.0
WebTest==2.0.20
factory-boy==2.6.1
# Management script
Flask-Script==2.0.5
# Lint and code style
flake8==2.5.4
flake8-blind-except==0.1.0

@ -4,11 +4,12 @@ setuptools==20.2.2
wheel==0.29.0
# Flask
Flask==0.10.1
Flask==0.11.1
MarkupSafe==0.23
Werkzeug==0.11.4
Jinja2==2.8
itsdangerous==0.24
click>=5.0
# Database
Flask-SQLAlchemy==2.1
@ -16,7 +17,7 @@ psycopg2==2.6.1
SQLAlchemy==1.0.12
# Migrations
Flask-Migrate==1.8.0
Flask-Migrate==2.0.0
# Forms
Flask-WTF==0.12
@ -26,7 +27,7 @@ WTForms==2.1
gunicorn>=19.1.1
# Assets
Flask-Assets==0.11
Flask-Assets==0.12
cssmin>=0.2.0
jsmin>=2.0.11
@ -35,7 +36,7 @@ Flask-Login==0.3.2
Flask-Bcrypt==0.7.1
# Caching
Flask-Cache>=0.13.1
Flask-Caching>=1.0.0
# Debug toolbar
Flask-DebugToolbar==0.10.0

@ -18,6 +18,8 @@ def create_app(config_object=ProdConfig):
register_extensions(app)
register_blueprints(app)
register_errorhandlers(app)
register_shellcontext(app)
register_commands(app)
return app
@ -51,3 +53,18 @@ def register_errorhandlers(app):
for errcode in [401, 404, 500]:
app.errorhandler(errcode)(render_error)
return None
def register_shellcontext(app):
"""Register shell context objects."""
def shell_context():
"""Shell context objects."""
return {
'db': db}
app.shell_context_processor(shell_context)
def register_commands(app):
"""Register Click commands."""
app.cli.add_command(cli.test_command)

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""Create an application instance."""
import os
from {{cookiecutter.app_name}}.app import create_app
from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig
CONFIG = ProdConfig if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod' else DevConfig
app = create_app(CONFIG)

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""Click commands."""
import click
import os
from flask.cli import with_appcontext
HERE = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.join(HERE, os.pardir)
TEST_PATH = os.path.join(PROJECT_ROOT, 'tests')
@click.command('test')
@with_appcontext
def test_command():
"""Run the tests."""
import pytest
pytest.main([TEST_PATH, '--verbose'])

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Extensions module. Each extension is initialized in the app factory located in app.py."""
from flask_bcrypt import Bcrypt
from flask_cache import Cache
from flask_caching import Cache
from flask_debugtoolbar import DebugToolbarExtension
from flask_login import LoginManager
from flask_migrate import Migrate

Loading…
Cancel
Save