From 34c6faf6307000e0a63bdf81b0bab03a46437001 Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Fri, 6 Nov 2015 01:42:50 +0100 Subject: [PATCH 1/8] Add a lint command with flake8 and isort --- {{cookiecutter.app_name}}/.isort.cfg | 2 ++ {{cookiecutter.app_name}}/manage.py | 33 ++++++++++++++++++++++------ {{cookiecutter.app_name}}/setup.cfg | 2 ++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 {{cookiecutter.app_name}}/.isort.cfg create mode 100644 {{cookiecutter.app_name}}/setup.cfg diff --git a/{{cookiecutter.app_name}}/.isort.cfg b/{{cookiecutter.app_name}}/.isort.cfg new file mode 100644 index 0000000..8913d96 --- /dev/null +++ b/{{cookiecutter.app_name}}/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +line_length=120 diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py index ed8ad09..6fbb022 100644 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -1,7 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +"""Management script.""" import os -from flask_script import Manager, Shell, Server +from subprocess import call + +from flask_script import Manager, Shell, Server, Command from flask_script.commands import Clean, ShowUrls from flask_migrate import MigrateCommand @@ -10,7 +13,7 @@ from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig from {{cookiecutter.app_name}}.database import db -if os.environ.get("{{cookiecutter.app_name | upper}}_ENV") == 'prod': +if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod': app = create_app(ProdConfig) else: app = create_app(DevConfig) @@ -22,9 +25,7 @@ manager = Manager(app) def _make_context(): - """Return context dict for a shell session so you can access - app, db, and the User model by default. - """ + """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} @@ -36,11 +37,29 @@ def test(): return exit_code +class Lint(Command): + """Lint and check code style.""" + + def run(self): + """Run command.""" + skip = ['requirements'] + files = [name for name in next(os.walk('.'))[1] if not name.startswith('.') and name not in skip] + + command_line = ['isort', '-rc'] + files + print('Fixing import order: %s' % ' '.join(command_line)) + call(command_line) + + command_line = ['flake8'] + files + print('Checking code style: %s' % ' '.join(command_line)) + exit(call(command_line)) + + 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('urls', ShowUrls()) +manager.add_command('clean', Clean()) +manager.add_command('lint', Lint()) if __name__ == '__main__': manager.run() diff --git a/{{cookiecutter.app_name}}/setup.cfg b/{{cookiecutter.app_name}}/setup.cfg new file mode 100644 index 0000000..aa079ec --- /dev/null +++ b/{{cookiecutter.app_name}}/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length=120 From 2986743a8c4da82f0e2d71339ed808384bde65ff Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Fri, 6 Nov 2015 03:10:01 +0100 Subject: [PATCH 2/8] Add an option to fix imports before linting, make all code conform to the linting rules --- {{cookiecutter.app_name}}/README.rst | 2 +- {{cookiecutter.app_name}}/manage.py | 36 +++++++++++----- {{cookiecutter.app_name}}/tests/__init__.py | 1 + {{cookiecutter.app_name}}/tests/conftest.py | 6 ++- {{cookiecutter.app_name}}/tests/factories.py | 16 +++++-- .../tests/test_config.py | 5 ++- {{cookiecutter.app_name}}/tests/test_forms.py | 20 +++++---- .../tests/test_functional.py | 42 +++++++++++-------- .../tests/test_models.py | 23 ++++++---- .../{{cookiecutter.app_name}}/__init__.py | 1 + .../{{cookiecutter.app_name}}/app.py | 22 ++++------ .../{{cookiecutter.app_name}}/assets.py | 21 +++++----- .../{{cookiecutter.app_name}}/compat.py | 8 ++-- .../{{cookiecutter.app_name}}/database.py | 26 ++++++------ .../{{cookiecutter.app_name}}/extensions.py | 20 ++++----- .../public/__init__.py | 3 +- .../{{cookiecutter.app_name}}/public/forms.py | 9 +++- .../{{cookiecutter.app_name}}/public/views.py | 36 ++++++++-------- .../{{cookiecutter.app_name}}/settings.py | 10 ++++- .../user/__init__.py | 2 +- .../{{cookiecutter.app_name}}/user/forms.py | 23 ++++++---- .../{{cookiecutter.app_name}}/user/models.py | 22 ++++++---- .../{{cookiecutter.app_name}}/user/views.py | 9 ++-- .../{{cookiecutter.app_name}}/utils.py | 5 +-- 24 files changed, 215 insertions(+), 153 deletions(-) diff --git a/{{cookiecutter.app_name}}/README.rst b/{{cookiecutter.app_name}}/README.rst index 9e11961..25ac2d3 100644 --- a/{{cookiecutter.app_name}}/README.rst +++ b/{{cookiecutter.app_name}}/README.rst @@ -20,7 +20,7 @@ Then run the following commands to bootstrap your environment. :: - git clone https://github.com/{{cookiecutter.github_username}}/{{ cookiecutter.app_name }} + git clone https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.app_name}} cd {{cookiecutter.app_name}} pip install -r requirements/dev.txt python manage.py server diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py index 6fbb022..eea8d24 100644 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -2,18 +2,19 @@ # -*- coding: utf-8 -*- """Management script.""" import os +from glob import glob from subprocess import call -from flask_script import Manager, Shell, Server, Command -from flask_script.commands import Clean, ShowUrls from flask_migrate import 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}}.user.models import User -from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig from {{cookiecutter.app_name}}.database import db +from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig +from {{cookiecutter.app_name}}.user.models import User -if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod': +if os.environ.get('MYFLASKAPP_ENV') == 'prod': app = create_app(ProdConfig) else: app = create_app(DevConfig) @@ -40,16 +41,29 @@ def test(): class Lint(Command): """Lint and check code style.""" - def run(self): + def get_options(self): + """Command line options.""" + return ( + Option('-f', '--fix', + action='store_true', + dest='fix_imports', + default=False, + help='Fix imports before linting'), + ) + + def run(self, fix_imports): """Run command.""" skip = ['requirements'] - files = [name for name in next(os.walk('.'))[1] if not name.startswith('.') and name not in skip] + root_files = glob('*.py') + root_directories = [name for name in next(os.walk('.'))[1] if not name.startswith('.')] + arguments = [arg for arg in root_files + root_directories if arg not in skip] - command_line = ['isort', '-rc'] + files - print('Fixing import order: %s' % ' '.join(command_line)) - call(command_line) + if fix_imports: + command_line = ['isort', '-rc'] + arguments + print('Fixing import order: %s' % ' '.join(command_line)) + call(command_line) - command_line = ['flake8'] + files + command_line = ['flake8'] + arguments print('Checking code style: %s' % ' '.join(command_line)) exit(call(command_line)) diff --git a/{{cookiecutter.app_name}}/tests/__init__.py b/{{cookiecutter.app_name}}/tests/__init__.py index e69de29..b0c2ab8 100644 --- a/{{cookiecutter.app_name}}/tests/__init__.py +++ b/{{cookiecutter.app_name}}/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the app.""" diff --git a/{{cookiecutter.app_name}}/tests/conftest.py b/{{cookiecutter.app_name}}/tests/conftest.py index 31118e7..f2472b5 100644 --- a/{{cookiecutter.app_name}}/tests/conftest.py +++ b/{{cookiecutter.app_name}}/tests/conftest.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- """Defines fixtures available to all tests.""" -import os import pytest from webtest import TestApp -from {{ cookiecutter.app_name }}.settings import TestConfig from {{cookiecutter.app_name}}.app import create_app from {{cookiecutter.app_name}}.database import db as _db +from {{cookiecutter.app_name}}.settings import TestConfig from .factories import UserFactory @pytest.yield_fixture(scope='function') def app(): + """An application for the tests.""" _app = create_app(TestConfig) ctx = _app.test_request_context() ctx.push() @@ -31,6 +31,7 @@ def testapp(app): @pytest.yield_fixture(scope='function') def db(app): + """A database for the tests.""" _db.app = app with app.app_context(): _db.create_all() @@ -42,6 +43,7 @@ def db(app): @pytest.fixture def user(db): + """A user for the tests.""" user = UserFactory(password='myprecious') db.session.commit() return user diff --git a/{{cookiecutter.app_name}}/tests/factories.py b/{{cookiecutter.app_name}}/tests/factories.py index 10b98ab..33efe7a 100644 --- a/{{cookiecutter.app_name}}/tests/factories.py +++ b/{{cookiecutter.app_name}}/tests/factories.py @@ -1,23 +1,31 @@ # -*- coding: utf-8 -*- -from factory import Sequence, PostGenerationMethodCall +"""Factories to help in tests.""" +from factory import PostGenerationMethodCall, Sequence from factory.alchemy import SQLAlchemyModelFactory -from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.database import db +from {{cookiecutter.app_name}}.user.models import User class BaseFactory(SQLAlchemyModelFactory): + """Base factory.""" class Meta: + """Factory configuration.""" + abstract = True sqlalchemy_session = db.session class UserFactory(BaseFactory): - username = Sequence(lambda n: "user{0}".format(n)) - email = Sequence(lambda n: "user{0}@example.com".format(n)) + """User factory.""" + + username = Sequence(lambda n: 'user{0}'.format(n)) + email = Sequence(lambda n: 'user{0}@example.com'.format(n)) password = PostGenerationMethodCall('set_password', 'example') active = True class Meta: + """Factory configuration.""" + model = User diff --git a/{{cookiecutter.app_name}}/tests/test_config.py b/{{cookiecutter.app_name}}/tests/test_config.py index c945dc7..4223730 100644 --- a/{{cookiecutter.app_name}}/tests/test_config.py +++ b/{{cookiecutter.app_name}}/tests/test_config.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- +"""Test configs.""" from {{cookiecutter.app_name}}.app import create_app -from {{cookiecutter.app_name}}.settings import ProdConfig, DevConfig +from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig def test_production_config(): + """Production config.""" app = create_app(ProdConfig) assert app.config['ENV'] == 'prod' assert app.config['DEBUG'] is False @@ -12,6 +14,7 @@ def test_production_config(): def test_dev_config(): + """Development config.""" app = create_app(DevConfig) assert app.config['ENV'] == 'dev' assert app.config['DEBUG'] is True diff --git a/{{cookiecutter.app_name}}/tests/test_forms.py b/{{cookiecutter.app_name}}/tests/test_forms.py index 53e5bd0..590d07f 100644 --- a/{{cookiecutter.app_name}}/tests/test_forms.py +++ b/{{cookiecutter.app_name}}/tests/test_forms.py @@ -1,38 +1,41 @@ # -*- coding: utf-8 -*- -import pytest +"""Test forms.""" from {{cookiecutter.app_name}}.public.forms import LoginForm from {{cookiecutter.app_name}}.user.forms import RegisterForm -from .factories import UserFactory class TestRegisterForm: + """Register form.""" def test_validate_user_already_registered(self, user): - # Enters username that is already registered + """Enter username that is already registered.""" form = RegisterForm(username=user.username, email='foo@bar.com', - password='example', confirm='example') + password='example', confirm='example') assert form.validate() is False assert 'Username already registered' in form.username.errors def test_validate_email_already_registered(self, user): - # enters email that is already registered + """Enter email that is already registered.""" form = RegisterForm(username='unique', email=user.email, - password='example', confirm='example') + password='example', confirm='example') assert form.validate() is False assert 'Email already registered' in form.email.errors def test_validate_success(self, db): + """Register with success.""" form = RegisterForm(username='newusername', email='new@test.test', - password='example', confirm='example') + password='example', confirm='example') assert form.validate() is True class TestLoginForm: + """Login form.""" def test_validate_success(self, user): + """Login successful.""" user.set_password('example') user.save() form = LoginForm(username=user.username, password='example') @@ -40,12 +43,14 @@ class TestLoginForm: assert form.user == user def test_validate_unknown_username(self, db): + """Unknown username.""" form = LoginForm(username='unknown', password='example') assert form.validate() is False assert 'Unknown username' in form.username.errors assert form.user is None def test_validate_invalid_password(self, user): + """Invalid password.""" user.set_password('example') user.save() form = LoginForm(username=user.username, password='wrongpassword') @@ -53,6 +58,7 @@ class TestLoginForm: assert 'Invalid password' in form.password.errors def test_validate_inactive_user(self, user): + """Inactive user.""" user.active = False user.set_password('example') user.save() diff --git a/{{cookiecutter.app_name}}/tests/test_functional.py b/{{cookiecutter.app_name}}/tests/test_functional.py index ac489cd..6b9151f 100644 --- a/{{cookiecutter.app_name}}/tests/test_functional.py +++ b/{{cookiecutter.app_name}}/tests/test_functional.py @@ -3,19 +3,20 @@ See: http://webtest.readthedocs.org/ """ -import pytest from flask import url_for - from {{cookiecutter.app_name}}.user.models import User + from .factories import UserFactory class TestLoggingIn: + """Login.""" def test_can_log_in_returns_200(self, user, testapp): + """Login successful.""" # Goes to homepage - res = testapp.get("/") + res = testapp.get('/') # Fills out login form in navbar form = res.forms['loginForm'] form['username'] = user.username @@ -25,7 +26,8 @@ class TestLoggingIn: assert res.status_code == 200 def test_sees_alert_on_log_out(self, user, testapp): - res = testapp.get("/") + """Show alert on logout.""" + res = testapp.get('/') # Fills out login form in navbar form = res.forms['loginForm'] form['username'] = user.username @@ -37,8 +39,9 @@ class TestLoggingIn: assert 'You are logged out.' in res def test_sees_error_message_if_password_is_incorrect(self, user, testapp): + """Show error if password is incorrect.""" # Goes to homepage - res = testapp.get("/") + res = testapp.get('/') # Fills out login form, password incorrect form = res.forms['loginForm'] form['username'] = user.username @@ -46,11 +49,12 @@ class TestLoggingIn: # Submits res = form.submit() # sees error - assert "Invalid password" in res + assert 'Invalid password' in res def test_sees_error_message_if_username_doesnt_exist(self, user, testapp): + """Show error if username doesn't exist.""" # Goes to homepage - res = testapp.get("/") + res = testapp.get('/') # Fills out login form, password incorrect form = res.forms['loginForm'] form['username'] = 'unknown' @@ -58,19 +62,21 @@ class TestLoggingIn: # Submits res = form.submit() # sees error - assert "Unknown user" in res + assert 'Unknown user' in res class TestRegistering: + """Register a user.""" def test_can_register(self, user, testapp): + """Register a new user.""" old_count = len(User.query.all()) # Goes to homepage - res = testapp.get("/") + res = testapp.get('/') # Clicks Create Account button - res = res.click("Create account") + res = res.click('Create account') # Fills out the form - form = res.forms["registerForm"] + form = res.forms['registerForm'] form['username'] = 'foobar' form['email'] = 'foo@bar.com' form['password'] = 'secret' @@ -82,10 +88,11 @@ class TestRegistering: assert len(User.query.all()) == old_count + 1 def test_sees_error_message_if_passwords_dont_match(self, user, testapp): + """Show error if passwords don't match.""" # Goes to registration page - res = testapp.get(url_for("public.register")) + res = testapp.get(url_for('public.register')) # Fills out form, but passwords don't match - form = res.forms["registerForm"] + form = res.forms['registerForm'] form['username'] = 'foobar' form['email'] = 'foo@bar.com' form['password'] = 'secret' @@ -93,15 +100,16 @@ class TestRegistering: # Submits res = form.submit() # sees error message - assert "Passwords must match" in res + assert 'Passwords must match' in res def test_sees_error_message_if_user_already_registered(self, user, testapp): + """Show error if user already registered.""" user = UserFactory(active=True) # A registered user user.save() # Goes to registration page - res = testapp.get(url_for("public.register")) + res = testapp.get(url_for('public.register')) # Fills out form, but username is already registered - form = res.forms["registerForm"] + form = res.forms['registerForm'] form['username'] = user.username form['email'] = 'foo@bar.com' form['password'] = 'secret' @@ -109,4 +117,4 @@ class TestRegistering: # Submits res = form.submit() # sees error - assert "Username already registered" in res + assert 'Username already registered' in res diff --git a/{{cookiecutter.app_name}}/tests/test_models.py b/{{cookiecutter.app_name}}/tests/test_models.py index feb7078..d321f5f 100644 --- a/{{cookiecutter.app_name}}/tests/test_models.py +++ b/{{cookiecutter.app_name}}/tests/test_models.py @@ -4,14 +4,17 @@ import datetime as dt import pytest -from {{ cookiecutter.app_name }}.user.models import User, Role +from {{cookiecutter.app_name}}.user.models import Role, User + from .factories import UserFactory @pytest.mark.usefixtures('db') class TestUser: + """User tests.""" def test_get_by_id(self): + """Get user by ID.""" user = User('foo', 'foo@bar.com') user.save() @@ -19,18 +22,21 @@ class TestUser: assert retrieved == user def test_created_at_defaults_to_datetime(self): + """Test creation date.""" user = User(username='foo', email='foo@bar.com') user.save() assert bool(user.created_at) assert isinstance(user.created_at, dt.datetime) def test_password_is_nullable(self): + """Test null password.""" user = User(username='foo', email='foo@bar.com') user.save() assert user.password is None def test_factory(self, db): - user = UserFactory(password="myprecious") + """Test user factory.""" + user = UserFactory(password='myprecious') db.session.commit() assert bool(user.username) assert bool(user.email) @@ -40,16 +46,19 @@ class TestUser: assert user.check_password('myprecious') def test_check_password(self): - user = User.create(username="foo", email="foo@bar.com", - password="foobarbaz123") + """Check password.""" + user = User.create(username='foo', email='foo@bar.com', + password='foobarbaz123') assert user.check_password('foobarbaz123') is True - assert user.check_password("barfoobaz") is False + assert user.check_password('barfoobaz') is False def test_full_name(self): - user = UserFactory(first_name="Foo", last_name="Bar") - assert user.full_name == "Foo Bar" + """User full name.""" + user = UserFactory(first_name='Foo', last_name='Bar') + assert user.full_name == 'Foo Bar' def test_roles(self): + """Add a role to a user.""" role = Role(name='admin') role.save() u = UserFactory() diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py index e69de29..5b52f8c 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py @@ -0,0 +1 @@ +"""Main application package.""" diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py index c945411..62c54ff 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/app.py @@ -2,22 +2,14 @@ """The app module, containing the app factory function.""" from flask import Flask, render_template -from {{cookiecutter.app_name}}.settings import ProdConfig -from {{cookiecutter.app_name}}.assets import assets -from {{cookiecutter.app_name}}.extensions import ( - bcrypt, - cache, - db, - login_manager, - migrate, - debug_toolbar, -) from {{cookiecutter.app_name}} import public, user +from {{cookiecutter.app_name}}.assets import assets +from {{cookiecutter.app_name}}.extensions import bcrypt, cache, db, debug_toolbar, login_manager, migrate +from {{cookiecutter.app_name}}.settings import ProdConfig def create_app(config_object=ProdConfig): - """An application factory, as explained here: - http://flask.pocoo.org/docs/patterns/appfactories/ + """An application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. :param config_object: The configuration object to use. """ @@ -30,6 +22,7 @@ def create_app(config_object=ProdConfig): def register_extensions(app): + """Register Flask extensions.""" assets.init_app(app) bcrypt.init_app(app) cache.init_app(app) @@ -41,16 +34,19 @@ def register_extensions(app): def register_blueprints(app): + """Register Flask blueprints.""" app.register_blueprint(public.views.blueprint) app.register_blueprint(user.views.blueprint) return None def register_errorhandlers(app): + """Register error handlers.""" def render_error(error): + """Render error template.""" # If a HTTPException, pull the `code` attribute; default to 500 error_code = getattr(error, 'code', 500) - return render_template("{0}.html".format(error_code)), error_code + return render_template('{0}.html'.format(error_code)), error_code for errcode in [401, 404, 500]: app.errorhandler(errcode)(render_error) return None diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py index b425a85..833e2e3 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py @@ -1,22 +1,23 @@ # -*- coding: utf-8 -*- +"""Application assets.""" from flask_assets import Bundle, Environment css = Bundle( - "libs/bootstrap/dist/css/bootstrap.css", - "css/style.css", - filters="cssmin", - output="public/css/common.css" + 'libs/bootstrap/dist/css/bootstrap.css', + 'css/style.css', + filters='cssmin', + output='public/css/common.css' ) js = Bundle( - "libs/jQuery/dist/jquery.js", - "libs/bootstrap/dist/js/bootstrap.js", - "js/plugins.js", + 'libs/jQuery/dist/jquery.js', + 'libs/bootstrap/dist/js/bootstrap.js', + 'js/plugins.js', filters='jsmin', - output="public/js/common.js" + output='public/js/common.js' ) assets = Environment() -assets.register("js_all", js) -assets.register("css_all", css) +assets.register('js_all', js) +assets.register('css_all', css) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py index 863f2a0..f749d7c 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py @@ -7,11 +7,11 @@ import sys PY2 = int(sys.version[0]) == 2 if PY2: - text_type = unicode + text_type = unicode # noqa binary_type = str - string_types = (str, unicode) - unicode = unicode - basestring = basestring + string_types = (str, unicode) # noqa + unicode = unicode # noqa + basestring = basestring # noqa else: text_type = str binary_type = bytes diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py index 5d88d4d..8265fb8 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -"""Database module, including the SQLAlchemy database object and DB-related -utilities. -""" +"""Database module, including the SQLAlchemy database object and DB-related utilities.""" from sqlalchemy.orm import relationship -from .extensions import db from .compat import basestring +from .extensions import db # Alias common SQLAlchemy names Column = db.Column @@ -13,9 +11,7 @@ relationship = relationship class CRUDMixin(object): - """Mixin that adds convenience methods for CRUD (create, read, update, delete) - operations. - """ + """Mixin that adds convenience methods for CRUD (create, read, update, delete) operations.""" @classmethod def create(cls, **kwargs): @@ -41,32 +37,34 @@ class CRUDMixin(object): db.session.delete(self) return commit and db.session.commit() + class Model(CRUDMixin, db.Model): """Base model class that includes CRUD convenience methods.""" + __abstract__ = True # From Mike Bayer's "Building the app" talk # https://speakerdeck.com/zzzeek/building-the-app class SurrogatePK(object): - """A mixin that adds a surrogate integer 'primary key' column named - ``id`` to any declarative-mapped class. - """ + """A mixin that adds a surrogate integer 'primary key' column named ``id`` to any declarative-mapped class.""" + __table_args__ = {'extend_existing': True} id = db.Column(db.Integer, primary_key=True) @classmethod def get_by_id(cls, id): + """Get record by ID.""" if any( - (isinstance(id, basestring) and id.isdigit(), - isinstance(id, (int, float))), + (isinstance(id, basestring) and id.isdigit(), + isinstance(id, (int, float))), ): return cls.query.get(int(id)) return None -def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): +def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): # noqa """Column that adds primary key foreign key reference. Usage: :: @@ -75,5 +73,5 @@ def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): category = relationship('Category', backref='categories') """ return db.Column( - db.ForeignKey("{0}.{1}".format(tablename, pk_name)), + db.ForeignKey('{0}.{1}'.format(tablename, pk_name)), nullable=nullable, **kwargs) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py index af8ece3..7d86238 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- -"""Extensions module. Each extension is initialized in the app factory located -in app.py -""" +"""Extensions module. Each extension is initialized in the app factory located in app.py.""" from flask_bcrypt import Bcrypt -bcrypt = Bcrypt() - +from flask_cache import Cache +from flask_debugtoolbar import DebugToolbarExtension from flask_login import LoginManager -login_manager = LoginManager() - +from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy -db = SQLAlchemy() -from flask_migrate import Migrate +bcrypt = Bcrypt() +login_manager = LoginManager() +db = SQLAlchemy() migrate = Migrate() - -from flask_cache import Cache cache = Cache() - -from flask_debugtoolbar import DebugToolbarExtension debug_toolbar = DebugToolbarExtension() diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/__init__.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/__init__.py index 4260a43..234a4e5 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/__init__.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- """The public module, including the homepage and user auth.""" - -from . import views +from . import views # noqa diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py index 43da1de..ed7f1fe 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py @@ -1,20 +1,25 @@ # -*- coding: utf-8 -*- +"""Public forms.""" from flask_wtf import Form -from wtforms import TextField, PasswordField +from wtforms import PasswordField, StringField from wtforms.validators import DataRequired from {{cookiecutter.app_name}}.user.models import User class LoginForm(Form): - username = TextField('Username', validators=[DataRequired()]) + """Login form.""" + + username = StringField('Username', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()]) def __init__(self, *args, **kwargs): + """Create instance.""" super(LoginForm, self).__init__(*args, **kwargs) self.user = None def validate(self): + """Validate the form.""" initial_validation = super(LoginForm, self).validate() if not initial_validation: return False diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py index 879291b..381e1cd 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py @@ -1,63 +1,63 @@ # -*- coding: utf-8 -*- """Public section, including homepage and signup.""" -from flask import (Blueprint, request, render_template, flash, url_for, - redirect, session) -from flask_login import login_user, login_required, logout_user +from flask import Blueprint, flash, redirect, render_template, request, url_for +from flask_login import login_required, login_user, logout_user from {{cookiecutter.app_name}}.extensions import login_manager -from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.public.forms import LoginForm from {{cookiecutter.app_name}}.user.forms import RegisterForm +from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.utils import flash_errors -from {{cookiecutter.app_name}}.database import db -blueprint = Blueprint('public', __name__, static_folder="../static") +blueprint = Blueprint('public', __name__, static_folder='../static') @login_manager.user_loader def load_user(id): + """Load user by ID.""" return User.get_by_id(int(id)) -@blueprint.route("/", methods=["GET", "POST"]) +@blueprint.route('/', methods=['GET', 'POST']) def home(): + """Home page.""" form = LoginForm(request.form) # Handle logging in if request.method == 'POST': if form.validate_on_submit(): login_user(form.user) - flash("You are logged in.", 'success') - redirect_url = request.args.get("next") or url_for("user.members") + flash('You are logged in.', 'success') + redirect_url = request.args.get('next') or url_for('user.members') return redirect(redirect_url) else: flash_errors(form) - return render_template("public/home.html", form=form) + return render_template('public/home.html', form=form) @blueprint.route('/logout/') @login_required def logout(): + """Logout.""" logout_user() flash('You are logged out.', 'info') return redirect(url_for('public.home')) -@blueprint.route("/register/", methods=['GET', 'POST']) +@blueprint.route('/register/', methods=['GET', 'POST']) def register(): + """Register new user.""" form = RegisterForm(request.form, csrf_enabled=False) if form.validate_on_submit(): - new_user = User.create(username=form.username.data, - email=form.email.data, - password=form.password.data, - active=True) - flash("Thank you for registering. You can now log in.", 'success') + User.create(username=form.username.data, email=form.email.data, password=form.password.data, active=True) + flash('Thank you for registering. You can now log in.', 'success') return redirect(url_for('public.home')) else: flash_errors(form) return render_template('public/register.html', form=form) -@blueprint.route("/about/") +@blueprint.route('/about/') def about(): + """About page.""" form = LoginForm(request.form) - return render_template("public/about.html", form=form) + return render_template('public/about.html', form=form) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py index ce70006..f94b81e 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py @@ -1,11 +1,14 @@ # -*- coding: utf-8 -*- +"""Application configuration.""" import os os_env = os.environ class Config(object): - SECRET_KEY = os_env.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me + """Base configuration.""" + + SECRET_KEY = os_env.get('MYFLASKAPP_SECRET', 'secret-key') # TODO: Change me APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir)) BCRYPT_LOG_ROUNDS = 13 @@ -13,10 +16,12 @@ class Config(object): DEBUG_TB_ENABLED = False # Disable Debug toolbar DEBUG_TB_INTERCEPT_REDIRECTS = False CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc. + SQLALCHEMY_TRACK_MODIFICATIONS = False class ProdConfig(Config): """Production configuration.""" + ENV = 'prod' DEBUG = False SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example' # TODO: Change me @@ -25,6 +30,7 @@ class ProdConfig(Config): class DevConfig(Config): """Development configuration.""" + ENV = 'dev' DEBUG = True DB_NAME = 'dev.db' @@ -37,6 +43,8 @@ class DevConfig(Config): class TestConfig(Config): + """Test configuration.""" + TESTING = True DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite://' diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/__init__.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/__init__.py index a655bf1..c11ac5e 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/__init__.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """The user module.""" -from . import views +from . import views # noqa diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py index f4ba798..1ba4166 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py @@ -1,35 +1,40 @@ # -*- coding: utf-8 -*- +"""User forms.""" from flask_wtf import Form -from wtforms import TextField, PasswordField +from wtforms import PasswordField, StringField from wtforms.validators import DataRequired, Email, EqualTo, Length from .models import User class RegisterForm(Form): - username = TextField('Username', - validators=[DataRequired(), Length(min=3, max=25)]) - email = TextField('Email', - validators=[DataRequired(), Email(), Length(min=6, max=40)]) + """Register form.""" + + username = StringField('Username', + validators=[DataRequired(), Length(min=3, max=25)]) + email = StringField('Email', + validators=[DataRequired(), Email(), Length(min=6, max=40)]) password = PasswordField('Password', - validators=[DataRequired(), Length(min=6, max=40)]) + validators=[DataRequired(), Length(min=6, max=40)]) confirm = PasswordField('Verify password', - [DataRequired(), EqualTo('password', message='Passwords must match')]) + [DataRequired(), EqualTo('password', message='Passwords must match')]) def __init__(self, *args, **kwargs): + """Create instance.""" super(RegisterForm, self).__init__(*args, **kwargs) self.user = None def validate(self): + """Validate the form.""" initial_validation = super(RegisterForm, self).validate() if not initial_validation: return False user = User.query.filter_by(username=self.username.data).first() if user: - self.username.errors.append("Username already registered") + self.username.errors.append('Username already registered') return False user = User.query.filter_by(email=self.email.data).first() if user: - self.email.errors.append("Email already registered") + self.email.errors.append('Email already registered') return False return True diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py index 06ad15a..7fb50ef 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py @@ -1,33 +1,32 @@ # -*- coding: utf-8 -*- +"""User models.""" import datetime as dt from flask_login import UserMixin +from {{cookiecutter.app_name}}.database import Column, Model, ReferenceCol, SurrogatePK, db, relationship from {{cookiecutter.app_name}}.extensions import bcrypt -from {{cookiecutter.app_name}}.database import ( - Column, - db, - Model, - ReferenceCol, - relationship, - SurrogatePK, -) class Role(SurrogatePK, Model): + """A role for a user.""" + __tablename__ = 'roles' name = Column(db.String(80), unique=True, nullable=False) user_id = ReferenceCol('users', nullable=True) user = relationship('User', backref='roles') def __init__(self, name, **kwargs): + """Create instance.""" db.Model.__init__(self, name=name, **kwargs) def __repr__(self): + """Represent instance as a unique string.""" return ''.format(name=self.name) class User(UserMixin, SurrogatePK, Model): + """A user of the app.""" __tablename__ = 'users' username = Column(db.String(80), unique=True, nullable=False) @@ -41,6 +40,7 @@ class User(UserMixin, SurrogatePK, Model): is_admin = Column(db.Boolean(), default=False) def __init__(self, username, email, password=None, **kwargs): + """Create instance.""" db.Model.__init__(self, username=username, email=email, **kwargs) if password: self.set_password(password) @@ -48,14 +48,18 @@ class User(UserMixin, SurrogatePK, Model): self.password = None def set_password(self, password): + """Set password.""" self.password = bcrypt.generate_password_hash(password) def check_password(self, value): + """Check password.""" return bcrypt.check_password_hash(self.password, value) @property def full_name(self): - return "{0} {1}".format(self.first_name, self.last_name) + """Full user name.""" + return '{0} {1}'.format(self.first_name, self.last_name) def __repr__(self): + """Represent instance as a unique string.""" return ''.format(username=self.username) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py index ca99cc8..2dff555 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- +"""User views.""" from flask import Blueprint, render_template from flask_login import login_required -blueprint = Blueprint("user", __name__, url_prefix='/users', - static_folder="../static") +blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static') -@blueprint.route("/") +@blueprint.route('/') @login_required def members(): - return render_template("users/members.html") + """List members.""" + return render_template('users/members.html') diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py index 2def30c..1bafe98 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py @@ -3,9 +3,8 @@ from flask import flash -def flash_errors(form, category="warning"): +def flash_errors(form, category='warning'): """Flash all errors for a form.""" for field, errors in form.errors.items(): for error in errors: - flash("{0} - {1}" - .format(getattr(form, field).label.text, error), category) + flash('{0} - {1}'.format(getattr(form, field).label.text, error), category) From 819fd8d700a9dcf4cfd186b2bb8d6941d9ac9557 Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Fri, 6 Nov 2015 03:21:15 +0100 Subject: [PATCH 3/8] Fix environment variables --- {{cookiecutter.app_name}}/manage.py | 2 +- {{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py index eea8d24..3d59f38 100644 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -14,7 +14,7 @@ from {{cookiecutter.app_name}}.database import db from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig from {{cookiecutter.app_name}}.user.models import User -if os.environ.get('MYFLASKAPP_ENV') == 'prod': +if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod': app = create_app(ProdConfig) else: app = create_app(DevConfig) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py index f94b81e..1b0ff97 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py @@ -8,7 +8,7 @@ os_env = os.environ class Config(object): """Base configuration.""" - SECRET_KEY = os_env.get('MYFLASKAPP_SECRET', 'secret-key') # TODO: Change me + SECRET_KEY = os_env.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir)) BCRYPT_LOG_ROUNDS = 13 From 720ead02d392143cf1a4d553236ba557b450acef Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Fri, 6 Nov 2015 03:34:44 +0100 Subject: [PATCH 4/8] Add flake8, isort and ipython to the dev requirements file --- {{cookiecutter.app_name}}/requirements/dev.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/{{cookiecutter.app_name}}/requirements/dev.txt b/{{cookiecutter.app_name}}/requirements/dev.txt index 11f74bb..7c944e5 100644 --- a/{{cookiecutter.app_name}}/requirements/dev.txt +++ b/{{cookiecutter.app_name}}/requirements/dev.txt @@ -5,6 +5,18 @@ pytest==2.8.2 WebTest==2.0.20 factory-boy==2.6.0 +ipdb==0.8.1 +ipython==4.0.0 # Management script Flask-Script==2.0.5 + +# Lint and code style +flake8==2.5.0 +flake8-blind-except==0.1.0 +flake8-debugger==1.4.0 +flake8-docstrings==0.2.1.post1 +flake8-isort==0.2 +flake8-quotes==0.1.1 +isort==4.2.2 +pep8-naming==0.3.3 From e27fa10e158436cbeadb89cfb349eeb9db9d17f4 Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Sat, 7 Nov 2015 18:26:35 +0100 Subject: [PATCH 5/8] Add an option to use pylint after flake8, change code to conform to pylint rules --- {{cookiecutter.app_name}}/.pylintrc | 263 ++++++++++++++++++ {{cookiecutter.app_name}}/manage.py | 43 +-- .../requirements/dev.txt | 1 + {{cookiecutter.app_name}}/tests/conftest.py | 1 + {{cookiecutter.app_name}}/tests/factories.py | 1 + {{cookiecutter.app_name}}/tests/test_forms.py | 1 + .../tests/test_functional.py | 1 + .../tests/test_models.py | 9 +- .../{{cookiecutter.app_name}}/assets.py | 1 + .../{{cookiecutter.app_name}}/compat.py | 3 +- .../{{cookiecutter.app_name}}/database.py | 14 +- .../{{cookiecutter.app_name}}/extensions.py | 2 +- .../{{cookiecutter.app_name}}/public/forms.py | 2 +- .../{{cookiecutter.app_name}}/public/views.py | 6 +- .../{{cookiecutter.app_name}}/settings.py | 4 +- .../{{cookiecutter.app_name}}/user/forms.py | 4 +- .../{{cookiecutter.app_name}}/user/views.py | 2 +- 17 files changed, 313 insertions(+), 45 deletions(-) create mode 100755 {{cookiecutter.app_name}}/.pylintrc mode change 100644 => 100755 {{cookiecutter.app_name}}/manage.py diff --git a/{{cookiecutter.app_name}}/.pylintrc b/{{cookiecutter.app_name}}/.pylintrc new file mode 100755 index 0000000..c8ce3d4 --- /dev/null +++ b/{{cookiecutter.app_name}}/.pylintrc @@ -0,0 +1,263 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook='import os; import sys; print(sys.path)' + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=3 + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +# You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# W0511 - Warning for fix me and to;do +disable=W0511,locally-disabled,pointless-string-statement + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=colorized + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject,SQLAlchemy,scoped_session + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=120 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,id,db,rv,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py old mode 100644 new mode 100755 index 3d59f38..d86f354 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -14,15 +14,12 @@ from {{cookiecutter.app_name}}.database import db from {{cookiecutter.app_name}}.settings import DevConfig, ProdConfig from {{cookiecutter.app_name}}.user.models import User -if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') == 'prod': - app = create_app(ProdConfig) -else: - app = create_app(DevConfig) - +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') -manager = Manager(app) +app = create_app(CONFIG) # pylint: disable=invalid-name +manager = Manager(app) # pylint: disable=invalid-name def _make_context(): @@ -39,33 +36,37 @@ def test(): class Lint(Command): - """Lint and check code style.""" + """Lint and check code style with flake8, isort and, optionally, pylint.""" def get_options(self): """Command line options.""" return ( - Option('-f', '--fix', - action='store_true', - dest='fix_imports', - default=False, - help='Fix imports before linting'), + Option('-f', '--fix-imports', action='store_true', dest='fix_imports', default=False, + help='Fix imports using isort, before linting'), + Option('-p', '--pylint', action='store_true', dest='use_pylint', default=False, + help='Use pylint after flake8, for an extended strict check'), ) - def run(self, fix_imports): + def run(self, fix_imports, use_pylint): # pylint: disable=arguments-differ,method-hidden """Run command.""" skip = ['requirements'] root_files = glob('*.py') root_directories = [name for name in next(os.walk('.'))[1] if not name.startswith('.')] - arguments = [arg for arg in root_files + root_directories if arg not in skip] + files_and_directories = [arg for arg in root_files + root_directories if arg not in skip] - if fix_imports: - command_line = ['isort', '-rc'] + arguments - print('Fixing import order: %s' % ' '.join(command_line)) - call(command_line) + def execute_tool(description, *args): + """Execute a checking tool with its arguments.""" + command_line = list(args) + files_and_directories + print('%s: %s' % (description, ' '.join(command_line))) + rv = call(command_line) + if rv is not 0: + exit(rv) - command_line = ['flake8'] + arguments - print('Checking code style: %s' % ' '.join(command_line)) - exit(call(command_line)) + if fix_imports: + execute_tool('Fixing import order', 'isort', '-rc') + execute_tool('Checking code style', 'flake8') + if use_pylint: + execute_tool('Checking code style', 'pylint', '--rcfile=.pylintrc') manager.add_command('server', Server()) diff --git a/{{cookiecutter.app_name}}/requirements/dev.txt b/{{cookiecutter.app_name}}/requirements/dev.txt index 7c944e5..40fbcca 100644 --- a/{{cookiecutter.app_name}}/requirements/dev.txt +++ b/{{cookiecutter.app_name}}/requirements/dev.txt @@ -20,3 +20,4 @@ flake8-isort==0.2 flake8-quotes==0.1.1 isort==4.2.2 pep8-naming==0.3.3 +pylint==1.4.4 diff --git a/{{cookiecutter.app_name}}/tests/conftest.py b/{{cookiecutter.app_name}}/tests/conftest.py index f2472b5..b0b73ba 100644 --- a/{{cookiecutter.app_name}}/tests/conftest.py +++ b/{{cookiecutter.app_name}}/tests/conftest.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=redefined-outer-name """Defines fixtures available to all tests.""" import pytest diff --git a/{{cookiecutter.app_name}}/tests/factories.py b/{{cookiecutter.app_name}}/tests/factories.py index 33efe7a..794d2a2 100644 --- a/{{cookiecutter.app_name}}/tests/factories.py +++ b/{{cookiecutter.app_name}}/tests/factories.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=unnecessary-lambda """Factories to help in tests.""" from factory import PostGenerationMethodCall, Sequence from factory.alchemy import SQLAlchemyModelFactory diff --git a/{{cookiecutter.app_name}}/tests/test_forms.py b/{{cookiecutter.app_name}}/tests/test_forms.py index 590d07f..ff379bb 100644 --- a/{{cookiecutter.app_name}}/tests/test_forms.py +++ b/{{cookiecutter.app_name}}/tests/test_forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-self-use,no-member,invalid-name,unused-argument """Test forms.""" from {{cookiecutter.app_name}}.public.forms import LoginForm diff --git a/{{cookiecutter.app_name}}/tests/test_functional.py b/{{cookiecutter.app_name}}/tests/test_functional.py index 6b9151f..f683cf7 100644 --- a/{{cookiecutter.app_name}}/tests/test_functional.py +++ b/{{cookiecutter.app_name}}/tests/test_functional.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-self-use,no-member,invalid-name,unused-argument """Functional tests using WebTest. See: http://webtest.readthedocs.org/ diff --git a/{{cookiecutter.app_name}}/tests/test_models.py b/{{cookiecutter.app_name}}/tests/test_models.py index d321f5f..0bbb4b1 100644 --- a/{{cookiecutter.app_name}}/tests/test_models.py +++ b/{{cookiecutter.app_name}}/tests/test_models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-self-use,no-member,invalid-name """Model unit tests.""" import datetime as dt @@ -61,7 +62,7 @@ class TestUser: """Add a role to a user.""" role = Role(name='admin') role.save() - u = UserFactory() - u.roles.append(role) - u.save() - assert role in u.roles + user = UserFactory() + user.roles.append(role) + user.save() + assert role in user.roles diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py index 833e2e3..3483a94 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=invalid-name """Application assets.""" from flask_assets import Bundle, Environment diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py index f749d7c..718d9f9 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=invalid-name,used-before-assignment """Python 2/3 compatibility module.""" - - import sys PY2 = int(sys.version[0]) == 2 diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py index 8265fb8..8de1aec 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py @@ -6,8 +6,8 @@ from .compat import basestring from .extensions import db # Alias common SQLAlchemy names -Column = db.Column -relationship = relationship +Column = db.Column # pylint: disable=invalid-name +relationship = relationship # pylint: disable=invalid-name class CRUDMixin(object): @@ -54,17 +54,17 @@ class SurrogatePK(object): id = db.Column(db.Integer, primary_key=True) @classmethod - def get_by_id(cls, id): + def get_by_id(cls, record_id): """Get record by ID.""" if any( - (isinstance(id, basestring) and id.isdigit(), - isinstance(id, (int, float))), + (isinstance(record_id, basestring) and record_id.isdigit(), + isinstance(record_id, (int, float))), ): - return cls.query.get(int(id)) + return cls.query.get(int(record_id)) # pylint: disable=no-member return None -def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): # noqa +def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): # noqa # pylint: disable=invalid-name """Column that adds primary key foreign key reference. Usage: :: diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py index 7d86238..e8b6e5c 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=invalid-name """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_debugtoolbar import DebugToolbarExtension diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py index ed7f1fe..f76a69d 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py @@ -24,7 +24,7 @@ class LoginForm(Form): if not initial_validation: return False - self.user = User.query.filter_by(username=self.username.data).first() + self.user = User.query.filter_by(username=self.username.data).first() # pylint: disable=no-member if not self.user: self.username.errors.append('Unknown username') return False diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py index 381e1cd..2c6bfc7 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py @@ -9,13 +9,13 @@ from {{cookiecutter.app_name}}.user.forms import RegisterForm from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.utils import flash_errors -blueprint = Blueprint('public', __name__, static_folder='../static') +blueprint = Blueprint('public', __name__, static_folder='../static') # pylint: disable=invalid-name @login_manager.user_loader -def load_user(id): +def load_user(user_id): """Load user by ID.""" - return User.get_by_id(int(id)) + return User.get_by_id(int(user_id)) @blueprint.route('/', methods=['GET', 'POST']) diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py index 1b0ff97..8d070ef 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/settings.py @@ -2,13 +2,11 @@ """Application configuration.""" import os -os_env = os.environ - class Config(object): """Base configuration.""" - SECRET_KEY = os_env.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me + SECRET_KEY = os.environ.get('{{cookiecutter.app_name | upper}}_SECRET', 'secret-key') # TODO: Change me APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir)) BCRYPT_LOG_ROUNDS = 13 diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py index 1ba4166..6ba9956 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py @@ -29,11 +29,11 @@ class RegisterForm(Form): initial_validation = super(RegisterForm, self).validate() if not initial_validation: return False - user = User.query.filter_by(username=self.username.data).first() + user = User.query.filter_by(username=self.username.data).first() # pylint: disable=no-member if user: self.username.errors.append('Username already registered') return False - user = User.query.filter_by(email=self.email.data).first() + user = User.query.filter_by(email=self.email.data).first() # pylint: disable=no-member if user: self.email.errors.append('Email already registered') return False diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py index 2dff555..f0ad971 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py @@ -3,7 +3,7 @@ from flask import Blueprint, render_template from flask_login import login_required -blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static') +blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static') # pylint: disable=invalid-name @blueprint.route('/') From 8f39add44ab7a7352629a4674dbf6b7ffb414b67 Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Sat, 7 Nov 2015 21:21:57 +0100 Subject: [PATCH 6/8] Change string interpolation from % to .format() --- {{cookiecutter.app_name}}/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py index d86f354..dda683b 100755 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -57,7 +57,7 @@ class Lint(Command): def execute_tool(description, *args): """Execute a checking tool with its arguments.""" command_line = list(args) + files_and_directories - print('%s: %s' % (description, ' '.join(command_line))) + print('{}: {}'.format(description, ' '.join(command_line))) rv = call(command_line) if rv is not 0: exit(rv) From 8230fa17d5a6f2e0f7cf7f377d456e26e736393b Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Tue, 10 Nov 2015 00:52:59 +0100 Subject: [PATCH 7/8] Remove pylint from project, remove ipdb and ipython from requirements --- {{cookiecutter.app_name}}/.pylintrc | 263 ------------------ {{cookiecutter.app_name}}/manage.py | 12 +- .../requirements/dev.txt | 3 - {{cookiecutter.app_name}}/tests/conftest.py | 1 - {{cookiecutter.app_name}}/tests/factories.py | 1 - {{cookiecutter.app_name}}/tests/test_forms.py | 1 - .../tests/test_functional.py | 1 - .../tests/test_models.py | 1 - .../{{cookiecutter.app_name}}/assets.py | 1 - .../{{cookiecutter.app_name}}/compat.py | 1 - .../{{cookiecutter.app_name}}/database.py | 10 +- .../{{cookiecutter.app_name}}/extensions.py | 1 - .../{{cookiecutter.app_name}}/public/forms.py | 2 +- .../{{cookiecutter.app_name}}/public/views.py | 2 +- .../{{cookiecutter.app_name}}/user/forms.py | 4 +- .../{{cookiecutter.app_name}}/user/models.py | 4 +- .../{{cookiecutter.app_name}}/user/views.py | 2 +- 17 files changed, 16 insertions(+), 294 deletions(-) delete mode 100755 {{cookiecutter.app_name}}/.pylintrc diff --git a/{{cookiecutter.app_name}}/.pylintrc b/{{cookiecutter.app_name}}/.pylintrc deleted file mode 100755 index c8ce3d4..0000000 --- a/{{cookiecutter.app_name}}/.pylintrc +++ /dev/null @@ -1,263 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook='import os; import sys; print(sys.path)' - -# Profiled execution. -profile=no - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=3 - -[MESSAGES CONTROL] - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). -# You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# W0511 - Warning for fix me and to;do -disable=W0511,locally-disabled,pointless-string-statement - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html -output-format=colorized - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (RP0004). -comment=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes=SQLObject,SQLAlchemy,scoped_session - -# When zope mode is activated, add a predefined set of Zope acquired attributes -# to generated-members. -zope=no - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E0201 when accessed. Python regular -# expressions are accepted. -generated-members=REQUEST,acl_users,aq_parent - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=120 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the beginning of the name of dummy variables -# (i.e. not used). -dummy-variables-rgx=_|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - - -[BASIC] - -# Required attributes for module, separated by a comma -required-attributes= - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input - -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression which should only match correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct list comprehension / -# generator expression variable names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,id,db,rv,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression which should only match functions or classes name which do -# not require a docstring -no-docstring-rgx=__.*__ - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,string,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branchs=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=0 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - -[CLASSES] - -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/{{cookiecutter.app_name}}/manage.py b/{{cookiecutter.app_name}}/manage.py index dda683b..6a461ef 100755 --- a/{{cookiecutter.app_name}}/manage.py +++ b/{{cookiecutter.app_name}}/manage.py @@ -18,8 +18,8 @@ CONFIG = ProdConfig if os.environ.get('{{cookiecutter.app_name | upper}}_ENV') = HERE = os.path.abspath(os.path.dirname(__file__)) TEST_PATH = os.path.join(HERE, 'tests') -app = create_app(CONFIG) # pylint: disable=invalid-name -manager = Manager(app) # pylint: disable=invalid-name +app = create_app(CONFIG) +manager = Manager(app) def _make_context(): @@ -36,18 +36,16 @@ def test(): class Lint(Command): - """Lint and check code style with flake8, isort and, optionally, pylint.""" + """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'), - Option('-p', '--pylint', action='store_true', dest='use_pylint', default=False, - help='Use pylint after flake8, for an extended strict check'), ) - def run(self, fix_imports, use_pylint): # pylint: disable=arguments-differ,method-hidden + def run(self, fix_imports): """Run command.""" skip = ['requirements'] root_files = glob('*.py') @@ -65,8 +63,6 @@ class Lint(Command): if fix_imports: execute_tool('Fixing import order', 'isort', '-rc') execute_tool('Checking code style', 'flake8') - if use_pylint: - execute_tool('Checking code style', 'pylint', '--rcfile=.pylintrc') manager.add_command('server', Server()) diff --git a/{{cookiecutter.app_name}}/requirements/dev.txt b/{{cookiecutter.app_name}}/requirements/dev.txt index 40fbcca..5a3c599 100644 --- a/{{cookiecutter.app_name}}/requirements/dev.txt +++ b/{{cookiecutter.app_name}}/requirements/dev.txt @@ -5,8 +5,6 @@ pytest==2.8.2 WebTest==2.0.20 factory-boy==2.6.0 -ipdb==0.8.1 -ipython==4.0.0 # Management script Flask-Script==2.0.5 @@ -20,4 +18,3 @@ flake8-isort==0.2 flake8-quotes==0.1.1 isort==4.2.2 pep8-naming==0.3.3 -pylint==1.4.4 diff --git a/{{cookiecutter.app_name}}/tests/conftest.py b/{{cookiecutter.app_name}}/tests/conftest.py index b0b73ba..f2472b5 100644 --- a/{{cookiecutter.app_name}}/tests/conftest.py +++ b/{{cookiecutter.app_name}}/tests/conftest.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=redefined-outer-name """Defines fixtures available to all tests.""" import pytest diff --git a/{{cookiecutter.app_name}}/tests/factories.py b/{{cookiecutter.app_name}}/tests/factories.py index 794d2a2..33efe7a 100644 --- a/{{cookiecutter.app_name}}/tests/factories.py +++ b/{{cookiecutter.app_name}}/tests/factories.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=unnecessary-lambda """Factories to help in tests.""" from factory import PostGenerationMethodCall, Sequence from factory.alchemy import SQLAlchemyModelFactory diff --git a/{{cookiecutter.app_name}}/tests/test_forms.py b/{{cookiecutter.app_name}}/tests/test_forms.py index ff379bb..590d07f 100644 --- a/{{cookiecutter.app_name}}/tests/test_forms.py +++ b/{{cookiecutter.app_name}}/tests/test_forms.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=no-self-use,no-member,invalid-name,unused-argument """Test forms.""" from {{cookiecutter.app_name}}.public.forms import LoginForm diff --git a/{{cookiecutter.app_name}}/tests/test_functional.py b/{{cookiecutter.app_name}}/tests/test_functional.py index f683cf7..6b9151f 100644 --- a/{{cookiecutter.app_name}}/tests/test_functional.py +++ b/{{cookiecutter.app_name}}/tests/test_functional.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=no-self-use,no-member,invalid-name,unused-argument """Functional tests using WebTest. See: http://webtest.readthedocs.org/ diff --git a/{{cookiecutter.app_name}}/tests/test_models.py b/{{cookiecutter.app_name}}/tests/test_models.py index 0bbb4b1..4fe897e 100644 --- a/{{cookiecutter.app_name}}/tests/test_models.py +++ b/{{cookiecutter.app_name}}/tests/test_models.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=no-self-use,no-member,invalid-name """Model unit tests.""" import datetime as dt diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py index 3483a94..833e2e3 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/assets.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=invalid-name """Application assets.""" from flask_assets import Bundle, Environment diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py index 718d9f9..2833e72 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/compat.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=invalid-name,used-before-assignment """Python 2/3 compatibility module.""" import sys diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py index 8de1aec..18f4faf 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/database.py @@ -6,8 +6,8 @@ from .compat import basestring from .extensions import db # Alias common SQLAlchemy names -Column = db.Column # pylint: disable=invalid-name -relationship = relationship # pylint: disable=invalid-name +Column = db.Column +relationship = relationship class CRUDMixin(object): @@ -60,16 +60,16 @@ class SurrogatePK(object): (isinstance(record_id, basestring) and record_id.isdigit(), isinstance(record_id, (int, float))), ): - return cls.query.get(int(record_id)) # pylint: disable=no-member + return cls.query.get(int(record_id)) return None -def ReferenceCol(tablename, nullable=False, pk_name='id', **kwargs): # noqa # pylint: disable=invalid-name +def reference_col(tablename, nullable=False, pk_name='id', **kwargs): """Column that adds primary key foreign key reference. Usage: :: - category_id = ReferenceCol('category') + category_id = reference_col('category') category = relationship('Category', backref='categories') """ return db.Column( diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py index e8b6e5c..de72e89 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/extensions.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=invalid-name """Extensions module. Each extension is initialized in the app factory located in app.py.""" from flask_bcrypt import Bcrypt from flask_cache import Cache diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py index f76a69d..ed7f1fe 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/forms.py @@ -24,7 +24,7 @@ class LoginForm(Form): if not initial_validation: return False - self.user = User.query.filter_by(username=self.username.data).first() # pylint: disable=no-member + self.user = User.query.filter_by(username=self.username.data).first() if not self.user: self.username.errors.append('Unknown username') return False diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py index 2c6bfc7..72b3366 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/public/views.py @@ -9,7 +9,7 @@ from {{cookiecutter.app_name}}.user.forms import RegisterForm from {{cookiecutter.app_name}}.user.models import User from {{cookiecutter.app_name}}.utils import flash_errors -blueprint = Blueprint('public', __name__, static_folder='../static') # pylint: disable=invalid-name +blueprint = Blueprint('public', __name__, static_folder='../static') @login_manager.user_loader diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py index 6ba9956..1ba4166 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/forms.py @@ -29,11 +29,11 @@ class RegisterForm(Form): initial_validation = super(RegisterForm, self).validate() if not initial_validation: return False - user = User.query.filter_by(username=self.username.data).first() # pylint: disable=no-member + user = User.query.filter_by(username=self.username.data).first() if user: self.username.errors.append('Username already registered') return False - user = User.query.filter_by(email=self.email.data).first() # pylint: disable=no-member + user = User.query.filter_by(email=self.email.data).first() if user: self.email.errors.append('Email already registered') return False diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py index 7fb50ef..073c378 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py @@ -4,7 +4,7 @@ import datetime as dt from flask_login import UserMixin -from {{cookiecutter.app_name}}.database import Column, Model, ReferenceCol, SurrogatePK, db, relationship +from {{cookiecutter.app_name}}.database import Column, Model, reference_col, SurrogatePK, db, relationship from {{cookiecutter.app_name}}.extensions import bcrypt @@ -13,7 +13,7 @@ class Role(SurrogatePK, Model): __tablename__ = 'roles' name = Column(db.String(80), unique=True, nullable=False) - user_id = ReferenceCol('users', nullable=True) + user_id = reference_col('users', nullable=True) user = relationship('User', backref='roles') def __init__(self, name, **kwargs): diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py index f0ad971..2dff555 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/views.py @@ -3,7 +3,7 @@ from flask import Blueprint, render_template from flask_login import login_required -blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static') # pylint: disable=invalid-name +blueprint = Blueprint('user', __name__, url_prefix='/users', static_folder='../static') @blueprint.route('/') From d6f1f5c2cc826129a8d7c129ff1535f58417e1a3 Mon Sep 17 00:00:00 2001 From: Wagner Augusto Andreoli Date: Tue, 10 Nov 2015 01:09:52 +0100 Subject: [PATCH 8/8] Travis: Migrating from legacy to container-based infrastructure --- .travis.yml | 2 ++ {{cookiecutter.app_name}}/.travis.yml | 10 ++++------ .../{{cookiecutter.app_name}}/user/models.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 158f7e0..f536a58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +# Config file for automatic testing at travis-ci.org +sudo: false # http://docs.travis-ci.com/user/migrating-from-legacy/ language: python python: - 2.7 diff --git a/{{cookiecutter.app_name}}/.travis.yml b/{{cookiecutter.app_name}}/.travis.yml index 70c1b80..dece91f 100644 --- a/{{cookiecutter.app_name}}/.travis.yml +++ b/{{cookiecutter.app_name}}/.travis.yml @@ -1,11 +1,9 @@ # Config file for automatic testing at travis-ci.org - +sudo: false # http://docs.travis-ci.com/user/migrating-from-legacy/ language: python - python: - - "3.3" - - "2.7" - + - 2.7 + - 3.3 + - 3.4 install: pip install -r requirements/dev.txt - script: py.test tests diff --git a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py index 073c378..81c3b19 100644 --- a/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py +++ b/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/user/models.py @@ -4,7 +4,7 @@ import datetime as dt from flask_login import UserMixin -from {{cookiecutter.app_name}}.database import Column, Model, reference_col, SurrogatePK, db, relationship +from {{cookiecutter.app_name}}.database import Column, Model, SurrogatePK, db, reference_col, relationship from {{cookiecutter.app_name}}.extensions import bcrypt