Added: Fabric features

* default_hosts decorator
 * filelist from git diff feature
 * task to run tests
coffeedogs 5 years ago
parent 74ddda8775
commit 92c3173faf
No known key found for this signature in database
GPG Key ID: 9D818C503D0B7E70

@ -17,7 +17,7 @@ For more help on a particular command
from fabric.api import env
from fabfile.tasks import code_quality
from fabfile.tasks import code_quality, test
# Without this, `fab -l` would display the whole docstring as preamble
@ -26,6 +26,7 @@ __doc__ = ""
# This list defines which tasks are made available to the user
__all__ = [
# Honour the user's ssh client configuration

@ -6,8 +6,10 @@ A library of functions and constants for tasks to make use of.
import os
import sys
import re
from functools import wraps
from fabric.api import run, hide
from fabric.api import run, hide, cd, env
from fabric.context_managers import settings, shell_env
from fabvenv import virtualenv
@ -18,6 +20,14 @@ VENV_ROOT = os.path.expanduser(os.path.join('~', '.virtualenvs', 'pybitmessage-d
PYTHONPATH = os.path.join(PROJECT_ROOT, 'src',)
def coerce_list(value):
"""Coerce a value into a list"""
if isinstance(value, str):
return value.split(',')
sys.exit("Bad string value {}".format(value))
def coerce_bool(value):
"""Coerce a value into a boolean"""
if isinstance(value, bool):
@ -51,6 +61,29 @@ def flatten(data):
return result
def filelist_from_git(rev=None):
"""Return a list of files based on git output"""
cmd = 'git diff --name-only'
if rev:
if rev in ['cached', 'staged']:
cmd += ' --{}'.format(rev)
elif rev == 'working':
cmd += ' -r {}'.format(rev)
with cd(PROJECT_ROOT):
with hide('running', 'stdout'):
results = []
ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
clean = ansi_escape.sub('', run(cmd))
clean = re.sub('\n', '', clean)
for line in clean.split('\r'):
if line.endswith(".py"):
return results
def pycodestyle(path_to_file):
"""Run pycodestyle on a file"""
with virtualenv(VENV_ROOT):
@ -148,3 +181,20 @@ def get_filtered_pylint_output(path_to_file):
not i.startswith('Using config file'),
def default_hosts(hosts):
"""Decorator to apply default hosts to a task"""
def real_decorator(func):
"""Manipulate env"""
env.hosts = env.hosts or hosts
def wrapper(*args, **kwargs):
"""Original function called from here"""
return func(*args, **kwargs)
return wrapper
return real_decorator

@ -1,8 +1,6 @@
# pylint: disable=not-context-manager
Fabric tasks for PyBitmessage devops operations.
# pylint: disable=not-context-manager
import os
@ -12,7 +10,7 @@ from fabric.api import run, task, hide, cd
from fabvenv import virtualenv
from fabfile.lib import (
autopep8, PROJECT_ROOT, VENV_ROOT, coerce_bool, flatten,
autopep8, PROJECT_ROOT, VENV_ROOT, coerce_bool, flatten, filelist_from_git, default_hosts,
get_filtered_pycodestyle_output, get_filtered_flake8_output, get_filtered_pylint_output,
@ -28,9 +26,9 @@ def get_tool_results(file_list):
result['pylint_violations'] = get_filtered_pylint_output(path_to_file)
result['path_to_file'] = path_to_file
result['total_violations'] = sum([
return results
@ -39,7 +37,7 @@ def get_tool_results(file_list):
def print_results(results, top, verbose, details):
"""Print an item with the appropriate verbosity / detail"""
if verbose:
if verbose and results:
print ''.join(
@ -117,7 +115,8 @@ def generate_file_list(filename):
def code_quality(verbose=True, details=False, fix=False, filename=None, top=10):
def code_quality(verbose=True, details=False, fix=False, filename=None, top=10, rev=None):
Check code quality.
@ -128,6 +127,8 @@ def code_quality(verbose=True, details=False, fix=False, filename=None, top=10):
$ fab -H localhost code_quality
:param rev: If not None, act on files changed since this commit. 'cached/staged' and 'working' have special meaning
:type rev: str or None, default None
: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
@ -146,13 +147,14 @@ def code_quality(verbose=True, details=False, fix=False, filename=None, top=10):
Intended to be temporary until we have improved code quality and have safeguards to maintain it in place.
# pylint: disable=too-many-arguments
verbose = coerce_bool(verbose)
details = coerce_bool(details)
fix = coerce_bool(fix)
top = int(top) or -1
file_list = generate_file_list(filename)
file_list = generate_file_list(filename) if not rev else filelist_from_git(rev)
results = get_tool_results(file_list)
if fix:
@ -163,3 +165,18 @@ def code_quality(verbose=True, details=False, fix=False, filename=None, top=10):
print_results(results, top, verbose, details)
sys.exit(sum([item['total_violations'] for item in results]))
def test():
"""Run tests on the code"""
with cd(PROJECT_ROOT):
with virtualenv(VENV_ROOT):
run('pip uninstall -y pybitmessage')
run('python install')
run('pybitmessage -t')
run('python test')