Fixed: Responded to PR comments, also:
* fixed typos, documentation and help improvements * factored out sorting and slicing * user convenience for ssh * avoid being os-specific * add return status equal to total violations * fixed pylint's path issue causing false import errors
This commit is contained in:
parent
80f7033ad4
commit
bac7056e40
|
@ -35,7 +35,7 @@ Furthermore, you can use -- to run arbitrary shell commands rather than tasks:
|
|||
Create a virtualenv called pybitmessage and install fabfile/requirements.txt
|
||||
$ mkvirtualenv -r fabfile/requirements.txt --system-site-packages pybitmessage-devops
|
||||
* Ensure you can ssh localhost with no intervention, which may include:
|
||||
* settings in ~/.ssh/config
|
||||
* ssh [sshd_config server] and [ssh_config client] configuration
|
||||
* authorized_keys file
|
||||
* load ssh key
|
||||
* check(!) and accept the host key
|
||||
|
@ -52,3 +52,35 @@ There are a number of advantages that should benefit us:
|
|||
* Tasks can be combined either programmatically or on the commandline and run in series or parallel
|
||||
* Whole environments can be managed very effectively in conjunction with a configuration management system
|
||||
|
||||
<a name="sshd_config"></a>
|
||||
# /etc/ssh/sshd_config
|
||||
|
||||
If you're going to be using ssh to connect to localhost you want to avoid weakening your security. The best way of
|
||||
doing this is blocking port 22 with a firewall. As a belt and braces approach you can also edit the
|
||||
/etc/ssh/sshd_config file to restrict login further:
|
||||
|
||||
```
|
||||
PubkeyAuthentication no
|
||||
|
||||
...
|
||||
|
||||
Match ::1
|
||||
PubkeyAuthentication yes
|
||||
```
|
||||
Adapted from [stackexchange](https://unix.stackexchange.com/questions/406245/limit-ssh-access-to-specific-clients-by-ip-address)
|
||||
|
||||
<a name="ssh_config"></a>
|
||||
# ~/.ssh/config
|
||||
|
||||
Fabric will honour your ~/.ssh/config file for your convenience. Since you will spend more time with this key unlocked
|
||||
than others you should use a different key:
|
||||
|
||||
```
|
||||
Host localhost
|
||||
HostName localhost
|
||||
IdentityFile ~/.ssh/id_rsa_localhost
|
||||
|
||||
Host github
|
||||
HostName github.com
|
||||
IdentityFile ~/.ssh/id_rsa_github
|
||||
```
|
||||
|
|
|
@ -4,11 +4,29 @@ run commands like this:
|
|||
|
||||
$ fab code_quality
|
||||
|
||||
For a list of commands
|
||||
For a list of commands:
|
||||
|
||||
$ fab -l
|
||||
|
||||
For help on fabric itself:
|
||||
|
||||
$ fab -h
|
||||
|
||||
For more help on a particular command
|
||||
"""
|
||||
|
||||
from fabric.api import env
|
||||
|
||||
from fabfile.tasks import code_quality
|
||||
|
||||
|
||||
__all__ = ["code_quality"]
|
||||
# Without this, `fab -l` would display the whole docstring as preamble
|
||||
__doc__ = ""
|
||||
|
||||
# This list defines which tasks are made available to the user
|
||||
__all__ = [
|
||||
"code_quality",
|
||||
]
|
||||
|
||||
# Honour the user's ssh client configuration
|
||||
env.use_ssh_config = True
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
"""A library of functions and constrants for tasks to make use of."""
|
||||
# pylint: disable=not-context-manager
|
||||
"""
|
||||
A library of functions and constants for tasks to make use of.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from fabric.api import run, hide
|
||||
from fabric.context_managers import settings
|
||||
from fabric.context_managers import settings, shell_env
|
||||
from fabvenv import virtualenv
|
||||
|
||||
|
||||
FABRIC_ROOT = os.path.dirname(__file__)
|
||||
PROJECT_ROOT = os.path.dirname(FABRIC_ROOT)
|
||||
VENV_ROOT = os.path.expanduser(os.path.join('~', '.virtualenvs', 'pybitmessage-devops'))
|
||||
PYTHONPATH = os.path.join(PROJECT_ROOT, 'src',)
|
||||
|
||||
|
||||
def coerce_bool(value):
|
||||
|
@ -49,7 +54,7 @@ def flatten(data):
|
|||
def pycodestyle(path_to_file):
|
||||
"""Run pycodestyle on a file"""
|
||||
with virtualenv(VENV_ROOT):
|
||||
with hide('warnings', 'running', 'stdout', 'stderr'): # pylint: disable=not-context-manager
|
||||
with hide('warnings', 'running', 'stdout', 'stderr'):
|
||||
with settings(warn_only=True):
|
||||
return run(
|
||||
'pycodestyle --config={0} {1}'.format(
|
||||
|
@ -65,7 +70,7 @@ def pycodestyle(path_to_file):
|
|||
def flake8(path_to_file):
|
||||
"""Run flake8 on a file"""
|
||||
with virtualenv(VENV_ROOT):
|
||||
with hide('warnings', 'running', 'stdout'): # pylint: disable=not-context-manager
|
||||
with hide('warnings', 'running', 'stdout'):
|
||||
with settings(warn_only=True):
|
||||
return run(
|
||||
'flake8 --config={0} {1}'.format(
|
||||
|
@ -81,26 +86,65 @@ def flake8(path_to_file):
|
|||
def pylint(path_to_file):
|
||||
"""Run pylint on a file"""
|
||||
with virtualenv(VENV_ROOT):
|
||||
with hide('warnings', 'running', 'stdout', 'stderr'): # pylint: disable=not-context-manager
|
||||
with hide('warnings', 'running', 'stdout', 'stderr'):
|
||||
with settings(warn_only=True):
|
||||
return run(
|
||||
'pylint --rcfile={0} {1}'.format(
|
||||
os.path.join(
|
||||
PROJECT_ROOT,
|
||||
'setup.cfg',
|
||||
with shell_env(PYTHONPATH=PYTHONPATH):
|
||||
return run(
|
||||
'pylint --rcfile={0} {1}'.format(
|
||||
os.path.join(
|
||||
PROJECT_ROOT,
|
||||
'setup.cfg',
|
||||
),
|
||||
path_to_file,
|
||||
),
|
||||
path_to_file,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def autopep8(path_to_file):
|
||||
"""Run autopep8 on a file"""
|
||||
with virtualenv(VENV_ROOT):
|
||||
with hide('running'): # pylint: disable=not-context-manager
|
||||
with hide('running'):
|
||||
with settings(warn_only=True):
|
||||
return run(
|
||||
"autopep8 --experimental --aggressive --aggressive -i --max-line-length=119 {}".format(
|
||||
path_to_file
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_filtered_pycodestyle_output(path_to_file):
|
||||
"""Clean up the raw results for pycodestyle"""
|
||||
|
||||
return [
|
||||
i
|
||||
for i in pycodestyle(path_to_file).split(os.linesep)
|
||||
if i
|
||||
]
|
||||
|
||||
|
||||
def get_filtered_flake8_output(path_to_file):
|
||||
"""Clean up the raw results for flake8"""
|
||||
|
||||
return [
|
||||
i
|
||||
for i in flake8(path_to_file).split(os.linesep)
|
||||
if i
|
||||
]
|
||||
|
||||
|
||||
def get_filtered_pylint_output(path_to_file):
|
||||
"""Clean up the raw results for pylint"""
|
||||
|
||||
return [
|
||||
i
|
||||
for i in pylint(path_to_file).split(os.linesep)
|
||||
if all([
|
||||
i,
|
||||
not i.startswith(' '),
|
||||
not i.startswith('\r'),
|
||||
not i.startswith('-'),
|
||||
not i.startswith('Y'),
|
||||
not i.startswith('*'),
|
||||
not i.startswith('Using config file'),
|
||||
])
|
||||
]
|
||||
|
|
200
fabfile/tasks.py
200
fabfile/tasks.py
|
@ -1,5 +1,8 @@
|
|||
# pylint: disable=not-context-manager
|
||||
"""
|
||||
Fabric tasks for PyBitmessage devops operations.
|
||||
|
||||
# pylint: disable=not-context-manager
|
||||
"""
|
||||
|
||||
import os
|
||||
|
@ -8,83 +11,113 @@ import sys
|
|||
from fabric.api import run, task, hide, cd
|
||||
from fabvenv import virtualenv
|
||||
|
||||
from fabfile.lib import pycodestyle, flake8, pylint, autopep8, PROJECT_ROOT, VENV_ROOT, coerce_bool, flatten
|
||||
from fabfile.lib import (
|
||||
autopep8, PROJECT_ROOT, VENV_ROOT, coerce_bool, flatten,
|
||||
get_filtered_pycodestyle_output, get_filtered_flake8_output, get_filtered_pylint_output,
|
||||
)
|
||||
|
||||
|
||||
def get_tool_results(file_list):
|
||||
"""Take a list of files and resuln the results of applying the tools"""
|
||||
|
||||
results = []
|
||||
for path_to_file in file_list:
|
||||
result = {}
|
||||
|
||||
result['pycodestyle_violations'] = [
|
||||
i
|
||||
for i in pycodestyle(path_to_file).split("\n")
|
||||
if i
|
||||
]
|
||||
result['flake8_violations'] = [
|
||||
i
|
||||
for i in flake8(path_to_file).split("\n")
|
||||
if i
|
||||
]
|
||||
result['pylint_violations'] = [
|
||||
i
|
||||
for i in pylint(path_to_file).split("\n")
|
||||
if all([
|
||||
i,
|
||||
not i.startswith(' '),
|
||||
not i.startswith('\r'),
|
||||
not i.startswith('-'),
|
||||
not i.startswith('Y'),
|
||||
not i.startswith('*'),
|
||||
not i.startswith('Using config file'),
|
||||
])
|
||||
]
|
||||
result['pycodestyle_violations'] = get_filtered_pycodestyle_output(path_to_file)
|
||||
result['flake8_violations'] = get_filtered_flake8_output(path_to_file)
|
||||
result['pylint_violations'] = get_filtered_pylint_output(path_to_file)
|
||||
result['path_to_file'] = path_to_file
|
||||
result['total_violations'] = sum(
|
||||
[
|
||||
result['total_violations'] = sum([
|
||||
len(result['pycodestyle_violations']),
|
||||
len(result['flake8_violations']),
|
||||
len(result['pylint_violations']),
|
||||
]
|
||||
)
|
||||
])
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
|
||||
def print_item(item, verbose, details):
|
||||
def print_results(results, top, verbose, details):
|
||||
"""Print an item with the appropriate verbosity / detail"""
|
||||
|
||||
if verbose:
|
||||
line = "{0} {1} {2} {3} {4}".format(
|
||||
item['total_violations'],
|
||||
len(item['pycodestyle_violations']),
|
||||
len(item['flake8_violations']),
|
||||
len(item['pylint_violations']),
|
||||
item['path_to_file'],
|
||||
print ''.join(
|
||||
[
|
||||
os.linesep,
|
||||
'total pycodestyle flake8 pylint path_to_file',
|
||||
os.linesep,
|
||||
]
|
||||
)
|
||||
else:
|
||||
line = item['path_to_file']
|
||||
print line
|
||||
|
||||
if details:
|
||||
print "pycodestyle:"
|
||||
for detail in flatten(item['pycodestyle_violations']):
|
||||
print detail
|
||||
print
|
||||
for item in sort_and_slice(results, top):
|
||||
|
||||
print "flake8:"
|
||||
for detail in flatten(item['flake8_violations']):
|
||||
print detail
|
||||
print
|
||||
if verbose:
|
||||
line = "{0} {1} {2} {3} {4}".format(
|
||||
item['total_violations'],
|
||||
len(item['pycodestyle_violations']),
|
||||
len(item['flake8_violations']),
|
||||
len(item['pylint_violations']),
|
||||
item['path_to_file'],
|
||||
)
|
||||
else:
|
||||
line = item['path_to_file']
|
||||
print line
|
||||
|
||||
print "pylint:"
|
||||
for detail in flatten(item['pylint_violations']):
|
||||
print detail
|
||||
print
|
||||
if details:
|
||||
print "pycodestyle:"
|
||||
for detail in flatten(item['pycodestyle_violations']):
|
||||
print detail
|
||||
print
|
||||
|
||||
print "flake8:"
|
||||
for detail in flatten(item['flake8_violations']):
|
||||
print detail
|
||||
print
|
||||
|
||||
print "pylint:"
|
||||
for detail in flatten(item['pylint_violations']):
|
||||
print detail
|
||||
print
|
||||
|
||||
|
||||
def sort_and_slice(results, top):
|
||||
"""Sort dictionary items by the `total_violations` key and honour top"""
|
||||
|
||||
returnables = []
|
||||
for item in sorted(
|
||||
results,
|
||||
reverse=True,
|
||||
key=lambda x: x['total_violations']
|
||||
)[:top]:
|
||||
returnables.append(item)
|
||||
return returnables
|
||||
|
||||
|
||||
def generate_file_list(filename):
|
||||
"""Return an unfiltered list of absolute paths to the files to act on"""
|
||||
|
||||
with hide('warnings', 'running', 'stdout'):
|
||||
with virtualenv(VENV_ROOT):
|
||||
|
||||
if filename:
|
||||
filename = os.path.abspath(filename)
|
||||
if not os.path.exists(filename):
|
||||
print "Bad filename, specify a Python file"
|
||||
sys.exit(1)
|
||||
else:
|
||||
file_list = [filename]
|
||||
|
||||
else:
|
||||
with cd(PROJECT_ROOT):
|
||||
file_list = [
|
||||
os.path.abspath(i.rstrip('\r'))
|
||||
for i in run('find . -name "*.py"').split(os.linesep)
|
||||
]
|
||||
|
||||
return file_list
|
||||
|
||||
|
||||
@task
|
||||
def code_quality(top=10, verbose=True, details=False, fix=False, filename=None):
|
||||
def code_quality(verbose=True, details=False, fix=False, filename=None, top=10):
|
||||
"""
|
||||
Check code quality.
|
||||
|
||||
|
@ -95,13 +128,20 @@ def code_quality(top=10, verbose=True, details=False, fix=False, filename=None):
|
|||
|
||||
$ fab -H localhost code_quality
|
||||
|
||||
Options:
|
||||
top: Display / fix only the top violating files, a value of 0 will display / fix all files
|
||||
verbose: Display a header and the counts. Without this you just get the filenames in order
|
||||
details: Display the violations one per line after the count/file summary
|
||||
fix: Run autopep8 on the displayed file(s)
|
||||
filename: Rather than analysing all files and displaying / fixing the top N, just analyse / diaply / fix the
|
||||
specified file
|
||||
:param top: Display / fix only the top N violating files, a value of 0 will display / fix all files
|
||||
:type top: int, default 10
|
||||
:param verbose: Display a header and the counts, without this you just get the filenames in order
|
||||
:type verbose: bool, default True
|
||||
:param details: Display the violations one per line after the count / file summary
|
||||
:type details: bool, default False
|
||||
:param fix: Run autopep8 aggressively on the displayed file(s)
|
||||
:type fix: bool, default False
|
||||
:param filename: Rather than analysing all files and displaying / fixing the top N, just analyse / display / fix
|
||||
the specified file
|
||||
:type filename: string, valid path to a file, default all files in the project
|
||||
:return: This fabric task has an exit status equal to the total number of violations and uses stdio but it does
|
||||
not return anything if you manage to call it successfully from Python
|
||||
:rtype: None
|
||||
|
||||
Intended to be temporary until we have improved code quality and have safeguards to maintain it in place.
|
||||
|
||||
|
@ -110,38 +150,16 @@ def code_quality(top=10, verbose=True, details=False, fix=False, filename=None):
|
|||
verbose = coerce_bool(verbose)
|
||||
details = coerce_bool(details)
|
||||
fix = coerce_bool(fix)
|
||||
end = int(top) if int(top) else -1
|
||||
|
||||
with hide('warnings', 'running', 'stdout'): # pylint: disable=not-context-manager
|
||||
with virtualenv(VENV_ROOT):
|
||||
if filename:
|
||||
filename = os.path.abspath(filename)
|
||||
if not os.path.exists(filename):
|
||||
print "Bad filename, specify a Python file"
|
||||
sys.exit(1)
|
||||
else:
|
||||
file_list = [filename]
|
||||
else:
|
||||
with cd(PROJECT_ROOT): # pylint: disable=not-context-manager
|
||||
file_list = [
|
||||
os.path.abspath(i.rstrip('\r'))
|
||||
for i in run('find . -name "*.py"').split("\n")
|
||||
]
|
||||
|
||||
if fix:
|
||||
for path_to_file in file_list:
|
||||
autopep8(path_to_file)
|
||||
top = int(top) or -1
|
||||
|
||||
file_list = generate_file_list(filename)
|
||||
results = get_tool_results(file_list)
|
||||
|
||||
if verbose:
|
||||
print "\ntotal pycodestyle flake8 pylint path_to_file\n"
|
||||
if fix:
|
||||
for item in sort_and_slice(results, top):
|
||||
autopep8(item['path_to_file'])
|
||||
# Recalculate results after autopep8 to surprise the user the least
|
||||
results = get_tool_results(file_list)
|
||||
|
||||
# Sort and slice
|
||||
for item in sorted(
|
||||
results,
|
||||
reverse=True,
|
||||
key=lambda x: x['total_violations']
|
||||
)[:end]:
|
||||
|
||||
print_item(item, verbose, details)
|
||||
print_results(results, top, verbose, details)
|
||||
sys.exit(sum([item['total_violations'] for item in results]))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# pylint: disable=too-many-lines,broad-except,too-many-instance-attributes,global-statement,too-few-public-methods
|
||||
# pylint: disable=too-many-statements,too-many-branches,attribute-defined-outside-init,too-many-arguments,no-member
|
||||
# pylint: disable=unused-argument,no-self-use,too-many-locals,unused-variable,too-many-nested-blocks,ungrouped-imports
|
||||
# pylint: disable=unused-argument,no-self-use,too-many-locals,unused-variable,too-many-nested-blocks
|
||||
# pylint: disable=too-many-return-statements,protected-access,super-init-not-called,non-parent-init-called
|
||||
"""
|
||||
Initialise the QT interface
|
||||
"""
|
||||
|
||||
from src.debug import logger # pylint: disable=wrong-import-order
|
||||
from debug import logger # pylint: disable=wrong-import-order
|
||||
|
||||
import hashlib
|
||||
import locale
|
||||
|
@ -34,38 +34,49 @@ except ImportError:
|
|||
|
||||
from sqlite3 import register_adapter
|
||||
|
||||
from src import (
|
||||
shared, defaults, queues, shutdown, state, openclpow, knownnodes, paths, l10n, helper_search, debug, upnp,
|
||||
)
|
||||
from src.bitmessageqt import sound, support, dialogs
|
||||
from src.bitmessageqt.foldertree import (
|
||||
import debug # pylint: disable=ungrouped-imports
|
||||
import defaults
|
||||
import helper_search
|
||||
import knownnodes
|
||||
import l10n
|
||||
import openclpow
|
||||
import paths
|
||||
import queues
|
||||
import shared
|
||||
import shutdown
|
||||
import state
|
||||
import upnp
|
||||
|
||||
from bitmessageqt import sound, support, dialogs
|
||||
from bitmessageqt.foldertree import (
|
||||
AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget, MessageList_AddressWidget,
|
||||
MessageList_SubjectWidget, Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress,
|
||||
)
|
||||
from src.bitmessageqt.account import (
|
||||
from bitmessageqt.account import (
|
||||
getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, GatewayAccount, MailchuckAccount, AccountColor,
|
||||
)
|
||||
from src.bitmessageqt.bitmessageui import Ui_MainWindow, settingsmixin
|
||||
from src.bitmessageqt.messageview import MessageView
|
||||
from src.bitmessageqt.migrationwizard import Ui_MigrationWizard
|
||||
from src.bitmessageqt.settings import Ui_settingsDialog
|
||||
from src.bitmessageqt.utils import str_broadcast_subscribers, avatarize
|
||||
from src.bitmessageqt.uisignaler import UISignaler
|
||||
from src.bitmessageqt.statusbar import BMStatusBar
|
||||
from src.proofofwork import getPowType
|
||||
from src.tr import _translate
|
||||
from src.addresses import decodeAddress, addBMIfNotPresent
|
||||
from src.bmconfigparser import BMConfigParser
|
||||
from src.namecoin import namecoinConnection
|
||||
from src.helper_ackPayload import genAckPayload
|
||||
from src.helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure
|
||||
from src.helper_generic import powQueueSize
|
||||
from src.network.stats import pendingDownload, pendingUpload
|
||||
from src.network.asyncore_pollchoose import set_rates
|
||||
from bitmessageqt.bitmessageui import Ui_MainWindow, settingsmixin
|
||||
from bitmessageqt.messageview import MessageView
|
||||
from bitmessageqt.migrationwizard import Ui_MigrationWizard
|
||||
from bitmessageqt.settings import Ui_settingsDialog
|
||||
from bitmessageqt.utils import str_broadcast_subscribers, avatarize
|
||||
from bitmessageqt.uisignaler import UISignaler
|
||||
from bitmessageqt.statusbar import BMStatusBar
|
||||
|
||||
from addresses import decodeAddress, addBMIfNotPresent
|
||||
from bmconfigparser import BMConfigParser
|
||||
from namecoin import namecoinConnection
|
||||
from helper_ackPayload import genAckPayload
|
||||
from helper_generic import powQueueSize
|
||||
from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure
|
||||
from network.stats import pendingDownload, pendingUpload
|
||||
from network.asyncore_pollchoose import set_rates
|
||||
from proofofwork import getPowType
|
||||
from tr import _translate
|
||||
|
||||
|
||||
try:
|
||||
from src.plugins.plugin import get_plugin, get_plugins
|
||||
from plugins.plugin import get_plugin, get_plugins
|
||||
except ImportError:
|
||||
get_plugins = False
|
||||
|
||||
|
@ -1489,7 +1500,7 @@ class MyForm(settingsmixin.SMainWindow): # pylint: disable=too-many-public-meth
|
|||
messagelist = self.getCurrentMessagelist()
|
||||
folder = self.getCurrentFolder()
|
||||
if event.key() == QtCore.Qt.Key_Delete:
|
||||
if isinstance(focus, [MessageView, QtGui.QTableWidget]):
|
||||
if isinstance(focus, (MessageView, QtGui.QTableWidget)):
|
||||
if folder == "sent":
|
||||
self.on_action_SentTrash()
|
||||
else:
|
||||
|
@ -3623,7 +3634,7 @@ class MyForm(settingsmixin.SMainWindow): # pylint: disable=too-many-public-meth
|
|||
self.ui.tableWidgetInbox,
|
||||
self.ui.tableWidgetInboxChans,
|
||||
self.ui.tableWidgetInboxSubscriptions)
|
||||
elif not isinstance(messageLists, [list, tuple]):
|
||||
elif not isinstance(messageLists, (list, tuple)):
|
||||
messageLists = (messageLists)
|
||||
for messageList in messageLists:
|
||||
if row is not None:
|
||||
|
@ -4751,7 +4762,7 @@ class MyForm(settingsmixin.SMainWindow): # pylint: disable=too-many-public-meth
|
|||
def updateStatusBar(self, data):
|
||||
"""TBC"""
|
||||
|
||||
if isinstance(data, [tuple, list]):
|
||||
if isinstance(data, (tuple, list)):
|
||||
option = data[1]
|
||||
message = data[0]
|
||||
else:
|
||||
|
|
Reference in New Issue
Block a user