From 6b66edfd46ffd681e50eb493ee7533b486aa6c07 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 30 Mar 2014 02:51:32 -0400 Subject: [PATCH] Added Travis CI support Fixed bugs / style issues to please Lord Travis Squashed commit of the following: commit f25048a1e1e9d0be355f33fbaff6ef74845f4782 Author: Luke Campagnola Date: Sun Mar 30 02:40:47 2014 -0400 syntax commit cc8b69695a2698b75ff216dce27fabe46c79a325 Author: Luke Campagnola Date: Sun Mar 30 02:36:49 2014 -0400 add size check, diff style check commit 5d5ea065a4d4cc714bfaf7c7e7624295164cd86c Author: Luke Campagnola Date: Sun Mar 30 02:16:05 2014 -0400 travis fix commit b154c6d9971d35c2a46e575a0a884880ec14c8b1 Author: Luke Campagnola Date: Sun Mar 30 02:09:41 2014 -0400 travis, flake colored output commit 46921dcd878fdc08f05ef74d08be7953b1820a85 Author: Luke Campagnola Date: Wed Mar 26 12:37:54 2014 -0400 fix pyside+py3 bugs to satisfy CI commit 1d30f3c5c7763876ccfff54e460b83cb7422a5b4 Author: Luke Campagnola Date: Wed Mar 26 11:13:18 2014 -0400 fix py3 tests commit 426578fa4c5ec991d361c60005c32ca88c85e505 Author: Luke Campagnola Date: Wed Mar 26 07:39:19 2014 -0400 fix pytest install commit 88a13c1a7158904af7d25c5ba5bfc8e63e6cf49d Author: Luke Campagnola Date: Wed Mar 26 00:29:29 2014 -0400 qt5 updates commit 51995488ccc9f286aeced3bd49c3213819529e57 Author: Luke Campagnola Date: Wed Mar 26 00:16:04 2014 -0400 correct py.test command for py3 commit e2b02fbcbdbd95cbec5dd9307870f39337e6eb45 Author: Luke Campagnola Date: Tue Mar 25 23:50:38 2014 -0400 fix 2nd install test commit 4b3e3ee04adee3a8d1aabdfb18a198cbd0e83a06 Author: Luke Campagnola Date: Tue Mar 25 23:31:31 2014 -0400 syntax error commit 250eabdb34cac48d6f9622e6453235f0ec7f1151 Author: Luke Campagnola Date: Tue Mar 25 23:13:42 2014 -0400 look for py.test3 commit 9f9bca47c1a0a5c35be21835a674058242aa5f48 Author: Luke Campagnola Date: Tue Mar 25 22:54:19 2014 -0400 fix syntax commit 0a871c6f36ecddb7a1002b74d3b6d433e1648e8f Author: Luke Campagnola Date: Tue Mar 25 22:47:58 2014 -0400 output pip build log commit dbce58d8cd3f3a558202f8d7a149b5ec8fbfcf78 Author: Luke Campagnola Date: Tue Mar 25 22:38:55 2014 -0400 no comments allowed between shall lines commit b79c06121d8e40d8b2d2db0a4dd0a60fbf48edba Author: Luke Campagnola Date: Tue Mar 25 20:56:35 2014 -0400 another pip try commit 09f4f5d82ab41f66e5c746985f09dfcbe36f2089 Author: Luke Campagnola Date: Tue Mar 25 13:36:09 2014 -0400 pip correction commit 0eedb5c18e7242370f996c6b7481fbcdc8e6caf2 Author: Luke Campagnola Date: Tue Mar 25 13:29:00 2014 -0400 correct py version output commit d9fd039be22cb297f4de83f7ab543de25e2969dd Author: Luke Campagnola Date: Tue Mar 25 11:55:43 2014 -0400 apt checks commit cf95ccef86bd26964d73bdc649de8f23f64d2575 Author: Luke Campagnola Date: Tue Mar 25 10:23:10 2014 -0400 alternate pip install method commit bee0bcddfef44917a5ee425557ba6ff246edec87 Author: Luke Campagnola Date: Mon Mar 24 23:51:45 2014 -0400 correct deps install commit 963a4211fcaa5ebbfe0e13a5d879635bd77334ac Author: Luke Campagnola Date: Mon Mar 24 23:47:30 2014 -0400 fixes commit 0c86cd1dc28e286f999f01b37beb3c3252a8c864 Author: Luke Campagnola Date: Mon Mar 24 23:31:06 2014 -0400 permission fix commit 5d04ef53b80a83aa62452ff9a9f9152ca862f8d8 Author: Luke Campagnola Date: Mon Mar 24 23:30:19 2014 -0400 Fix py.test version selection commit b0e6c7cb94c7fa85653e492f8817e79b1ca30426 Author: Luke Campagnola Date: Mon Mar 24 23:25:34 2014 -0400 try another pyqt5 install method commit 422a7928665b5afb72861400672cc81b4bcd9779 Author: Luke Campagnola Date: Mon Mar 24 23:12:36 2014 -0400 syntax error commit 533133905a8e4f2ba26bc6e8f0e4fe631fbd119e Author: Luke Campagnola Date: Mon Mar 24 23:04:37 2014 -0400 fixes commit 8d65211ba4e08e4f4b13b68f9778c3aee4b43cdc Author: Luke Campagnola Date: Mon Mar 24 22:40:18 2014 -0400 Add Qt5 test minor fixes commit 4484efaefe0c99516940812d0472e82998e801f6 Author: Luke Campagnola Date: Mon Mar 24 22:31:56 2014 -0400 use correct py.test for python version commit 5d2441a29b98ed573e15580fc5efd533352ffb45 Author: Luke Campagnola Date: Mon Mar 24 22:24:27 2014 -0400 add setup tests commit 9291db64f25afeb46ee46d92b3bd13aabb325cfe Author: Luke Campagnola Date: Mon Mar 24 21:48:43 2014 -0400 fix py3-pyqt install commit a7aa675c5a5a5c4a2fff69feefc9298bcc626641 Author: Luke Campagnola Date: Mon Mar 24 21:31:33 2014 -0400 travis tests commit e71cd2b23ab09490c29c1c8ee18fc4db87ff0c01 Author: Luke Campagnola Date: Mon Mar 24 21:17:15 2014 -0400 more corrections commit 527df3bca897ba6a02cb3fe4a5a6db34042600b5 Author: Luke Campagnola Date: Mon Mar 24 20:56:01 2014 -0400 travis corrections commit 87d65cac4aa3bf815860030fac78e8e28069e29d Author: Luke Campagnola Date: Mon Mar 24 20:48:02 2014 -0400 Add flake tests Correct style in a few files to please Lord Travis commit 537028f88f17da59a6e8a09b9a3dee34282791cf Author: Luke Campagnola Date: Mon Mar 24 17:36:24 2014 -0400 minimize pyside package install correct line endings to satisfy Lord Travis commit 1e3cc95e37f03f70f50900dcb2e8a4dc4772208a Author: Luke Campagnola Date: Mon Mar 24 17:23:03 2014 -0400 enable pyside, line ending check fix test commit d7df4517f9004399703cb44237d27be313405e84 Author: Luke Campagnola Date: Mon Mar 24 17:12:06 2014 -0400 syntax fix commit 1ad77a21551c38f7ff77bd537eb6c2a9e13a26ae Author: Luke Campagnola Date: Mon Mar 24 17:00:30 2014 -0400 alt. pytest install commit 5edcc020729b1ecd452555852652720b4c3285f5 Author: Luke Campagnola Date: Mon Mar 24 16:52:33 2014 -0400 Added initial travis.yml --- .travis.yml | 230 +++++++++++++++++ pyqtgraph/GraphicsScene/mouseEvents.py | 21 +- pyqtgraph/debug.py | 15 +- pyqtgraph/exporters/PrintExporter.py | 2 +- pyqtgraph/exporters/SVGExporter.py | 7 +- pyqtgraph/flowchart/eq.py | 2 +- pyqtgraph/flowchart/library/Data.py | 2 +- pyqtgraph/frozenSupport.py | 102 ++++---- pyqtgraph/functions.py | 109 ++++---- pyqtgraph/graphicsItems/PlotCurveItem.py | 4 +- pyqtgraph/graphicsItems/PlotItem/PlotItem.py | 14 +- pyqtgraph/multiprocess/remoteproxy.py | 2 +- pyqtgraph/opengl/items/GLAxisItem.py | 2 +- pyqtgraph/opengl/items/GLGridItem.py | 2 +- pyqtgraph/opengl/items/GLLinePlotItem.py | 2 +- pyqtgraph/ordereddict.py | 254 +++++++++---------- pyqtgraph/pixmaps/__init__.py | 52 ++-- pyqtgraph/pixmaps/compile.py | 38 +-- pyqtgraph/tests/test_functions.py | 10 +- pyqtgraph/util/colorama/winterm.py | 2 +- setup.py | 21 +- tools/rebuildUi.py | 2 +- tools/setupHelpers.py | 239 ++++++++++++++++- 23 files changed, 819 insertions(+), 315 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..80cd5067 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,230 @@ +language: python + +# Credit: Original .travis.yml lifted from VisPy + +# Here we use anaconda for 2.6 and 3.3, since it provides the simplest +# interface for running different versions of Python. We could also use +# it for 2.7, but the Ubuntu system has installable 2.7 Qt4-GL, which +# allows for more complete testing. +notifications: + email: false + +virtualenv: + system_site_packages: true + + +env: + # Enable python 2 and python 3 builds + # Note that the 2.6 build doesn't get flake8, and runs old versions of + # Pyglet and GLFW to make sure we deal with those correctly + #- PYTHON=2.6 QT=pyqt TEST=standard + - PYTHON=2.7 QT=pyqt TEST=extra + - PYTHON=2.7 QT=pyside TEST=standard + - PYTHON=3.2 QT=pyqt TEST=standard + - PYTHON=3.2 QT=pyside TEST=standard + #- PYTHON=3.2 QT=pyqt5 TEST=standard + + +before_install: + - TRAVIS_DIR=`pwd` + - travis_retry sudo apt-get update; +# - if [ "${PYTHON}" != "2.7" ]; then +# wget http://repo.continuum.io/miniconda/Miniconda-2.2.2-Linux-x86_64.sh -O miniconda.sh && +# chmod +x miniconda.sh && +# ./miniconda.sh -b && +# export PATH=/home/$USER/anaconda/bin:$PATH && +# conda update --yes conda && +# travis_retry sudo apt-get -qq -y install libgl1-mesa-dri; +# fi; + - if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then + GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}"; + GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge"; + else + GIT_TARGET_EXTRA=""; + GIT_SOURCE_EXTRA=""; + fi; + + # to aid in debugging + - echo ${TRAVIS_BRANCH} + - echo ${TRAVIS_REPO_SLUG} + - echo ${GIT_TARGET_EXTRA} + - echo ${GIT_SOURCE_EXTRA} + +install: + # Dependencies + - if [ "${PYTHON}" == "2.7" ]; then + travis_retry sudo apt-get -qq -y install python-numpy && + export PIP=pip && + sudo ${PIP} install pytest && + sudo ${PIP} install flake8 && + export PYTEST=py.test; + else + travis_retry sudo apt-get -qq -y install python3-numpy && + curl http://python-distribute.org/distribute_setup.py | sudo python3 && + curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | sudo python3 && + export PIP=pip3.2 && + sudo ${PIP} install pytest && + sudo ${PIP} install flake8 && + export PYTEST=py.test-3.2; + fi; + + # Qt + - if [ "${PYTHON}" == "2.7" ]; then + if [ ${QT} == 'pyqt' ]; then + travis_retry sudo apt-get -qq -y install python-qt4 python-qt4-gl; + else + travis_retry sudo apt-get -qq -y install python-pyside.qtcore python-pyside.qtgui python-pyside.qtsvg python-pyside.qtopengl; + fi; + elif [ "${PYTHON}" == "3.2" ]; then + if [ ${QT} == 'pyqt' ]; then + travis_retry sudo apt-get -qq -y install python3-pyqt4; + elif [ ${QT} == 'pyside' ]; then + travis_retry sudo apt-get -qq -y install python3-pyside; + else + ${PIP} search PyQt5; + ${PIP} install PyQt5; + cat /home/travis/.pip/pip.log; + fi; + else + conda create -n testenv --yes --quiet pip python=$PYTHON && + source activate testenv && + if [ ${QT} == 'pyqt' ]; then + conda install --yes --quiet pyside; + else + conda install --yes --quiet pyside; + fi; + fi; + + # Install PyOpenGL + - if [ "${PYTHON}" == "2.7" ]; then + echo "Using OpenGL stable version (apt)"; + travis_retry sudo apt-get -qq -y install python-opengl; + else + echo "Using OpenGL stable version (pip)"; + ${PIP} install -q PyOpenGL; + cat /home/travis/.pip/pip.log; + fi; + + + # Debugging helpers + - uname -a + - cat /etc/issue + - if [ "${PYTHON}" == "2.7" ]; then + python --version; + else + python3 --version; + fi; + - apt-cache search python3-pyqt + - apt-cache search python3-pyside + - apt-cache search pytest + - apt-cache search python pip + - apt-cache search python qt5 + + +before_script: + # We need to create a (fake) display on Travis, let's use a funny resolution + - export DISPLAY=:99.0 + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render + + # Make sure everyone uses the correct python + - mkdir ~/bin && ln -s `which python${PYTHON}` ~/bin/python + - export PATH=/home/travis/bin:$PATH + - which python + - python --version + # Help color output from each test + - RESET='\033[0m'; + RED='\033[00;31m'; + GREEN='\033[00;32m'; + YELLOW='\033[00;33m'; + BLUE='\033[00;34m'; + PURPLE='\033[00;35m'; + CYAN='\033[00;36m'; + WHITE='\033[00;37m'; + start_test() { + echo -e "${BLUE}======== Starting $1 ========${RESET}"; + }; + check_output() { + ret=$?; + if [ $ret == 0 ]; then + echo -e "${GREEN}>>>>>> $1 passed <<<<<<${RESET}"; + else + echo -e "${RED}>>>>>> $1 FAILED <<<<<<${RESET}"; + fi; + return $ret; + }; + + - if [ "${TEST}" == "extra" ]; then + start_test "repo size check"; + mkdir ~/repo-clone && cd ~/repo-clone && + git init && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git && + git fetch origin ${GIT_TARGET_EXTRA} && + git checkout -qf FETCH_HEAD && + git tag travis-merge-target && + git gc --aggressive && + TARGET_SIZE=`du -s . | sed -e "s/\t.*//"` && + git pull origin ${GIT_SOURCE_EXTRA} && + git gc --aggressive && + MERGE_SIZE=`du -s . | sed -e "s/\t.*//"` && + if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then + SIZE_DIFF=`expr \( ${MERGE_SIZE} - ${TARGET_SIZE} \)`; + else + SIZE_DIFF=0; + fi; + fi; + + - cd $TRAVIS_DIR + + +script: + + # Run unit tests + - start_test "unit tests"; + PYTHONPATH=. ${PYTEST} pyqtgraph/; + check_output "unit tests"; + + + # check line endings + - if [ "${TEST}" == "extra" ]; then + start_test "line ending check"; + ! find ./ -name "*.py" | xargs file | grep CRLF && + ! find ./ -name "*.rst" | xargs file | grep CRLF; + check_output "line ending check"; + fi; + + # Check repo size does not expand too much + - if [ "${TEST}" == "extra" ]; then + start_test "repo size check"; + echo -e "Estimated content size difference = ${SIZE_DIFF} kB" && + test ${SIZE_DIFF} -lt 100; + check_output "repo size check"; + fi; + + # Check for style issues + - if [ "${TEST}" == "extra" ]; then + start_test "style check"; + cd ~/repo-clone && + git reset -q travis-merge-target && + python setup.py style && + check_output "style check"; + fi; + + - cd $TRAVIS_DIR + + # Check install works + - start_test "install test"; + sudo python${PYTHON} setup.py --quiet install; + check_output "install test"; + + # Check double-install fails + # Note the bash -c is because travis strips off the ! otherwise. + - start_test "double install test"; + bash -c "! sudo python${PYTHON} setup.py --quiet install"; + check_output "double install test"; + + # Check we can import pg + - start_test "import test"; + echo "import sys; print(sys.path)" | python && + cd /; echo "import pyqtgraph.examples" | python; + check_output "import test"; + + diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/pyqtgraph/GraphicsScene/mouseEvents.py index fa9bc36d..7809d464 100644 --- a/pyqtgraph/GraphicsScene/mouseEvents.py +++ b/pyqtgraph/GraphicsScene/mouseEvents.py @@ -131,8 +131,12 @@ class MouseDragEvent(object): return self.finish def __repr__(self): - lp = self.lastPos() - p = self.pos() + if self.currentItem is None: + lp = self._lastScenePos + p = self._scenePos + else: + lp = self.lastPos() + p = self.pos() return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) def modifiers(self): @@ -222,7 +226,10 @@ class MouseClickEvent(object): def __repr__(self): try: - p = self.pos() + if self.currentItem is None: + p = self._scenePos + else: + p = self.pos() return "" % (p.x(), p.y(), int(self.button())) except: return "" % (int(self.button())) @@ -348,8 +355,12 @@ class HoverEvent(object): return Point(self.currentItem.mapFromScene(self._lastScenePos)) def __repr__(self): - lp = self.lastPos() - p = self.pos() + if self.currentItem is None: + lp = self._lastScenePos + p = self._scenePos + else: + lp = self.lastPos() + p = self.pos() return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) def modifiers(self): diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py index 7836ba90..0deae0e0 100644 --- a/pyqtgraph/debug.py +++ b/pyqtgraph/debug.py @@ -240,7 +240,8 @@ def refPathString(chain): def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): """Guess how much memory an object is using""" - ignoreTypes = [types.MethodType, types.UnboundMethodType, types.BuiltinMethodType, types.FunctionType, types.BuiltinFunctionType] + ignoreTypes = ['MethodType', 'UnboundMethodType', 'BuiltinMethodType', 'FunctionType', 'BuiltinFunctionType'] + ignoreTypes = [getattr(types, key) for key in ignoreTypes if hasattr(types, key)] ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') @@ -624,12 +625,12 @@ class ObjTracker(object): ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) delRefs = {} - for i in self.startRefs.keys(): + for i in list(self.startRefs.keys()): if i not in refs: delRefs[i] = self.startRefs[i] del self.startRefs[i] self.forgetRef(delRefs[i]) - for i in self.newRefs.keys(): + for i in list(self.newRefs.keys()): if i not in refs: delRefs[i] = self.newRefs[i] del self.newRefs[i] @@ -667,7 +668,8 @@ class ObjTracker(object): for k in self.startCount: c1[k] = c1.get(k, 0) - self.startCount[k] typs = list(c1.keys()) - typs.sort(lambda a,b: cmp(c1[a], c1[b])) + #typs.sort(lambda a,b: cmp(c1[a], c1[b])) + typs.sort(key=lambda a: c1[a]) for t in typs: if c1[t] == 0: continue @@ -767,7 +769,8 @@ class ObjTracker(object): c = count.get(typ, [0,0]) count[typ] = [c[0]+1, c[1]+objectSize(obj)] typs = list(count.keys()) - typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) + #typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) + typs.sort(key=lambda a: count[a][1]) for t in typs: line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) @@ -827,7 +830,7 @@ def describeObj(obj, depth=4, path=None, ignore=None): def typeStr(obj): """Create a more useful type string by making types report their class.""" typ = type(obj) - if typ == types.InstanceType: + if typ == getattr(types, 'InstanceType', None): return "" % obj.__class__.__name__ else: return str(typ) diff --git a/pyqtgraph/exporters/PrintExporter.py b/pyqtgraph/exporters/PrintExporter.py index 3e2d45fa..530a1800 100644 --- a/pyqtgraph/exporters/PrintExporter.py +++ b/pyqtgraph/exporters/PrintExporter.py @@ -36,7 +36,7 @@ class PrintExporter(Exporter): dialog = QtGui.QPrintDialog(printer) dialog.setWindowTitle("Print Document") if dialog.exec_() != QtGui.QDialog.Accepted: - return; + return #dpi = QtGui.QDesktopWidget().physicalDpiX() diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py index 4a02965b..e46c9981 100644 --- a/pyqtgraph/exporters/SVGExporter.py +++ b/pyqtgraph/exporters/SVGExporter.py @@ -1,7 +1,7 @@ from .Exporter import Exporter from ..python2_3 import asUnicode from ..parametertree import Parameter -from ..Qt import QtGui, QtCore, QtSvg +from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE from .. import debug from .. import functions as fn import re @@ -219,7 +219,10 @@ def _generateItemSvg(item, nodes=None, root=None): #if hasattr(item, 'setExportMode'): #item.setExportMode(False) - xmlStr = bytes(arr).decode('utf-8') + if USE_PYSIDE: + xmlStr = str(arr) + else: + xmlStr = bytes(arr).decode('utf-8') doc = xml.parseString(xmlStr) try: diff --git a/pyqtgraph/flowchart/eq.py b/pyqtgraph/flowchart/eq.py index 89ebe09f..554989b2 100644 --- a/pyqtgraph/flowchart/eq.py +++ b/pyqtgraph/flowchart/eq.py @@ -29,7 +29,7 @@ def eq(a, b): except: return False if (hasattr(e, 'implements') and e.implements('MetaArray')): - return e.asarray().all() + return e.asarray().all() else: return e.all() else: diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py index 52458bd9..532f6c5b 100644 --- a/pyqtgraph/flowchart/library/Data.py +++ b/pyqtgraph/flowchart/library/Data.py @@ -328,7 +328,7 @@ class ColumnJoinNode(Node): ## Node.restoreState should have created all of the terminals we need ## However: to maintain support for some older flowchart files, we need - ## to manually add any terminals that were not taken care of. + ## to manually add any terminals that were not taken care of. for name in [n for n in state['order'] if n not in inputs]: Node.addInput(self, name, renamable=True, removable=True, multiable=True) inputs = self.inputs() diff --git a/pyqtgraph/frozenSupport.py b/pyqtgraph/frozenSupport.py index 385bb435..c42a12e1 100644 --- a/pyqtgraph/frozenSupport.py +++ b/pyqtgraph/frozenSupport.py @@ -1,52 +1,52 @@ -## Definitions helpful in frozen environments (eg py2exe) -import os, sys, zipfile - -def listdir(path): - """Replacement for os.listdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.listdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.listdir(path) - - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - results = set() - for name in contents: - # components in zip archive paths are always separated by forward slash - if name.startswith(archivePath) and len(name) > len(archivePath): - name = name[len(archivePath):].split('/')[0] - results.add(name) - return list(results) - -def isdir(path): - """Replacement for os.path.isdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.path.isdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.path.isdir(path) - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end - for c in contents: - if c.startswith(archivePath): - return True - return False - - -def splitZip(path): - """Splits a path containing a zip file into (zipfile, subpath). - If there is no zip file, returns (path, None)""" - components = os.path.normpath(path).split(os.sep) - for index, component in enumerate(components): - if component.endswith('.zip'): - zipPath = os.sep.join(components[0:index+1]) - archivePath = ''.join([x+'/' for x in components[index+1:]]) - return (zipPath, archivePath) - else: - return (path, None) - +## Definitions helpful in frozen environments (eg py2exe) +import os, sys, zipfile + +def listdir(path): + """Replacement for os.listdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.listdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.listdir(path) + + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + results = set() + for name in contents: + # components in zip archive paths are always separated by forward slash + if name.startswith(archivePath) and len(name) > len(archivePath): + name = name[len(archivePath):].split('/')[0] + results.add(name) + return list(results) + +def isdir(path): + """Replacement for os.path.isdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.path.isdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.path.isdir(path) + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end + for c in contents: + if c.startswith(archivePath): + return True + return False + + +def splitZip(path): + """Splits a path containing a zip file into (zipfile, subpath). + If there is no zip file, returns (path, None)""" + components = os.path.normpath(path).split(os.sep) + for index, component in enumerate(components): + if component.endswith('.zip'): + zipPath = os.sep.join(components[0:index+1]) + archivePath = ''.join([x+'/' for x in components[index+1:]]) + return (zipPath, archivePath) + else: + return (path, None) + \ No newline at end of file diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index f76a71c9..2325186c 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1424,30 +1424,30 @@ def isocurve(data, level, connected=False, extendToEdge=False, path=False): data = d2 sideTable = [ - [], - [0,1], - [1,2], - [0,2], - [0,3], - [1,3], - [0,1,2,3], - [2,3], - [2,3], - [0,1,2,3], - [1,3], - [0,3], - [0,2], - [1,2], - [0,1], - [] - ] + [], + [0,1], + [1,2], + [0,2], + [0,3], + [1,3], + [0,1,2,3], + [2,3], + [2,3], + [0,1,2,3], + [1,3], + [0,3], + [0,2], + [1,2], + [0,1], + [] + ] edgeKey=[ - [(0,1), (0,0)], - [(0,0), (1,0)], - [(1,0), (1,1)], - [(1,1), (0,1)] - ] + [(0,1), (0,0)], + [(0,0), (1,0)], + [(1,0), (1,1)], + [(1,1), (0,1)] + ] lines = [] @@ -1635,38 +1635,39 @@ def isosurface(data, level): ## edge index tells us which edges are cut by the isosurface. ## (Data stolen from Bourk; see above.) edgeTable = np.array([ - 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, - 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, - 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, - 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, - 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, - 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, - 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, - 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, - 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, - 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, - 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, - 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, - 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, - 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, - 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , - 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, - 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, - 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, - 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, - 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, - 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, - 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, - 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, - 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, - 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, - 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, - 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, - 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, - 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, - 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, - 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, - 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16) + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 + ], dtype=np.uint16) ## Table of triangles to use for filling each grid cell. ## Each set of three integers tells us which three edges to diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index ea337100..2197a6cd 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -486,7 +486,7 @@ class PlotCurveItem(GraphicsObject): gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) ## draw stencil pattern - gl.glStencilMask(0xFF); + gl.glStencilMask(0xFF) gl.glClear(gl.GL_STENCIL_BUFFER_BIT) gl.glBegin(gl.GL_TRIANGLES) gl.glVertex2f(rect.x(), rect.y()) @@ -520,7 +520,7 @@ class PlotCurveItem(GraphicsObject): gl.glEnable(gl.GL_LINE_SMOOTH) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST); + gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.size / pos.shape[-1]) finally: gl.glDisableClientState(gl.GL_VERTEX_ARRAY) diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py index 847ff3ac..5c102d95 100644 --- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py +++ b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py @@ -290,16 +290,17 @@ class PlotItem(GraphicsWidget): def getViewBox(self): """Return the :class:`ViewBox ` contained within.""" return self.vb + ## Wrap a few methods from viewBox. - #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive #because we had a reference to an instance method (creating wrapper methods at runtime instead). - for m in [ - 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', - 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setLimits', - 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY', - 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. + + for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE: + 'setAutoVisible', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please + 'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring + 'setAspectLocked', 'invertY', 'register', 'unregister']: # as well. + def _create_method(name): def method(self, *args, **kwargs): return getattr(self.vb, name)(*args, **kwargs) @@ -310,6 +311,7 @@ class PlotItem(GraphicsWidget): del _create_method + def setLogMode(self, x=None, y=None): """ Set log scaling for x and/or y axes. diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py index f2896c8b..4e7b7a1c 100644 --- a/pyqtgraph/multiprocess/remoteproxy.py +++ b/pyqtgraph/multiprocess/remoteproxy.py @@ -579,7 +579,7 @@ class Request(object): return self._result if timeout is None: - timeout = self.timeout + timeout = self.timeout if block: start = time.time() diff --git a/pyqtgraph/opengl/items/GLAxisItem.py b/pyqtgraph/opengl/items/GLAxisItem.py index c6c206e4..989a44ca 100644 --- a/pyqtgraph/opengl/items/GLAxisItem.py +++ b/pyqtgraph/opengl/items/GLAxisItem.py @@ -45,7 +45,7 @@ class GLAxisItem(GLGraphicsItem): if self.antialias: glEnable(GL_LINE_SMOOTH) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glBegin( GL_LINES ) diff --git a/pyqtgraph/opengl/items/GLGridItem.py b/pyqtgraph/opengl/items/GLGridItem.py index a8d1fb7a..4d6bc9d6 100644 --- a/pyqtgraph/opengl/items/GLGridItem.py +++ b/pyqtgraph/opengl/items/GLGridItem.py @@ -59,7 +59,7 @@ class GLGridItem(GLGraphicsItem): glEnable(GL_LINE_SMOOTH) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glBegin( GL_LINES ) diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py index 29c7ab5a..f5cb7545 100644 --- a/pyqtgraph/opengl/items/GLLinePlotItem.py +++ b/pyqtgraph/opengl/items/GLLinePlotItem.py @@ -96,7 +96,7 @@ class GLLinePlotItem(GLGraphicsItem): glEnable(GL_LINE_SMOOTH) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) if self.mode == 'line_strip': glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) diff --git a/pyqtgraph/ordereddict.py b/pyqtgraph/ordereddict.py index 5b0303f5..7242b506 100644 --- a/pyqtgraph/ordereddict.py +++ b/pyqtgraph/ordereddict.py @@ -1,127 +1,127 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -from UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/pyqtgraph/pixmaps/__init__.py b/pyqtgraph/pixmaps/__init__.py index 42bd3276..c26e4a6b 100644 --- a/pyqtgraph/pixmaps/__init__.py +++ b/pyqtgraph/pixmaps/__init__.py @@ -1,26 +1,26 @@ -""" -Allows easy loading of pixmaps used in UI elements. -Provides support for frozen environments as well. -""" - -import os, sys, pickle -from ..functions import makeQImage -from ..Qt import QtGui -if sys.version_info[0] == 2: - from . import pixmapData_2 as pixmapData -else: - from . import pixmapData_3 as pixmapData - - -def getPixmap(name): - """ - Return a QPixmap corresponding to the image file with the given name. - (eg. getPixmap('auto') loads pyqtgraph/pixmaps/auto.png) - """ - key = name+'.png' - data = pixmapData.pixmapData[key] - if isinstance(data, basestring) or isinstance(data, bytes): - pixmapData.pixmapData[key] = pickle.loads(data) - arr = pixmapData.pixmapData[key] - return QtGui.QPixmap(makeQImage(arr, alpha=True)) - +""" +Allows easy loading of pixmaps used in UI elements. +Provides support for frozen environments as well. +""" + +import os, sys, pickle +from ..functions import makeQImage +from ..Qt import QtGui +if sys.version_info[0] == 2: + from . import pixmapData_2 as pixmapData +else: + from . import pixmapData_3 as pixmapData + + +def getPixmap(name): + """ + Return a QPixmap corresponding to the image file with the given name. + (eg. getPixmap('auto') loads pyqtgraph/pixmaps/auto.png) + """ + key = name+'.png' + data = pixmapData.pixmapData[key] + if isinstance(data, basestring) or isinstance(data, bytes): + pixmapData.pixmapData[key] = pickle.loads(data) + arr = pixmapData.pixmapData[key] + return QtGui.QPixmap(makeQImage(arr, alpha=True)) + diff --git a/pyqtgraph/pixmaps/compile.py b/pyqtgraph/pixmaps/compile.py index ae07d487..fa0d2408 100644 --- a/pyqtgraph/pixmaps/compile.py +++ b/pyqtgraph/pixmaps/compile.py @@ -1,19 +1,19 @@ -import numpy as np -from PyQt4 import QtGui -import os, pickle, sys - -path = os.path.abspath(os.path.split(__file__)[0]) -pixmaps = {} -for f in os.listdir(path): - if not f.endswith('.png'): - continue - print(f) - img = QtGui.QImage(os.path.join(path, f)) - ptr = img.bits() - ptr.setsize(img.byteCount()) - arr = np.asarray(ptr).reshape(img.height(), img.width(), 4).transpose(1,0,2) - pixmaps[f] = pickle.dumps(arr) -ver = sys.version_info[0] -fh = open(os.path.join(path, 'pixmapData_%d.py' %ver), 'w') -fh.write("import numpy as np; pixmapData=%s" % repr(pixmaps)) - +import numpy as np +from PyQt4 import QtGui +import os, pickle, sys + +path = os.path.abspath(os.path.split(__file__)[0]) +pixmaps = {} +for f in os.listdir(path): + if not f.endswith('.png'): + continue + print(f) + img = QtGui.QImage(os.path.join(path, f)) + ptr = img.bits() + ptr.setsize(img.byteCount()) + arr = np.asarray(ptr).reshape(img.height(), img.width(), 4).transpose(1,0,2) + pixmaps[f] = pickle.dumps(arr) +ver = sys.version_info[0] +fh = open(os.path.join(path, 'pixmapData_%d.py' %ver), 'w') +fh.write("import numpy as np; pixmapData=%s" % repr(pixmaps)) + diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py index af0dde58..47fa266d 100644 --- a/pyqtgraph/tests/test_functions.py +++ b/pyqtgraph/tests/test_functions.py @@ -34,8 +34,9 @@ def test_interpolateArray(): result = pg.interpolateArray(data, x) - import scipy.ndimage - spresult = scipy.ndimage.map_coordinates(data, x.T, order=1) + #import scipy.ndimage + #spresult = scipy.ndimage.map_coordinates(data, x.T, order=1) + spresult = np.array([ 5.92, 20. , 11. , 0. , 0. ]) # generated with the above line assert_array_almost_equal(result, spresult) @@ -54,7 +55,10 @@ def test_interpolateArray(): [[1.5, 0.5], [1.5, 1.0], [1.5, 1.5]]]) r1 = pg.interpolateArray(data, x) - r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=1) + #r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=1) + r2 = np.array([[ 8.25, 11. , 16.5 ], # generated with the above line + [ 82.5 , 110. , 165. ]]) + assert_array_almost_equal(r1, r2) diff --git a/pyqtgraph/util/colorama/winterm.py b/pyqtgraph/util/colorama/winterm.py index 27088115..9c1c8185 100644 --- a/pyqtgraph/util/colorama/winterm.py +++ b/pyqtgraph/util/colorama/winterm.py @@ -115,6 +115,6 @@ class WinTerm(object): # fill the entire screen with blanks win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) # now set the buffer's attributes accordingly - win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ) # put the cursor at (0, 0) win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) diff --git a/setup.py b/setup.py index 7f2db6bf..b9143245 100644 --- a/setup.py +++ b/setup.py @@ -93,17 +93,34 @@ class Build(distutils.command.build.build): sys.excepthook(*sys.exc_info()) return ret +import distutils.command.install +class Install(distutils.command.install.install): + """ + * Check for previously-installed version before installing + """ + def run(self): + name = self.config_vars['dist_name'] + if name in os.listdir(self.install_libbase): + raise Exception("It appears another version of %s is already " + "installed at %s; remove this before installing." + % (name, self.install_libbase)) + print("Installing to %s" % self.install_libbase) + return distutils.command.install.install.run(self) setup( version=version, - cmdclass={'build': Build, 'deb': helpers.DebCommand, 'test': helpers.TestCommand}, + cmdclass={'build': Build, + 'install': Install, + 'deb': helpers.DebCommand, + 'test': helpers.TestCommand, + 'debug': helpers.DebugCommand, + 'style': helpers.StyleCommand}, packages=allPackages, package_dir={'pyqtgraph.examples': 'examples'}, ## install examples along with the rest of the source #package_data={'pyqtgraph': ['graphicsItems/PlotItem/*.png']}, install_requires = [ 'numpy', - 'scipy', ], **setupOpts ) diff --git a/tools/rebuildUi.py b/tools/rebuildUi.py index 1e4cbf9c..36f4d34c 100644 --- a/tools/rebuildUi.py +++ b/tools/rebuildUi.py @@ -15,7 +15,7 @@ for path, sd, files in os.walk('.'): py = os.path.join(path, base + '_pyqt.py') if not os.path.exists(py) or os.stat(ui).st_mtime > os.stat(py).st_mtime: os.system('%s %s > %s' % (pyqtuic, ui, py)) - print(py) + print(py) py = os.path.join(path, base + '_pyside.py') if not os.path.exists(py) or os.stat(ui).st_mtime > os.stat(py).st_mtime: diff --git a/tools/setupHelpers.py b/tools/setupHelpers.py index ea6aba3f..16defeaa 100644 --- a/tools/setupHelpers.py +++ b/tools/setupHelpers.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os, sys, re try: from subprocess import check_output @@ -9,9 +10,211 @@ except ImportError: output = proc.stdout.read() proc.wait() if proc.returncode != 0: - raise Exception("Process had nonzero return value", proc.returncode) + ex = Exception("Process had nonzero return value %d" % proc.returncode) + ex.returncode = proc.returncode + ex.output = output + raise ex return output +# Paths that are checked for style by flake and flake_diff +FLAKE_CHECK_PATHS = ['pyqtgraph', 'examples', 'tools'] + +# Flake style checks -- mandatory, recommended, optional +# See: http://pep8.readthedocs.org/en/1.4.6/intro.html +# and https://flake8.readthedocs.org/en/2.0/warnings.html +FLAKE_MANDATORY = set([ + 'E101', # indentation contains mixed spaces and tabs + 'E112', # expected an indented block + 'E122', # continuation line missing indentation or outdented + 'E125', # continuation line does not distinguish itself from next line + 'E133', # closing bracket is missing indentation + + 'E223', # tab before operator + 'E224', # tab after operator + 'E242', # tab after ‘,’ + 'E273', # tab after keyword + 'E274', # tab before keyword + + 'E901', # SyntaxError or IndentationError + 'E902', # IOError + + 'W191', # indentation contains tabs + + 'W601', # .has_key() is deprecated, use ‘in’ + 'W602', # deprecated form of raising exception + 'W603', # ‘<>’ is deprecated, use ‘!=’ + 'W604', # backticks are deprecated, use ‘repr()’ + ]) + +FLAKE_RECOMMENDED = set([ + 'E124', # closing bracket does not match visual indentation + 'E231', # missing whitespace after ‘,’ + + 'E211', # whitespace before ‘(‘ + 'E261', # at least two spaces before inline comment + 'E271', # multiple spaces after keyword + 'E272', # multiple spaces before keyword + 'E304', # blank lines found after function decorator + + 'F401', # module imported but unused + 'F402', # import module from line N shadowed by loop variable + 'F403', # ‘from module import *’ used; unable to detect undefined names + 'F404', # future import(s) name after other statements + + 'E501', # line too long (82 > 79 characters) + 'E502', # the backslash is redundant between brackets + + 'E702', # multiple statements on one line (semicolon) + 'E703', # statement ends with a semicolon + 'E711', # comparison to None should be ‘if cond is None:’ + 'E712', # comparison to True should be ‘if cond is True:’ or ‘if cond:’ + 'E721', # do not compare types, use ‘isinstance()’ + + 'F811', # redefinition of unused name from line N + 'F812', # list comprehension redefines name from line N + 'F821', # undefined name name + 'F822', # undefined name name in __all__ + 'F823', # local variable name ... referenced before assignment + 'F831', # duplicate argument name in function definition + 'F841', # local variable name is assigned to but never used + + 'W292', # no newline at end of file + + ]) + +FLAKE_OPTIONAL = set([ + 'E121', # continuation line indentation is not a multiple of four + 'E123', # closing bracket does not match indentation of opening bracket + 'E126', # continuation line over-indented for hanging indent + 'E127', # continuation line over-indented for visual indent + 'E128', # continuation line under-indented for visual indent + + 'E201', # whitespace after ‘(‘ + 'E202', # whitespace before ‘)’ + 'E203', # whitespace before ‘:’ + 'E221', # multiple spaces before operator + 'E222', # multiple spaces after operator + 'E225', # missing whitespace around operator + 'E227', # missing whitespace around bitwise or shift operator + 'E226', # missing whitespace around arithmetic operator + 'E228', # missing whitespace around modulo operator + 'E241', # multiple spaces after ‘,’ + 'E251', # unexpected spaces around keyword / parameter equals + 'E262', # inline comment should start with ‘# ‘ + + 'E301', # expected 1 blank line, found 0 + 'E302', # expected 2 blank lines, found 0 + 'E303', # too many blank lines (3) + + 'E401', # multiple imports on one line + + 'E701', # multiple statements on one line (colon) + + 'W291', # trailing whitespace + 'W293', # blank line contains whitespace + + 'W391', # blank line at end of file + ]) + +FLAKE_IGNORE = set([ + # 111 and 113 are ignored because they appear to be broken. + 'E111', # indentation is not a multiple of four + 'E113', # unexpected indentation + ]) + + +#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(): + """ Run flake8, checking only lines that are modified since the last + git commit. """ + test = [ 1,2,3 ] + + # First check _all_ code against mandatory error codes + print('flake8: check all code against mandatory error set...') + errors = ','.join(FLAKE_MANDATORY) + cmd = ['flake8', '--select=' + errors] + FLAKE_CHECK_PATHS + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + #ret = proc.wait() + output = proc.stdout.read().decode('utf-8') + ret = proc.wait() + printFlakeOutput(output) + + # Next check new code with optional error codes + print('flake8: check new code against recommended error set...') + diff = subprocess.check_output(['git', 'diff']) + proc = subprocess.Popen(['flake8', '--diff', #'--show-source', + '--ignore=' + errors], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + proc.stdin.write(diff) + proc.stdin.close() + output = proc.stdout.read().decode('utf-8') + ret |= printFlakeOutput(output) + + if ret == 0: + print('flake8 test passed.') + else: + print('flake8 test failed: %d' % ret) + sys.exit(ret) + + +def printFlakeOutput(text): + """ Print flake output, colored by error category. + Return 2 if there were any mandatory errors, + 1 if only recommended / optional errors, and + 0 if only optional errors. + """ + ret = 0 + gotError = False + for line in text.split('\n'): + m = re.match(r'[^\:]+\:\d+\:\d+\: (\w+) .*', line) + if m is None: + print(line) + else: + gotError = True + error = m.group(1) + if error in FLAKE_MANDATORY: + print("\033[0;31m" + line + "\033[0m") + ret |= 2 + elif error in FLAKE_RECOMMENDED: + print("\033[0;33m" + line + "\033[0m") + #ret |= 1 + elif error in FLAKE_OPTIONAL: + print("\033[0;32m" + line + "\033[0m") + elif error in FLAKE_IGNORE: + continue + else: + print("\033[0;36m" + line + "\033[0m") + if not gotError: + print(" [ no errors ]\n") + return ret + + + +def unitTests(): + try: + if sys.version[0] == '3': + out = check_output('PYTHONPATH=. py.test-3', shell=True) + else: + out = check_output('PYTHONPATH=. py.test', shell=True) + ret = 0 + except Exception as e: + out = e.output + ret = e.returncode + print(out.decode('utf-8')) + return ret + def listAllPackages(pkgroot): path = os.getcwd() n = len(path.split(os.path.sep)) @@ -190,8 +393,8 @@ class DebCommand(Command): raise Exception("Error during debuild.") -class TestCommand(Command): - """Just for learning about distutils; not for running package tests.""" +class DebugCommand(Command): + """Just for learning about distutils.""" description = "" user_options = [] def initialize_options(self): @@ -203,3 +406,33 @@ class TestCommand(Command): cmd = self print(self.distribution.name) print(self.distribution.version) + + +class TestCommand(Command): + description = "Run all package tests and exit immediately with informative return code." + user_options = [] + + def run(self): + sys.exit(unitTests()) + + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + +class StyleCommand(Command): + description = "Check all code for style, exit immediately with informative return code." + user_options = [] + + def run(self): + sys.exit(checkStyle()) + + def initialize_options(self): + pass + + def finalize_options(self): + pass +