Expand CI + pre-commit (#991)

* Initial attempt at extra checks in CI land

* Adding flake8 config

* Adding pre-commit configuration and explanation in CONTRIBUTING.md
This commit is contained in:
Ogi Moore 2019-08-29 13:56:25 -07:00 committed by GitHub
parent fd11e1352d
commit 584c4516f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 469 additions and 202 deletions

49
.flake8 Normal file
View File

@ -0,0 +1,49 @@
[flake8]
exclude = .git,.tox,__pycache__,doc,old,build,dist
show_source = True
statistics = True
verbose = 2
select =
E101,
E112,
E122,
E125,
E133,
E223,
E224,
E242,
E273,
E274,
E901,
E902,
W191,
W601,
W602,
W603,
W604,
E124,
E231,
E211,
E261,
E271,
E272,
E304,
F401,
F402,
F403,
F404,
E501,
E502,
E702,
E703,
E711,
E712,
E721,
F811,
F812,
F821,
F822,
F823,
F831,
F841,
W292

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

@ -0,0 +1,11 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
sha: master
hooks:
- id: check-added-large-files
args: ['--maxkb=100']
- id: check-case-conflict
- id: end-of-file-fixer
- id: fix-encoding-pragma
- id: mixed-line-ending
args: [--fix=lf]

View File

@ -1,6 +1,6 @@
# Contributing to PyQtGraph # Contributing to PyQtGraph
Contributions to pyqtgraph are welcome! Contributions to pyqtgraph are welcome!
Please use the following guidelines when preparing changes: Please use the following guidelines when preparing changes:
@ -13,11 +13,13 @@ Please use the following guidelines when preparing changes:
## Documentation ## Documentation
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses nose / pytest style testing, so tests should usually be included in a tests/ directory adjacent to the relevant code. * Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses nose / pytest style testing, so tests should usually be included in a tests/ directory adjacent to the relevant code.
* Documentation is generated with sphinx; please check that docstring changes compile correctly * Documentation is generated with sphinx; please check that docstring changes compile correctly
## Style guidelines ## Style guidelines
### Rules
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced rigorously as long as the code is clean and readable. * PyQtGraph prefers PEP8 for most style issues, but this is not enforced rigorously as long as the code is clean and readable.
* Use `python setup.py style` to see whether your code follows the mandatory style guidelines checked by flake8. * Use `python setup.py style` to see whether your code follows the mandatory style guidelines checked by flake8.
* Exception 1: All variable names should use camelCase rather than underscore_separation. This is done for consistency with Qt * Exception 1: All variable names should use camelCase rather than underscore_separation. This is done for consistency with Qt
@ -33,9 +35,15 @@ Please use the following guidelines when preparing changes:
============== ======================================================== ============== ========================================================
``` ```
QObject subclasses that implement new signals should also describe QObject subclasses that implement new signals should also describe
these in a similar table. these in a similar table.
### Pre-Commit
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to ensure it conforms to various standards, such as `flake8`, utf-8 encoding pragma, line-ending fixers, and so on. If any of the checks fail, the commit will be rejected, and you will have the opportunity to make the necessary fixes before adding and committing a file again. This ensures that every commit made conforms to (most) of the styling standards that the library enforces; and you will most likely pass the code style checks by the CI.
To make use of `pre-commit`, have it available in your `$PATH` and run `pre-commit install` from the root directory of PyQtGraph.
## Testing Setting up a test environment ## Testing Setting up a test environment
### Dependencies ### Dependencies

View File

@ -1,7 +1,3 @@
############################################################################################
# This config was rectrieved in no small part from https://github.com/slaclab/pydm
############################################################################################
trigger: trigger:
branches: branches:
include: include:
@ -20,19 +16,83 @@ pr:
variables: variables:
OFFICIAL_REPO: 'pyqtgraph/pyqtgraph' OFFICIAL_REPO: 'pyqtgraph/pyqtgraph'
DEFAULT_MERGE_BRANCH: 'develop'
jobs: stages:
- template: azure-test-template.yml - stage: "pre_test"
parameters: jobs:
name: Linux - job: check_diff_size
pool:
vmImage: 'Ubuntu 16.04' vmImage: 'Ubuntu 16.04'
steps:
- bash: |
git config --global advice.detachedHead false
mkdir ~/repo-clone && cd ~/repo-clone
git init
git remote add -t $(Build.SourceBranchName) origin $(Build.Repository.Uri)
git remote add -t ${DEFAULT_MERGE_BRANCH} upstream https://github.com/${OFFICIAL_REPO}.git
git fetch origin $(Build.SourceBranchName)
git fetch upstream ${DEFAULT_MERGE_BRANCH}
git checkout $(Build.SourceBranchName)
MERGE_SIZE=`du -s . | sed -e "s/\t.*//"`
echo -e "Merge Size ${MERGE_SIZE}"
git checkout ${DEFAULT_MERGE_BRANCH}
TARGET_SIZE=`du -s . | sed -e "s/\t.*//"`
echo -e "Target Size ${TARGET_SIZE}"
if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then
SIZE_DIFF=`expr \( ${MERGE_SIZE} - ${TARGET_SIZE} \)`;
else
SIZE_DIFF=0;
fi;
echo -e "Estimated content size difference = ${SIZE_DIFF} kB" &&
test ${SIZE_DIFF} -lt 100;
displayName: 'Diff Size Check'
continueOnError: true
- job: "style_check"
pool:
vmImage: "Ubuntu 16.04"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.7
- bash: |
pip install flake8
python setup.py style
displayName: 'flake8 check'
continueOnError: true
- job: "build_wheel"
pool:
vmImage: 'Ubuntu 16.04'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.7
- script: |
python -m pip install setuptools wheel
python setup.py bdist_wheel --universal
displayName: "Build Python Wheel"
continueOnError: false
- publish: dist
artifact: wheel
- stage: "test"
jobs:
- template: azure-test-template.yml - template: azure-test-template.yml
parameters: parameters:
name: Windows name: linux
vmImage: 'Ubuntu 16.04'
- template: azure-test-template.yml
parameters:
name: windows
vmImage: 'vs2017-win2016' vmImage: 'vs2017-win2016'
- template: azure-test-template.yml - template: azure-test-template.yml
parameters: parameters:
name: MacOS name: macOS
vmImage: 'macOS-10.13' vmImage: 'macOS-10.13'

View File

@ -26,16 +26,22 @@ jobs:
python.version: "3.6" python.version: "3.6"
qt.bindings: "pyside2" qt.bindings: "pyside2"
install.method: "conda" install.method: "conda"
Python37-PyQt-5.12: Python37-PyQt-5.13:
python.version: '3.7' python.version: '3.7'
qt.bindings: "PyQt5" qt.bindings: "PyQt5"
install.method: "pip" install.method: "pip"
Python37-PySide2-5.12: Python37-PySide2-5.13:
python.version: "3.7" python.version: "3.7"
qt.bindings: "PySide2" qt.bindings: "PySide2"
install.method: "pip" install.method: "pip"
steps: steps:
- task: DownloadPipelineArtifact@2
inputs:
source: 'current'
artifact: wheel
path: 'dist'
- task: ScreenResolutionUtility@1 - task: ScreenResolutionUtility@1
inputs: inputs:
displaySettings: 'specific' displaySettings: 'specific'
@ -43,6 +49,11 @@ jobs:
height: '1080' height: '1080'
condition: eq(variables['agent.os'], 'Windows_NT' ) condition: eq(variables['agent.os'], 'Windows_NT' )
- task: UsePythonVersion@0
inputs:
versionSpec: $(python.version)
condition: eq(variables['install.method'], 'pip')
- script: | - script: |
curl -LJO https://github.com/pal1000/mesa-dist-win/releases/download/19.1.0/mesa3d-19.1.0-release-msvc.exe curl -LJO https://github.com/pal1000/mesa-dist-win/releases/download/19.1.0/mesa3d-19.1.0-release-msvc.exe
7z x mesa3d-19.1.0-release-msvc.exe 7z x mesa3d-19.1.0-release-msvc.exe
@ -60,75 +71,71 @@ jobs:
displayName: "Install Windows-Mesa OpenGL DLL" displayName: "Install Windows-Mesa OpenGL DLL"
condition: eq(variables['agent.os'], 'Windows_NT') condition: eq(variables['agent.os'], 'Windows_NT')
- task: UsePythonVersion@0
inputs:
versionSpec: $(python.version)
condition: eq(variables['install.method'], 'pip')
- bash: | - bash: |
if [ $(agent.os) == 'Linux' ] if [ $(agent.os) == 'Linux' ]
then then
echo '##vso[task.prependpath]/usr/share/miniconda/bin' echo "##vso[task.prependpath]$CONDA/bin"
if [ $(python.version) == '2.7' ]
then
echo "Grabbing Older Miniconda"
wget https://repo.anaconda.com/miniconda/Miniconda2-4.6.14-Linux-x86_64.sh -O Miniconda.sh
bash Miniconda.sh -b -p $CONDA -f
fi
elif [ $(agent.os) == 'Darwin' ] elif [ $(agent.os) == 'Darwin' ]
then then
echo '##vso[task.prependpath]$CONDA/bin' sudo chown -R $USER $CONDA
sudo install -d -m 0777 /usr/local/miniconda/envs echo "##vso[task.prependpath]$CONDA/bin"
if [ $(python.version) == '2.7' ]
then
echo "Grabbing Older Miniconda"
wget https://repo.anaconda.com/miniconda/Miniconda2-4.6.14-MacOSX-x86_64.sh -O Miniconda.sh
bash Miniconda.sh -b -p $CONDA -f
fi
elif [ $(agent.os) == 'Windows_NT' ] elif [ $(agent.os) == 'Windows_NT' ]
then then
echo "##vso[task.prependpath]$env:CONDA\Scripts" echo "##vso[task.prependpath]$CONDA/Scripts"
else else
echo 'Just what OS are you using?' echo 'Just what OS are you using?'
fi fi
displayName: 'Add Conda to $PATH' displayName: 'Add Conda To $PATH'
condition: eq(variables['install.method'], 'conda' ) condition: eq(variables['install.method'], 'conda' )
- task: CondaEnvironment@0
displayName: 'Create Conda Environment'
condition: eq(variables['install.method'], 'conda')
inputs:
environmentName: 'test-environment-$(python.version)'
packageSpecs: 'python=$(python.version)'
updateConda: false
- bash: | - bash: |
if [ $(install.method) == "conda" ] if [ $(install.method) == "conda" ]
then then
conda create --name test-environment-$(python.version) python=$(python.version) --yes
echo "Conda Info:"
conda info
echo "Installing qt-bindings"
source activate test-environment-$(python.version) source activate test-environment-$(python.version)
conda install -c conda-forge $(qt.bindings) numpy scipy pyopengl pytest six coverage --yes --quiet
if [ $(agent.os) == "Linux" ] && [ $(python.version) == "2.7" ]
then
conda install $(qt.bindings) --yes
else
conda install -c conda-forge $(qt.bindings) --yes
fi
echo "Installing remainder of dependencies"
conda install -c conda-forge numpy scipy six pyopengl --yes
else else
pip install $(qt.bindings) numpy scipy pyopengl pytest six coverage pip install $(qt.bindings) numpy scipy pyopengl six
fi fi
pip install pytest-xdist pytest-cov echo ""
pip install pytest pytest-xdist pytest-cov coverage
if [ $(python.version) == "2.7" ] if [ $(python.version) == "2.7" ]
then then
pip install pytest-faulthandler pip install pytest-faulthandler==1.6.0
export PYTEST_ADDOPTS="--faulthandler-timeout=15" export PYTEST_ADDOPTS="--faulthandler-timeout=15"
fi fi
displayName: "Install Dependencies" displayName: "Install Dependencies"
- bash: | - bash: |
if [ $(install.method) == "conda" ] if [ $(install.method) == "conda" ]
then then
source activate test-environment-$(python.version) source activate test-environment-$(python.version)
fi fi
pip install setuptools wheel python -m pip install --no-index --find-links=dist pyqtgraph
python setup.py bdist_wheel displayName: 'Install Wheel'
pip install dist/*.whl
displayName: 'Build Wheel and Install'
- task: CopyFiles@2
inputs:
contents: 'dist/**'
targetFolder: $(Build.ArtifactStagingDirectory)
cleanTargetFolder: true
displayName: "Copy Binary Wheel Distribution To Artifacts"
- task: PublishBuildArtifacts@1
displayName: 'Publish Binary Wheel'
condition: always()
inputs:
pathtoPublish: $(Build.ArtifactStagingDirectory)/dist
artifactName: Distributions
- bash: | - bash: |
sudo apt-get install -y libxkbcommon-x11-0 # herbstluftwm sudo apt-get install -y libxkbcommon-x11-0 # herbstluftwm

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from pyqtgraph import Qt from pyqtgraph import Qt
from . import utils from . import utils
@ -5,7 +6,6 @@ from collections import namedtuple
import errno import errno
import importlib import importlib
import itertools import itertools
import pkgutil
import pytest import pytest
import os, sys import os, sys
import subprocess import subprocess
@ -41,7 +41,12 @@ if os.getenv('TRAVIS') is not None:
files = sorted(set(utils.buildFileList(utils.examples))) files = sorted(set(utils.buildFileList(utils.examples)))
frontends = {Qt.PYQT4: False, Qt.PYQT5: False, Qt.PYSIDE: False, Qt.PYSIDE2: False} frontends = {
Qt.PYQT4: False,
Qt.PYQT5: False,
Qt.PYSIDE: False,
Qt.PYSIDE2: False
}
# sort out which of the front ends are available # sort out which of the front ends are available
for frontend in frontends.keys(): for frontend in frontends.keys():
try: try:
@ -50,48 +55,136 @@ for frontend in frontends.keys():
except ImportError: except ImportError:
pass pass
installedFrontends = sorted([frontend for frontend, isPresent in frontends.items() if isPresent]) installedFrontends = sorted([
frontend for frontend, isPresent in frontends.items() if isPresent
])
exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"]) exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"])
conditionalExampleTests = { conditionalExamples = {
"hdf5.py": exceptionCondition(False, reason="Example requires user interaction and is not suitable for testing"), "hdf5.py": exceptionCondition(
"RemoteSpeedTest.py": exceptionCondition(False, reason="Test is being problematic on CI machines"), False,
"optics_demos.py": exceptionCondition(not frontends[Qt.PYSIDE], reason="Test fails due to PySide bug: https://bugreports.qt.io/browse/PYSIDE-671"), reason="Example requires user interaction"
'GLVolumeItem.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), ),
'GLIsosurface.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), "RemoteSpeedTest.py": exceptionCondition(
'GLSurfacePlot.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), False,
'GLScatterPlotItem.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), reason="Test is being problematic on CI machines"
'GLshaders.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), ),
'GLLinePlotItem.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), "optics_demos.py": exceptionCondition(
'GLMeshItem.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939"), not frontends[Qt.PYSIDE],
'GLImageItem.py': exceptionCondition(not(sys.platform == "darwin" and sys.version_info[0] == 2 and (frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])), reason="glClear does not work on macOS + Python2.7 + Qt4: https://github.com/pyqtgraph/pyqtgraph/issues/939") reason=(
"Test fails due to PySide bug: ",
"https://bugreports.qt.io/browse/PYSIDE-671"
)
),
'GLVolumeItem.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLIsosurface.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLSurfacePlot.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLScatterPlotItem.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLshaders.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLLinePlotItem.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLMeshItem.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
),
'GLImageItem.py': exceptionCondition(
not(sys.platform == "darwin" and
sys.version_info[0] == 2 and
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
reason=(
"glClear does not work on macOS + Python2.7 + Qt4: ",
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
)
)
} }
@pytest.mark.parametrize( @pytest.mark.parametrize(
"frontend, f", "frontend, f",
[ [
pytest.param( pytest.param(
frontend, frontend,
f, f,
marks=pytest.mark.skipif(conditionalExampleTests[f[1]].condition is False, marks=pytest.mark.skipif(
reason=conditionalExampleTests[f[1]].reason) if f[1] in conditionalExampleTests.keys() else (), conditionalExamples[f[1]].condition is False,
) reason=conditionalExamples[f[1]].reason
for frontend, f, in itertools.product(installedFrontends, files) ) if f[1] in conditionalExamples.keys() else (),
], )
ids = [" {} - {} ".format(f[1], frontend) for frontend, f in itertools.product(installedFrontends, files)] for frontend, f, in itertools.product(installedFrontends, files)
],
ids = [
" {} - {} ".format(f[1], frontend)
for frontend, f in itertools.product(
installedFrontends,
files
)
]
) )
def testExamples(frontend, f, graphicsSystem=None): def testExamples(frontend, f, graphicsSystem=None):
# runExampleFile(f[0], f[1], sys.executable, frontend) # runExampleFile(f[0], f[1], sys.executable, frontend)
name, file = f name, file = f
global path global path
fn = os.path.join(path,file) fn = os.path.join(path, file)
os.chdir(path) os.chdir(path)
sys.stdout.write("{} ".format(name)) sys.stdout.write("{} ".format(name))
sys.stdout.flush() sys.stdout.flush()
import1 = "import %s" % frontend if frontend != '' else '' import1 = "import %s" % frontend if frontend != '' else ''
import2 = os.path.splitext(os.path.split(fn)[1])[0] import2 = os.path.splitext(os.path.split(fn)[1])[0]
graphicsSystem = '' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem graphicsSystem = (
'' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem
)
code = """ code = """
try: try:
%s %s
@ -123,7 +216,7 @@ except:
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8')) process.stdin.write(code.encode('UTF-8'))
process.stdin.close() ##? process.stdin.close()
output = '' output = ''
fail = False fail = False
while True: while True:
@ -146,10 +239,14 @@ except:
process.kill() process.kill()
#res = process.communicate() #res = process.communicate()
res = (process.stdout.read(), process.stderr.read()) res = (process.stdout.read(), process.stderr.read())
if fail or 'exception' in res[1].decode().lower() or 'error' in res[1].decode().lower(): if (fail or
'exception' in res[1].decode().lower() or
'error' in res[1].decode().lower()):
print(res[0].decode()) print(res[0].decode())
print(res[1].decode()) print(res[1].decode())
pytest.fail("{}\n{}\nFailed {} Example Test Located in {} ".format(res[0].decode(), res[1].decode(), name, file), pytrace=False) pytest.fail("{}\n{}\nFailed {} Example Test Located in {} "
.format(res[0].decode(), res[1].decode(), name, file),
pytrace=False)
if __name__ == "__main__": if __name__ == "__main__":
pytest.cmdline.main() pytest.cmdline.main()

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
DESCRIPTION = """\ DESCRIPTION = """\
PyQtGraph is a pure-python graphics and GUI library built on PyQt4/PySide and PyQtGraph is a pure-python graphics and GUI library built on PyQt4/PyQt5/PySide/PySide2 and
numpy. numpy.
It is intended for use in mathematics / scientific / engineering applications. It is intended for use in mathematics / scientific / engineering applications.
@ -12,14 +13,13 @@ setupOpts = dict(
name='pyqtgraph', name='pyqtgraph',
description='Scientific Graphics and GUI Library for Python', description='Scientific Graphics and GUI Library for Python',
long_description=DESCRIPTION, long_description=DESCRIPTION,
license='MIT', license = 'MIT',
url='http://www.pyqtgraph.org', url='http://www.pyqtgraph.org',
author='Luke Campagnola', author='Luke Campagnola',
author_email='luke.campagnola@gmail.com', author_email='luke.campagnola@gmail.com',
classifiers = [ classifiers = [
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
@ -145,4 +145,3 @@ setup(
], ],
**setupOpts **setupOpts
) )

View File

@ -10,14 +10,15 @@ except ImportError:
output = proc.stdout.read() output = proc.stdout.read()
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
ex = Exception("Process had nonzero return value %d" % proc.returncode) ex = Exception("Process had nonzero return value "
+ "%d " % proc.returncode)
ex.returncode = proc.returncode ex.returncode = proc.returncode
ex.output = output ex.output = output
raise ex raise ex
return output return output
# Maximum allowed repository size difference (in kB) following merge. # Maximum allowed repository size difference (in kB) following merge.
# This is used to prevent large files from being inappropriately added to # This is used to prevent large files from being inappropriately added to
# the repository history. # the repository history.
MERGE_SIZE_LIMIT = 100 MERGE_SIZE_LIMIT = 100
@ -42,19 +43,19 @@ FLAKE_MANDATORY = set([
'E901', # SyntaxError or IndentationError 'E901', # SyntaxError or IndentationError
'E902', # IOError 'E902', # IOError
'W191', # indentation contains tabs 'W191', # indentation contains tabs
'W601', # .has_key() is deprecated, use in 'W601', # .has_key() is deprecated, use in
'W602', # deprecated form of raising exception 'W602', # deprecated form of raising exception
'W603', # <> is deprecated, use != 'W603', # <> is deprecated, use !=
'W604', # backticks are deprecated, use repr() 'W604', # backticks are deprecated, use repr()
]) ])
FLAKE_RECOMMENDED = set([ FLAKE_RECOMMENDED = set([
'E124', # closing bracket does not match visual indentation 'E124', # closing bracket does not match visual indentation
'E231', # missing whitespace after , 'E231', # missing whitespace after ,
'E211', # whitespace before ( 'E211', # whitespace before (
'E261', # at least two spaces before inline comment 'E261', # at least two spaces before inline comment
'E271', # multiple spaces after keyword 'E271', # multiple spaces after keyword
@ -65,10 +66,10 @@ FLAKE_RECOMMENDED = set([
'F402', # import module from line N shadowed by loop variable 'F402', # import module from line N shadowed by loop variable
'F403', # from module import * used; unable to detect undefined names 'F403', # from module import * used; unable to detect undefined names
'F404', # future import(s) name after other statements 'F404', # future import(s) name after other statements
'E501', # line too long (82 > 79 characters) 'E501', # line too long (82 > 79 characters)
'E502', # the backslash is redundant between brackets 'E502', # the backslash is redundant between brackets
'E702', # multiple statements on one line (semicolon) 'E702', # multiple statements on one line (semicolon)
'E703', # statement ends with a semicolon 'E703', # statement ends with a semicolon
'E711', # comparison to None should be if cond is None: 'E711', # comparison to None should be if cond is None:
@ -82,7 +83,7 @@ FLAKE_RECOMMENDED = set([
'F823', # local variable name ... referenced before assignment 'F823', # local variable name ... referenced before assignment
'F831', # duplicate argument name in function definition 'F831', # duplicate argument name in function definition
'F841', # local variable name is assigned to but never used 'F841', # local variable name is assigned to but never used
'W292', # no newline at end of file 'W292', # no newline at end of file
]) ])
@ -93,7 +94,7 @@ FLAKE_OPTIONAL = set([
'E126', # continuation line over-indented for hanging indent 'E126', # continuation line over-indented for hanging indent
'E127', # continuation line over-indented for visual indent 'E127', # continuation line over-indented for visual indent
'E128', # continuation line under-indented for visual indent 'E128', # continuation line under-indented for visual indent
'E201', # whitespace after ( 'E201', # whitespace after (
'E202', # whitespace before ) 'E202', # whitespace before )
'E203', # whitespace before : 'E203', # whitespace before :
@ -105,19 +106,19 @@ FLAKE_OPTIONAL = set([
'E228', # missing whitespace around modulo operator 'E228', # missing whitespace around modulo operator
'E241', # multiple spaces after , 'E241', # multiple spaces after ,
'E251', # unexpected spaces around keyword / parameter equals 'E251', # unexpected spaces around keyword / parameter equals
'E262', # inline comment should start with # 'E262', # inline comment should start with #
'E301', # expected 1 blank line, found 0 'E301', # expected 1 blank line, found 0
'E302', # expected 2 blank lines, found 0 'E302', # expected 2 blank lines, found 0
'E303', # too many blank lines (3) 'E303', # too many blank lines (3)
'E401', # multiple imports on one line 'E401', # multiple imports on one line
'E701', # multiple statements on one line (colon) 'E701', # multiple statements on one line (colon)
'W291', # trailing whitespace 'W291', # trailing whitespace
'W293', # blank line contains whitespace 'W293', # blank line contains whitespace
'W391', # blank line at end of file 'W391', # blank line at end of file
]) ])
@ -128,23 +129,10 @@ FLAKE_IGNORE = set([
]) ])
#def checkStyle():
#try:
#out = check_output(['flake8', '--select=%s' % FLAKE_TESTS, '--statistics', 'pyqtgraph/'])
#ret = 0
#print("All style checks OK.")
#except Exception as e:
#out = e.output
#ret = e.returncode
#print(out.decode('utf-8'))
#return ret
def checkStyle(): def checkStyle():
""" Run flake8, checking only lines that are modified since the last """ Run flake8, checking only lines that are modified since the last
git commit. """ git commit. """
test = [ 1,2,3 ]
# First check _all_ code against mandatory error codes # First check _all_ code against mandatory error codes
print('flake8: check all code against mandatory error set...') print('flake8: check all code against mandatory error set...')
errors = ','.join(FLAKE_MANDATORY) errors = ','.join(FLAKE_MANDATORY)
@ -154,39 +142,47 @@ def checkStyle():
output = proc.stdout.read().decode('utf-8') output = proc.stdout.read().decode('utf-8')
ret = proc.wait() ret = proc.wait()
printFlakeOutput(output) printFlakeOutput(output)
# Check for DOS newlines # Check for DOS newlines
print('check line endings in all files...') print('check line endings in all files...')
count = 0 count = 0
allowedEndings = set([None, '\n']) allowedEndings = set([None, '\n'])
for path, dirs, files in os.walk('.'): for path, dirs, files in os.walk('.'):
if path.startswith("." + os.path.sep + ".tox"):
continue
for f in files: for f in files:
if os.path.splitext(f)[1] not in ('.py', '.rst'): if os.path.splitext(f)[1] not in ('.py', '.rst'):
continue continue
filename = os.path.join(path, f) filename = os.path.join(path, f)
fh = open(filename, 'U') fh = open(filename, 'U')
x = fh.readlines() _ = fh.readlines()
endings = set(fh.newlines if isinstance(fh.newlines, tuple) else (fh.newlines,)) endings = set(
fh.newlines
if isinstance(fh.newlines, tuple)
else (fh.newlines,)
)
endings -= allowedEndings endings -= allowedEndings
if len(endings) > 0: if len(endings) > 0:
print("\033[0;31m" + "File has invalid line endings: %s" % filename + "\033[0m") print("\033[0;31m"
+ "File has invalid line endings: "
+ "%s" % filename + "\033[0m")
ret = ret | 2 ret = ret | 2
count += 1 count += 1
print('checked line endings in %d files' % count) print('checked line endings in %d files' % count)
# Next check new code with optional error codes # Next check new code with optional error codes
print('flake8: check new code against recommended error set...') print('flake8: check new code against recommended error set...')
diff = subprocess.check_output(['git', 'diff']) diff = subprocess.check_output(['git', 'diff'])
proc = subprocess.Popen(['flake8', '--diff', #'--show-source', proc = subprocess.Popen(['flake8', '--diff', # '--show-source',
'--ignore=' + errors], '--ignore=' + errors],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
proc.stdin.write(diff) proc.stdin.write(diff)
proc.stdin.close() proc.stdin.close()
output = proc.stdout.read().decode('utf-8') output = proc.stdout.read().decode('utf-8')
ret |= printFlakeOutput(output) ret |= printFlakeOutput(output)
if ret == 0: if ret == 0:
print('style test passed.') print('style test passed.')
else: else:
@ -244,14 +240,20 @@ def unitTests():
return ret return ret
def checkMergeSize(sourceBranch=None, targetBranch=None, sourceRepo=None, targetRepo=None): def checkMergeSize(
sourceBranch=None,
targetBranch=None,
sourceRepo=None,
targetRepo=None
):
""" """
Check that a git merge would not increase the repository size by MERGE_SIZE_LIMIT. Check that a git merge would not increase the repository size by
MERGE_SIZE_LIMIT.
""" """
if sourceBranch is None: if sourceBranch is None:
sourceBranch = getGitBranch() sourceBranch = getGitBranch()
sourceRepo = '..' sourceRepo = '..'
if targetBranch is None: if targetBranch is None:
if sourceBranch == 'develop': if sourceBranch == 'develop':
targetBranch = 'develop' targetBranch = 'develop'
@ -259,38 +261,38 @@ def checkMergeSize(sourceBranch=None, targetBranch=None, sourceRepo=None, target
else: else:
targetBranch = 'develop' targetBranch = 'develop'
targetRepo = '..' targetRepo = '..'
workingDir = '__merge-test-clone' workingDir = '__merge-test-clone'
env = dict(TARGET_BRANCH=targetBranch, env = dict(TARGET_BRANCH=targetBranch,
SOURCE_BRANCH=sourceBranch, SOURCE_BRANCH=sourceBranch,
TARGET_REPO=targetRepo, TARGET_REPO=targetRepo,
SOURCE_REPO=sourceRepo, SOURCE_REPO=sourceRepo,
WORKING_DIR=workingDir, WORKING_DIR=workingDir,
) )
print("Testing merge size difference:\n" print("Testing merge size difference:\n"
" SOURCE: {SOURCE_REPO} {SOURCE_BRANCH}\n" " SOURCE: {SOURCE_REPO} {SOURCE_BRANCH}\n"
" TARGET: {TARGET_BRANCH} {TARGET_REPO}".format(**env)) " TARGET: {TARGET_BRANCH} {TARGET_REPO}".format(**env))
setup = """ setup = """
mkdir {WORKING_DIR} && cd {WORKING_DIR} && mkdir {WORKING_DIR} && cd {WORKING_DIR} &&
git init && git remote add -t {TARGET_BRANCH} target {TARGET_REPO} && git init && git remote add -t {TARGET_BRANCH} target {TARGET_REPO} &&
git fetch target {TARGET_BRANCH} && git fetch target {TARGET_BRANCH} &&
git checkout -qf target/{TARGET_BRANCH} && git checkout -qf target/{TARGET_BRANCH} &&
git gc -q --aggressive git gc -q --aggressive
""".format(**env) """.format(**env)
checkSize = """ checkSize = """
cd {WORKING_DIR} && cd {WORKING_DIR} &&
du -s . | sed -e "s/\t.*//" du -s . | sed -e "s/\t.*//"
""".format(**env) """.format(**env)
merge = """ merge = """
cd {WORKING_DIR} && cd {WORKING_DIR} &&
git pull -q {SOURCE_REPO} {SOURCE_BRANCH} && git pull -q {SOURCE_REPO} {SOURCE_BRANCH} &&
git gc -q --aggressive git gc -q --aggressive
""".format(**env) """.format(**env)
try: try:
print("Check out target branch:\n" + setup) print("Check out target branch:\n" + setup)
check_call(setup, shell=True) check_call(setup, shell=True)
@ -300,13 +302,17 @@ def checkMergeSize(sourceBranch=None, targetBranch=None, sourceRepo=None, target
check_call(merge, shell=True) check_call(merge, shell=True)
mergeSize = int(check_output(checkSize, shell=True)) mergeSize = int(check_output(checkSize, shell=True))
print("MERGE SIZE: %d kB" % mergeSize) print("MERGE SIZE: %d kB" % mergeSize)
diff = mergeSize - targetSize diff = mergeSize - targetSize
if diff <= MERGE_SIZE_LIMIT: if diff <= MERGE_SIZE_LIMIT:
print("DIFFERENCE: %d kB [OK]" % diff) print("DIFFERENCE: %d kB [OK]" % diff)
return 0 return 0
else: else:
print("\033[0;31m" + "DIFFERENCE: %d kB [exceeds %d kB]" % (diff, MERGE_SIZE_LIMIT) + "\033[0m") print("\033[0;31m"
+ "DIFFERENCE: %d kB [exceeds %d kB]" % (
diff,
MERGE_SIZE_LIMIT)
+ "\033[0m")
return 2 return 2
finally: finally:
if os.path.isdir(workingDir): if os.path.isdir(workingDir):
@ -327,7 +333,11 @@ def mergeTests():
def listAllPackages(pkgroot): def listAllPackages(pkgroot):
path = os.getcwd() path = os.getcwd()
n = len(path.split(os.path.sep)) n = len(path.split(os.path.sep))
subdirs = [i[0].split(os.path.sep)[n:] for i in os.walk(os.path.join(path, pkgroot)) if '__init__.py' in i[2]] subdirs = [
i[0].split(os.path.sep)[n:]
for i in os.walk(os.path.join(path, pkgroot))
if '__init__.py' in i[2]
]
return ['.'.join(p) for p in subdirs] return ['.'.join(p) for p in subdirs]
@ -338,48 +348,61 @@ def getInitVersion(pkgroot):
init = open(initfile).read() init = open(initfile).read()
m = re.search(r'__version__ = (\S+)\n', init) m = re.search(r'__version__ = (\S+)\n', init)
if m is None or len(m.groups()) != 1: if m is None or len(m.groups()) != 1:
raise Exception("Cannot determine __version__ from init file: '%s'!" % initfile) raise Exception("Cannot determine __version__ from init file: "
+ "'%s'!" % initfile)
version = m.group(1).strip('\'\"') version = m.group(1).strip('\'\"')
return version return version
def gitCommit(name): def gitCommit(name):
"""Return the commit ID for the given name.""" """Return the commit ID for the given name."""
commit = check_output(['git', 'show', name], universal_newlines=True).split('\n')[0] commit = check_output(
['git', 'show', name],
universal_newlines=True).split('\n')[0]
assert commit[:7] == 'commit ' assert commit[:7] == 'commit '
return commit[7:] return commit[7:]
def getGitVersion(tagPrefix): def getGitVersion(tagPrefix):
"""Return a version string with information about this git checkout. """Return a version string with information about this git checkout.
If the checkout is an unmodified, tagged commit, then return the tag version. If the checkout is an unmodified, tagged commit, then return the tag
If this is not a tagged commit, return the output of ``git describe --tags``. version
If this is not a tagged commit, return the output of
``git describe --tags``
If this checkout has been modified, append "+" to the version. If this checkout has been modified, append "+" to the version.
""" """
path = os.getcwd() path = os.getcwd()
if not os.path.isdir(os.path.join(path, '.git')): if not os.path.isdir(os.path.join(path, '.git')):
return None return None
v = check_output(['git', 'describe', '--tags', '--dirty', '--match=%s*'%tagPrefix]).strip().decode('utf-8') v = check_output(['git',
'describe',
'--tags',
'--dirty',
'--match=%s*'%tagPrefix]).strip().decode('utf-8')
# chop off prefix # chop off prefix
assert v.startswith(tagPrefix) assert v.startswith(tagPrefix)
v = v[len(tagPrefix):] v = v[len(tagPrefix):]
# split up version parts # split up version parts
parts = v.split('-') parts = v.split('-')
# has working tree been modified? # has working tree been modified?
modified = False modified = False
if parts[-1] == 'dirty': if parts[-1] == 'dirty':
modified = True modified = True
parts = parts[:-1] parts = parts[:-1]
# have commits been added on top of last tagged version? # have commits been added on top of last tagged version?
# (git describe adds -NNN-gXXXXXXX if this is the case) # (git describe adds -NNN-gXXXXXXX if this is the case)
local = None local = None
if len(parts) > 2 and re.match(r'\d+', parts[-2]) and re.match(r'g[0-9a-f]{7}', parts[-1]): if (len(parts) > 2 and
re.match(r'\d+', parts[-2]) and
re.match(r'g[0-9a-f]{7}', parts[-1])):
local = parts[-1] local = parts[-1]
parts = parts[:-2] parts = parts[:-2]
gitVersion = '-'.join(parts) gitVersion = '-'.join(parts)
if local is not None: if local is not None:
gitVersion += '+' + local gitVersion += '+' + local
@ -389,7 +412,10 @@ def getGitVersion(tagPrefix):
return gitVersion return gitVersion
def getGitBranch(): def getGitBranch():
m = re.search(r'\* (.*)', check_output(['git', 'branch'], universal_newlines=True)) m = re.search(
r'\* (.*)',
check_output(['git', 'branch'],
universal_newlines=True))
if m is None: if m is None:
return '' return ''
else: else:
@ -397,32 +423,33 @@ def getGitBranch():
def getVersionStrings(pkg): def getVersionStrings(pkg):
""" """
Returns 4 version strings: Returns 4 version strings:
* the version string to use for this build, * the version string to use for this build,
* version string requested with --force-version (or None) * version string requested with --force-version (or None)
* version string that describes the current git checkout (or None). * version string that describes the current git checkout (or None).
* version string in the pkg/__init__.py, * version string in the pkg/__init__.py,
The first return value is (forceVersion or gitVersion or initVersion). The first return value is (forceVersion or gitVersion or initVersion).
""" """
## Determine current version string from __init__.py ## Determine current version string from __init__.py
initVersion = getInitVersion(pkgroot=pkg) initVersion = getInitVersion(pkgroot=pkg)
## If this is a git checkout, try to generate a more descriptive version string # If this is a git checkout
# try to generate a more descriptive version string
try: try:
gitVersion = getGitVersion(tagPrefix=pkg+'-') gitVersion = getGitVersion(tagPrefix=pkg+'-')
except: except:
gitVersion = None gitVersion = None
sys.stderr.write("This appears to be a git checkout, but an error occurred " sys.stderr.write("This appears to be a git checkout, but an error "
"while attempting to determine a version string for the " "occurred while attempting to determine a version "
"current commit.\n") "string for the current commit.\n")
sys.excepthook(*sys.exc_info()) sys.excepthook(*sys.exc_info())
# See whether a --force-version flag was given # See whether a --force-version flag was given
forcedVersion = None forcedVersion = None
for i,arg in enumerate(sys.argv): for i, arg in enumerate(sys.argv):
if arg.startswith('--force-version'): if arg.startswith('--force-version'):
if arg == '--force-version': if arg == '--force-version':
forcedVersion = sys.argv[i+1] forcedVersion = sys.argv[i+1]
@ -431,8 +458,8 @@ def getVersionStrings(pkg):
elif arg.startswith('--force-version='): elif arg.startswith('--force-version='):
forcedVersion = sys.argv[i].replace('--force-version=', '') forcedVersion = sys.argv[i].replace('--force-version=', '')
sys.argv.pop(i) sys.argv.pop(i)
## Finally decide on a version string to use: ## Finally decide on a version string to use:
if forcedVersion is not None: if forcedVersion is not None:
version = forcedVersion version = forcedVersion
@ -443,7 +470,8 @@ def getVersionStrings(pkg):
_, local = gitVersion.split('+') _, local = gitVersion.split('+')
if local != '': if local != '':
version = version + '+' + local version = version + '+' + local
sys.stderr.write("Detected git commit; will use version string: '%s'\n" % version) sys.stderr.write("Detected git commit; "
+ "will use version string: '%s'\n" % version)
return version, forcedVersion, gitVersion, initVersion return version, forcedVersion, gitVersion, initVersion
@ -457,29 +485,31 @@ class DebCommand(Command):
maintainer = "Luke Campagnola <luke.campagnola@gmail.com>" maintainer = "Luke Campagnola <luke.campagnola@gmail.com>"
debTemplate = "debian" debTemplate = "debian"
debDir = "deb_build" debDir = "deb_build"
user_options = [] user_options = []
def initialize_options(self): def initialize_options(self):
self.cwd = None self.cwd = None
def finalize_options(self): def finalize_options(self):
self.cwd = os.getcwd() self.cwd = os.getcwd()
def run(self): def run(self):
version = self.distribution.get_version() version = self.distribution.get_version()
pkgName = self.distribution.get_name() pkgName = self.distribution.get_name()
debName = "python-" + pkgName debName = "python-" + pkgName
debDir = self.debDir debDir = self.debDir
assert os.getcwd() == self.cwd, 'Must be in package root: %s' % self.cwd assert os.getcwd() == self.cwd, 'Must be in package root: '
+ '%s' % self.cwd
if os.path.isdir(debDir): if os.path.isdir(debDir):
raise Exception('DEB build dir already exists: "%s"' % debDir) raise Exception('DEB build dir already exists: "%s"' % debDir)
sdist = "dist/%s-%s.tar.gz" % (pkgName, version) sdist = "dist/%s-%s.tar.gz" % (pkgName, version)
if not os.path.isfile(sdist): if not os.path.isfile(sdist):
raise Exception("No source distribution; run `setup.py sdist` first.") raise Exception("No source distribution; "
+ "run `setup.py sdist` first.")
# copy sdist to build directory and extract # copy sdist to build directory and extract
os.mkdir(debDir) os.mkdir(debDir)
renamedSdist = '%s_%s.orig.tar.gz' % (debName, version) renamedSdist = '%s_%s.orig.tar.gz' % (debName, version)
@ -489,16 +519,20 @@ class DebCommand(Command):
if os.system("cd %s; tar -xzf %s" % (debDir, renamedSdist)) != 0: if os.system("cd %s; tar -xzf %s" % (debDir, renamedSdist)) != 0:
raise Exception("Error extracting source distribution.") raise Exception("Error extracting source distribution.")
buildDir = '%s/%s-%s' % (debDir, pkgName, version) buildDir = '%s/%s-%s' % (debDir, pkgName, version)
# copy debian control structure # copy debian control structure
print("copytree %s => %s" % (self.debTemplate, buildDir+'/debian')) print("copytree %s => %s" % (self.debTemplate, buildDir+'/debian'))
shutil.copytree(self.debTemplate, buildDir+'/debian') shutil.copytree(self.debTemplate, buildDir+'/debian')
# Write new changelog # Write new changelog
chlog = generateDebianChangelog(pkgName, 'CHANGELOG', version, self.maintainer) chlog = generateDebianChangelog(
pkgName,
'CHANGELOG',
version,
self.maintainer)
print("write changelog %s" % buildDir+'/debian/changelog') print("write changelog %s" % buildDir+'/debian/changelog')
open(buildDir+'/debian/changelog', 'w').write(chlog) open(buildDir+'/debian/changelog', 'w').write(chlog)
# build package # build package
print('cd %s; debuild -us -uc' % buildDir) print('cd %s; debuild -us -uc' % buildDir)
if os.system('cd %s; debuild -us -uc' % buildDir) != 0: if os.system('cd %s; debuild -us -uc' % buildDir) != 0:
@ -521,43 +555,45 @@ class DebugCommand(Command):
class TestCommand(Command): class TestCommand(Command):
description = "Run all package tests and exit immediately with informative return code." description = "Run all package tests and exit immediately with ", \
"informative return code."
user_options = [] user_options = []
def run(self): def run(self):
sys.exit(unitTests()) sys.exit(unitTests())
def initialize_options(self): def initialize_options(self):
pass pass
def finalize_options(self): def finalize_options(self):
pass pass
class StyleCommand(Command): class StyleCommand(Command):
description = "Check all code for style, exit immediately with informative return code." description = "Check all code for style, exit immediately with ", \
"informative return code."
user_options = [] user_options = []
def run(self): def run(self):
sys.exit(checkStyle()) sys.exit(checkStyle())
def initialize_options(self): def initialize_options(self):
pass pass
def finalize_options(self): def finalize_options(self):
pass pass
class MergeTestCommand(Command): class MergeTestCommand(Command):
description = "Run all tests needed to determine whether the current code is suitable for merge." description = "Run all tests needed to determine whether the current ",\
"code is suitable for merge."
user_options = [] user_options = []
def run(self): def run(self):
sys.exit(mergeTests()) sys.exit(mergeTests())
def initialize_options(self): def initialize_options(self):
pass pass
def finalize_options(self): def finalize_options(self):
pass pass