Config files for auto linting (#3264)

* Config files for auto linting

autoflake, autopep8, docformatter, isort, yapf

* readthedocs.projects.views.base with auto styling applied

* readthedocs.core.models auto styling applied

* readthedocs.builds.urls with auto styling applied

* Do not make multi line summaries

* Improve isort settings

* Use 80 columns as maximum

* pre-commit config file

Use the `pre-commit` tool to run some linting commands before each
commit:

    http://pre-commit.com/

* Update yapf config file to 0.20.0

* Add i18n functions to yapf config

* Remove django as a special section for isort

* Rename config to be automatically discovered

* Add annoying as a third party lib for isort

* Use the new config for all the files modified as a test

I had to add a couple of "trailing commas" to keep the format as we want

* Install additional dependencies for each of the hooks

Remove the auto-lint.txt requirements file since it's not needed anymore

* Use default config name for autopep8

* Include autoflake as pre-commit hook

* Exclude D105 from flake8

D105: Missing docstring in magic method

* Minor fixes from flake8

* autoflake with --in-place

* Arguments for docformatter

* Autofix D203 with yapf

D203 / 1 blank line required before class docstring

* Ignore local_settings.py on prospector
theme-0.6.2
Manuel Kaufmann 2017-11-27 11:42:09 -05:00 committed by Anthony
parent c0de92d173
commit d18ddc9bb5
10 changed files with 351 additions and 46 deletions

3
.flake8 Normal file
View File

@ -0,0 +1,3 @@
[flake8]
ignore = E125,D100,D101,D102,D105,D200,D211,P101,FI15,FI16,FI12,FI11,FI17,FI50,FI53,FI54
max-line-length = 80

10
.isort.cfg Normal file
View File

@ -0,0 +1,10 @@
[settings]
line_length=80
indent=' '
multi_line_output=4
default_section=FIRSTPARTY
known_readthedocs=readthedocs
known_readthedocsinc=readthedocsinc
known_third_party=celery,stripe,requests,pytz,builtins,django,annoying
sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
add_imports=from __future__ import division, from __future__ import print_function, from __future__ import unicode_literals

3
.pep8 Normal file
View File

@ -0,0 +1,3 @@
[pep8]
ignore = E701,E70,E702
max-line-length = 80

54
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,54 @@
exclude: '^$'
fail_fast: false
repos:
- repo: git@github.com:pre-commit/pre-commit-hooks
sha: v1.1.1
hooks:
- id: autopep8-wrapper
- id: check-added-large-files
- id: debug-statements
- id: double-quote-string-fixer
- id: end-of-file-fixer
- id: fix-encoding-pragma
- id: flake8
additional_dependencies: [
'flake8-blind-except',
'flake8-coding',
'flake8-comprehensions',
'flake8-debugger',
'flake8-deprecated',
'flake8-docstrings',
'flake8-meiqia',
'flake8-mutable',
'flake8-pep3101',
'flake8-print',
'flake8-quotes',
'flake8-string-format',
'flake8-tidy-imports',
'flake8-todo']
- id: trailing-whitespace
- repo: git@github.com:pre-commit/mirrors-yapf.git
sha: v0.20.0
hooks:
- id: yapf
exclude: 'migrations|settings|scripts'
additional_dependencies: ['futures']
- repo: git@github.com:FalconSocial/pre-commit-python-sorter.git
sha: b57843b0b874df1d16eb0bef00b868792cb245c2
hooks:
- id: python-import-sorter
args: ['--silent-overwrite']
- repo: git@github.com:humitos/mirrors-docformatter.git
sha: v0.8
hooks:
- id: docformatter
args: ['--in-place', '--wrap-summaries=80', '--wrap-descriptions=80', '--pre-summary-newline', '--no-blank']
- repo: git@github.com:humitos/mirrors-autoflake.git
sha: v1.0
hooks:
- id: autoflake
args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable']

203
.style.yapf Normal file
View File

@ -0,0 +1,203 @@
# https://github.com/google/yapf
# current version used in this repo
# yapf==0.20.0
[style]
# Align closing bracket with visual indentation.
ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT=True
# Allow lambdas to be formatted on more than one line.
ALLOW_MULTILINE_LAMBDAS=False
# Allow dictionary keys to exist on multiple lines. For example:
#
# x = {
# ('this is the first element of a tuple',
# 'this is the second element of a tuple'):
# value,
# }
ALLOW_MULTILINE_DICTIONARY_KEYS=False
# Insert a blank line before a 'def' or 'class' immediately nested
# within another 'def' or 'class'. For example:
#
# class Foo:
# # <------ this blank line
# def method():
# ...
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=True
# Insert a blank line before a class-level docstring.
BLANK_LINE_BEFORE_CLASS_DOCSTRING=True
# Do not split consecutive brackets. Only relevant when
# dedent_closing_brackets is set. For example:
#
# call_func_that_takes_a_dict(
# {
# 'key1': 'value1',
# 'key2': 'value2',
# }
# )
#
# would reformat to:
#
# call_func_that_takes_a_dict({
# 'key1': 'value1',
# 'key2': 'value2',
# })
COALESCE_BRACKETS=True
# The column limit.
COLUMN_LIMIT=80
# Indent width used for line continuations.
CONTINUATION_INDENT_WIDTH=4
# Put closing brackets on a separate line, dedented, if the bracketed
# expression can't fit in a single line. Applies to all kinds of
# brackets, including function definitions and calls. For example:
#
# config = {
# 'key1': 'value1',
# 'key2': 'value2',
# } # <--- this bracket is dedented and on a separate line
#
# time_series = self.remote_client.query_entity_counters(
# entity='dev3246.region1',
# key='dns.query_latency_tcp',
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
# start_ts=now()-timedelta(days=3),
# end_ts=now(),
# ) # <--- this bracket is dedented and on a separate line
DEDENT_CLOSING_BRACKETS=False
# Place each dictionary entry onto its own line.
EACH_DICT_ENTRY_ON_SEPARATE_LINE=False
# The regex for an i18n comment. The presence of this comment stops
# reformatting of that line, because the comments are required to be
# next to the string they translate.
I18N_COMMENT=
# The i18n function call names. The presence of this function stops
# reformattting on that line, because the string it has cannot be moved
# away from the i18n comment.
I18N_FUNCTION_CALL=["_", "ugettext", "gettext"]
# Indent the dictionary value if it cannot fit on the same line as the
# dictionary key. For example:
#
# config = {
# 'key1':
# 'value1',
# 'key2': value1 +
# value2,
# }
INDENT_DICTIONARY_VALUE=False
# The number of columns to use for indentation.
INDENT_WIDTH=4
# Join short lines into one line. E.g., single line 'if' statements.
JOIN_MULTIPLE_LINES=True
# Use spaces around the power operator.
SPACES_AROUND_POWER_OPERATOR=True
# Do not include spaces around selected binary operators. For example:
# 1 + 2 * 3 - 4 / 5
# will be formatted as follows when configured with a value "*,/":
# 1 + 2*3 - 4/5
NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS=False
# Set to True to prefer spaces around the assignment operator for
# default or keyword arguments.
SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=False
# The number of spaces required before a trailing comment.
SPACES_BEFORE_COMMENT=2
# Insert a space between the ending comma and closing bracket of a list,
# etc.
SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=False
# Split before arguments if the argument list is terminated by a
# comma.
SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED=True
# Set to True to prefer splitting before '&', '|' or '^' rather than
# after.
SPLIT_BEFORE_BITWISE_OPERATOR=False
# Split before a dictionary or set generator (comp_for). For example,
# note the split before the for:
#
# foo = {
# variable: 'Hello world, have a nice day!'
# for variable in bar if variable != 42
# }
SPLIT_BEFORE_DICT_SET_GENERATOR=True
# Split after the opening paren which surrounds an expression if it doesn't fit on a single line.
SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN=True
# If an argument / parameter list is going to be split, then split
# before the first argument.
SPLIT_BEFORE_FIRST_ARGUMENT=True
# Set to True to prefer splitting before 'and' or 'or' rather than
# after.
SPLIT_BEFORE_LOGICAL_OPERATOR=False
# Split named assignments onto individual lines.
SPLIT_BEFORE_NAMED_ASSIGNS=False
# For list comprehensions and generator expressions with multiple clauses
SPLIT_COMPLEX_COMPREHENSION=False
# The penalty for splitting right after the opening bracket.
SPLIT_PENALTY_AFTER_OPENING_BRACKET=2000
# The penalty for splitting the line after a unary operator.
SPLIT_PENALTY_AFTER_UNARY_OPERATOR=10000
# The penalty for splitting right before an if expression.
SPLIT_PENALTY_BEFORE_IF_EXPR=0
# The penalty of splitting the line around the '&', '|', and '^'
# operators.
SPLIT_PENALTY_BITWISE_OPERATOR=300
# The penalty for splitting a list comprehension or generator expression.
SPLIT_PENALTY_COMPREHENSION=80
# The penalty for characters over the column limit.
SPLIT_PENALTY_EXCESS_CHARACTER=2600
# The penalty incurred by adding a line split to the unwrapped
# line. The more line splits added the higher the penalty.
SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT=30
# The penalty of splitting a list of "import as" names. For example:
#
# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
# long_argument_2,
# long_argument_3)
#
# would reformat to something like:
#
# from a_very_long_or_indented_module_name_yada_yad import (
# long_argument_1, long_argument_2, long_argument_3)
SPLIT_PENALTY_IMPORT_NAMES=0
# The penalty of splitting the line around the 'and' and 'or'
# operators.
SPLIT_PENALTY_LOGICAL_OPERATOR=300
# Use the Tab character for indentation.
USE_TABS=False
# Allow splits before the dictionary value.
ALLOW_SPLIT_BEFORE_DICT_VALUE=False

View File

@ -34,11 +34,29 @@ label. Those tickets are meant to be standalone and can be worked on ad-hoc.
When contributing code, then please follow the standard Contribution
Guidelines set forth at `contribution-guide.org`_.
We have a strict code style that it's easy to follow since you just
have to run a couple of commands and they will do everything for
you. These commands are a mix between `autoflake`_, `autopep8`_,
`docformatter`_, `isort`_, `unify`_ and `yapf`_::
$ autoflake --remove-all-unused-imports --remove-unused-variables --keep-useless-pass
$ autopep8
$ docformatter --wrap-summaries=80 --wrap-descriptions=80 --pre-summary-newline --no-blank
$ isort
$ unify --quote="'"
$ yapf --exclude=*migrations* --exclude=*settings* --exclude=*scripts* --parallel
.. _Feature Overview: https://github.com/rtfd/readthedocs.org/issues?direction=desc&labels=Feature+Overview&page=1&sort=updated&state=open
.. _Good First Issue: https://github.com/rtfd/readthedocs.org/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22
.. _Sprintable: https://github.com/rtfd/readthedocs.org/issues?q=is%3Aopen+is%3Aissue+label%3ASprintable
.. _contribution-guide.org: http://www.contribution-guide.org/#submitting-bugs
.. _autoflake: https://github.com/myint/autoflake
.. _autopep8: https://github.com/hhatto/autopep8
.. _docformatter: https://github.com/myint/docformatter
.. _unify: https://github.com/myint/unify
.. _yapf: https://github.com/google/yapf
Triaging tickets
----------------

View File

@ -12,6 +12,7 @@ ignore-paths:
ignore-patterns:
- /migrations/
- local_settings.py
pep8:
full: true

View File

@ -1,17 +1,21 @@
# -*- coding: utf-8 -*-
"""URL configuration for builds app."""
from __future__ import (
absolute_import, division, print_function, unicode_literals)
from __future__ import absolute_import
from django.conf.urls import url
from .views import builds_redirect_detail, builds_redirect_list
urlpatterns = [
url(r'^(?P<project_slug>[-\w]+)/(?P<pk>\d+)/$',
url(
r'^(?P<project_slug>[-\w]+)/(?P<pk>\d+)/$',
builds_redirect_detail,
name='old_builds_detail'),
url(r'^(?P<project_slug>[-\w]+)/$',
name='old_builds_detail',
),
url(
r'^(?P<project_slug>[-\w]+)/$',
builds_redirect_list,
name='old_builds_project_list'),
name='old_builds_project_list',
),
]

View File

@ -1,55 +1,60 @@
# -*- coding: utf-8 -*-
"""Models for the core app."""
from __future__ import (
absolute_import, division, print_function, unicode_literals)
from __future__ import absolute_import
import logging
from annoying.fields import AutoOneToOneField
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _, ugettext
from annoying.fields import AutoOneToOneField
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
STANDARD_EMAIL = "anonymous@readthedocs.org"
STANDARD_EMAIL = 'anonymous@readthedocs.org'
log = logging.getLogger(__name__)
@python_2_unicode_compatible
class UserProfile (models.Model):
class UserProfile(models.Model):
"""Additional information about a User."""
user = AutoOneToOneField('auth.User', verbose_name=_('User'),
related_name='profile')
user = AutoOneToOneField(
'auth.User', verbose_name=_('User'), related_name='profile')
whitelisted = models.BooleanField(_('Whitelisted'), default=False)
banned = models.BooleanField(_('Banned'), default=False)
homepage = models.CharField(_('Homepage'), max_length=100, blank=True)
allow_ads = models.BooleanField(_('See paid advertising'),
help_text=_('If unchecked, you will still see community ads.'),
default=True,
)
allow_email = models.BooleanField(_('Allow email'),
help_text=_('Show your email on VCS '
'contributions.'),
default=True)
allow_ads = models.BooleanField(
_('See paid advertising'),
help_text=_('If unchecked, you will still see community ads.'),
default=True,
)
allow_email = models.BooleanField(
_('Allow email'),
help_text=_('Show your email on VCS contributions.'),
default=True,
)
def __str__(self):
return (ugettext("%(username)s's profile")
% {'username': self.user.username})
return (
ugettext("%(username)s's profile") %
{'username': self.user.username})
def get_absolute_url(self):
return ('profiles_profile_detail', (),
{'username': self.user.username})
return ('profiles_profile_detail', (), {'username': self.user.username})
get_absolute_url = models.permalink(get_absolute_url)
def get_contribution_details(self):
"""
Gets the line to put into commits to attribute the author.
Get the line to put into commits to attribute the author.
Returns a tuple (name, email)
"""
if self.user.first_name and self.user.last_name:
name = '%s %s' % (self.user.first_name, self.user.last_name)
name = '{} {}'.format(self.user.first_name, self.user.last_name)
else:
name = self.user.username
if self.allow_email:

View File

@ -1,16 +1,19 @@
# -*- coding: utf-8 -*-
"""Mix-in classes for project views."""
from __future__ import absolute_import
from builtins import object
from __future__ import (
absolute_import, division, print_function, unicode_literals)
import logging
from datetime import datetime, timedelta
from builtins import object
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from ..models import Project
from ..exceptions import ProjectSpamError
from ..models import Project
log = logging.getLogger(__name__)
@ -19,10 +22,10 @@ USER_MATURITY_DAYS = getattr(settings, 'USER_MATURITY_DAYS', 7)
class ProjectOnboardMixin(object):
"""Add project onboard context data to project object views"""
"""Add project onboard context data to project object views."""
def get_context_data(self, **kwargs):
"""Add onboard context data"""
"""Add onboard context data."""
context = super(ProjectOnboardMixin, self).get_context_data(**kwargs)
# If more than 1 project, don't show onboarding at all. This could
# change in the future, to onboard each user maybe?
@ -47,13 +50,13 @@ class ProjectOnboardMixin(object):
# Mixins
class ProjectAdminMixin(object):
"""Mixin class that provides project sublevel objects
"""
Mixin class that provides project sublevel objects.
This mixin uses several class level variables
project_url_field
The URL kwarg name for the project slug
"""
project_url_field = 'project_slug'
@ -63,34 +66,34 @@ class ProjectAdminMixin(object):
return self.model.objects.filter(project=self.project)
def get_project(self):
"""Return project determined by url kwarg"""
"""Return project determined by url kwarg."""
if self.project_url_field not in self.kwargs:
return None
return get_object_or_404(
Project.objects.for_admin_user(user=self.request.user),
slug=self.kwargs[self.project_url_field]
)
slug=self.kwargs[self.project_url_field])
def get_context_data(self, **kwargs):
"""Add project to context data"""
"""Add project to context data."""
context = super(ProjectAdminMixin, self).get_context_data(**kwargs)
context['project'] = self.get_project()
return context
def get_form(self, data=None, files=None, **kwargs):
"""Pass in project to form class instance"""
"""Pass in project to form class instance."""
kwargs['project'] = self.get_project()
return self.form_class(data, files, **kwargs)
class ProjectSpamMixin(object):
"""Protects POST views from spammers"""
"""Protects POST views from spammers."""
def post(self, request, *args, **kwargs):
if request.user.profile.banned:
log.error('Rejecting project POST from shadowbanned user %s',
request.user)
log.error(
'Rejecting project POST from shadowbanned user %s',
request.user)
return HttpResponseRedirect(self.get_failure_url())
try:
return super(ProjectSpamMixin, self).post(request, *args, **kwargs)
@ -99,8 +102,9 @@ class ProjectSpamMixin(object):
if request.user.date_joined > date_maturity:
request.user.profile.banned = True
request.user.profile.save()
log.error('Spam detected from new user, shadowbanned user %s',
request.user)
log.error(
'Spam detected from new user, shadowbanned user %s',
request.user)
else:
log.error('Spam detected from user %s', request.user)
return HttpResponseRedirect(self.get_failure_url())