From 62583359e7d6b8556b42987b1ecea0fa8edb6a91 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sat, 10 Sep 2016 00:04:55 -0700 Subject: [PATCH 01/28] Added tools for building and uploading release files --- tools/release/REAME.md | 34 ++++++++ tools/release/build-pg-release.py | 108 ++++++++++++++++++++++++ tools/release/common.py | 47 +++++++++++ tools/release/release_config.example.py | 18 ++++ tools/{ => release}/setVersion.py | 0 tools/release/upload-pg-release.py | 76 +++++++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 tools/release/REAME.md create mode 100755 tools/release/build-pg-release.py create mode 100644 tools/release/common.py create mode 100644 tools/release/release_config.example.py rename tools/{ => release}/setVersion.py (100%) create mode 100755 tools/release/upload-pg-release.py diff --git a/tools/release/REAME.md b/tools/release/REAME.md new file mode 100644 index 00000000..30157813 --- /dev/null +++ b/tools/release/REAME.md @@ -0,0 +1,34 @@ +PyQtGraph Release Procedure +--------------------------- + +0. Create your release_config.py based on release_config.example.py + +1. Create a release-x.x.x branch + +2. Run build-release script + - creates clone of master from github + - merges release branch into master + - updates version numbers in code + - creates pyqtgraph-x.x.x tag + - creates release commit + - builds source dist + - builds windows dists + - builds deb dist + +3. test build files + - test setup.py, pip on OSX + - test 32/64 exe on windows + - deb on linux (py2, py3) + - source install on linux (py2, py3) + +4. Run upload-release script + - pip upload + - github push + release + - website upload + +5. publish + - update website + - mailing list announcement + - new conda recipe (http://conda.pydata.org/docs/build.html) + - contact deb maintainer + - other package maintainers? diff --git a/tools/release/build-pg-release.py b/tools/release/build-pg-release.py new file mode 100755 index 00000000..b9ae5bdd --- /dev/null +++ b/tools/release/build-pg-release.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +from common import * + + +usage = """ +Usage: build_pg_release.py x.y.z + + * Will attempt to clone branch release-x.y.z from %s. + * Will attempt to contact windows host at %s (suggest running bitvise ssh server). +""" % (sourcedir, winhost) + + +if len(sys.argv) != 2: + print usage + sys.exit(-1) +version = sys.argv[1] +if re.match(r'\d+\.\d+.*', version) is None: + print 'Invalid version number "%s".' % version + sys.exit(-1) + + +# Clone source repository and tag the release branch +shell(''' + # Clone and merge release branch + cd {bld} + rm -rf pyqtgraph + git clone --depth 1 -b master http://github.com/pyqtgraph/pyqtgraph + cd {bld}/pyqtgraph + git checkout -b release-{ver} + git pull {src} release-{ver} + git checkout master + git merge --no-ff --no-commit release-{ver} + + # Write new version number into the source + sed -i "s/__version__ = .*/__version__ = '{ver}'/" pyqtgraph/__init__.py + #sed -i "s/ version=.*,/ version='{ver}',/" setup.py # now automated + sed -i "s/version = .*/version = '{ver}'/" doc/source/conf.py + sed -i "s/release = .*/release = '{ver}'/" doc/source/conf.py + + # make sure changelog mentions unreleased changes + grep "pyqtgraph-{ver}.*unreleased.*" CHANGELOG + sed -i "s/pyqtgraph-{ver}.*unreleased.*/pyqtgraph-{ver}/" CHANGELOG + + # Commit and tag new release + git commit -a -m "PyQtGraph release {ver}" + git tag pyqtgraph-{ver} + + # Build HTML documentation + cd doc + make clean + make html + cd .. + find ./ -name "*.pyc" -delete + + # package source distribution + python setup.py sdist + + # test pip install source distribution + rm -rf release-{ver}-virtenv + virtualenv --system-site-packages release-{ver}-virtenv + . release-{ver}-virtenv/bin/activate + echo "PATH: $PATH" + echo "ENV: $VIRTUAL_ENV" + pip install --no-index dist/pyqtgraph-{ver}.tar.gz + deactivate + + # build deb packages + #python setup.py --command-packages=stdeb.command bdist_deb + python setup.py --command-packages=stdeb.command sdist_dsc + cd deb_dist/pyqtgraph-{ver} + sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control + dpkg-buildpackage + cd ../../ + mv deb_dist dist/pyqtgraph-{ver}-deb +'''.format(**vars)) + + +# build windows installers +if winhost is not None: + shell("# Build windows executables") + ssh(winhost, ''' + rmdir /s/q pyqtgraph-build + git clone {self}:{bld}/pyqtgraph pyqtgraph-build + cd pyqtgraph-build + python setup.py build --plat-name=win32 bdist_wininst + python setup.py build --plat-name=win-amd64 bdist_wininst + exit + '''.format(**vars)) + + shell(''' + scp {win}:pyqtgraph-build/dist/*.exe {bld}/pyqtgraph/dist/ + '''.format(**vars)) + + +print """ + +======== Build complete. ========= + +* Dist files in {bld}/pyqtgraph/dist +""".format(**vars) + + +if winhost is not None: + print """ * Dist files on windows host at {win}:pyqtgraph-build/dist + """.format(**vars) + + + diff --git a/tools/release/common.py b/tools/release/common.py new file mode 100644 index 00000000..75b5d775 --- /dev/null +++ b/tools/release/common.py @@ -0,0 +1,47 @@ +import os, sys, getopt, re +import subprocess as sp + +try: + from release_config import * +except ImportError: + print "Error: could not import release_config! See the README..\n\n" + raise + + +vars = dict(ver=version, src=sourcedir, bld=builddir, win=winhost, self=selfhost) + + +def shell(cmd): + """Run each line of a shell script; raise an exception if any line returns + a nonzero value. + """ + pin, pout = os.pipe() + proc = sp.Popen('/bin/sh', stdin=sp.PIPE) + for line in cmd.split('\n'): + line = line.strip() + if line.startswith('#'): + print '\033[33m> ' + line + '\033[0m' + else: + print '\033[32m> ' + line + '\033[0m' + if line.startswith('cd '): + os.chdir(line[3:]) + proc.stdin.write(line + '\n') + proc.stdin.write('echo $? 1>&%d\n' % pout) + ret = "" + while not ret.endswith('\n'): + ret += os.read(pin, 1) + ret = int(ret.strip()) + if ret != 0: + print "Error, bailing out." + sys.exit(-1) + proc.stdin.close() + proc.wait() + + +def ssh(host, cmd): + """Run commands on a remote host by ssh. + """ + proc = sp.Popen(['ssh', host], stdin=sp.PIPE) + proc.stdin.write(cmd) + proc.wait() + diff --git a/tools/release/release_config.example.py b/tools/release/release_config.example.py new file mode 100644 index 00000000..35d569e4 --- /dev/null +++ b/tools/release/release_config.example.py @@ -0,0 +1,18 @@ +""" +Example configuration file required by build-pg-release and upload-pg-release. + +Copy this file to release_config.py and edit. +""" + +# Where to find the repository from which the release files will be built. +# This repository must have a branch called release-x.y.z +sourcedir = '/home/user/pyqtgraph' + +# Where to generate build files--source packages, deb packages, .exe installers, etc. +builddir = '/home/user/pyqtgraph-build' + +# Where to archive build files (optional) +archivedir = builddir + '/archive' + +# A windows machine (typically a VM) running an SSH server for automatically building .exe installers +winhost = 'luke@192.168.56.101' diff --git a/tools/setVersion.py b/tools/release/setVersion.py similarity index 100% rename from tools/setVersion.py rename to tools/release/setVersion.py diff --git a/tools/release/upload-pg-release.py b/tools/release/upload-pg-release.py new file mode 100755 index 00000000..7a538c87 --- /dev/null +++ b/tools/release/upload-pg-release.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +from release_config import * +import os + +usage = """ +upload-pg-release.py x.y.z + + * Uploads source dist to pypi + * Uploads packages & docs to website + * Pushes new master branch to github + +""" % (source, winhost) + + + +pypi_err = """ +Missing ~/.pypirc file. Should look like: +----------------------------------------- + +[distutils] +index-servers = + pypi + +[pypi] +username:your_username +password:your_password + +""" + +if not os.path.isfile(os.path.expanduser('~/.pypirc')): + print pypi_err + sys.exit(-1) + +### Upload everything to server +shell(""" + # Uploading documentation.. + cd pyqtgraph + rsync -rv doc/build/* slice:/www/code/pyqtgraph/pyqtgraph/documentation/build/ + + # Uploading source dist to website + rsync -v dist/pyqtgraph-{ver}.tar.gz slice:/www/code/pyqtgraph/downloads/ + cp dist/pyqtgraph-{ver}.tar.gz ../archive + + # Upload deb to website + rsync -v dist/pyqtgraph-{ver}-deb/python-pyqtgraph_{ver}-1_all.deb slice:/www/code/pyqtgraph/downloads/ + + # Update APT repository.. + ssh slice "cd /www/debian; ln -sf /www/code/pyqtgraph/downloads/*.deb dev/; dpkg-scanpackages dev /dev/null | gzip -9c > dev/Packages.gz" + cp -a dist/pyqtgraph-{ver}-deb ../archive/ + + # Uploading windows executables.. + rsync -v dist/*.exe slice:/www/code/pyqtgraph/downloads/ + cp dist/*.exe ../archive/ + + # Push to github + git push --tags https://github.com/pyqtgraph/pyqtgraph master:master + + # Upload to pypi.. + python setup.py sdist upload + + +""".format(**vars)) + +print """ + +======== Upload complete. ========= + +Next steps to publish: + - update website + - mailing list announcement + - new conda recipe (http://conda.pydata.org/docs/build.html) + - contact deb maintainer (gianfranco costamagna) + - other package maintainers? + +""".format(**vars) From 11384beda24fc4c008615cd8ab5fc7f6d1fccaef Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 14 Sep 2016 08:57:20 -0700 Subject: [PATCH 02/28] Cleared out changelog backlog --- CHANGELOG | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c5c562a4..761e3b17 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,29 +1,50 @@ -pyqtgraph-0.9.11 [unreleased] +pyqtgraph-0.10.0 [unreleased] + + New Features: + - PyQt5 support + - Options for interpreting image data as either row-major or col-major + - InfiniteLine and LinearRegionItem can have attached labels + - DockArea: + - Dock titles can be changed after creation + - Added Dock.sigClosed + - Added TextItem.setColor() + - FillBetweenItem supports finite-connected curves (those that exclude nan/inf) + + API / behavior changes: + - Improved ImageItem performance for some data types by scaling LUT instead of image + - Change the defaut color kwarg to None in TextItem.setText() to avoid changing + the color every time the text is changed. + - FFT plots skip first sample if x-axis uses log scaling + - Multiprocessing system adds bytes and unicode to the default list of no-proxy data types Bugfixes: + - Fix for numpy API change that caused casting errors for inplace operations - Fixed git version string generation on python3 - Fixed setting default values for out-of-bound points in pg.interpolateArray - Fixed plot downsampling bug on python 3 + - Fixed invalid slice in ImageItem.getHistogram - DockArea: - Fixed adding Docks to DockArea after all Docks have been removed - Fixed DockArea save/restoreState when area is empty - Properly remove select box when export dialog is closed using window decorations - - Remove all modifications to builtins + - Remove all modifications to python builtins + - Better Python 2.6 compatibility - Fix SpinBox decimals - - API / behavior changes: - - Change the defaut color kwarg to None in TextItem.setText() to avoid changing - the color everytime the text is changed. - - New Features: - - Preliminary PyQt5 support - - DockArea: - - Dock titles can be changed after creation - - Added Dock.sigClosed - - Added TextItem.setColor() + - Fixed numerous issues with ImageItem automatic downsampling + - Fixed PlotItem average curves using incorrect stepMode + - Fixed TableWidget eating key events + - Prevent redundant updating of flowchart nodes with multiple inputs + - Ignore wheel events in GraphicsView if mouse interaction is disabled + - Correctly pass calls to QWidget.close() up the inheritance chain + - ColorMap forces color inputs to be sorted + - Fixed memory mapping for RemoteGraphicsView in OSX + - Fixed QPropertyAnimation str/bytes handling Maintenance: + - Image comparison system for unit testing plus tests for several graphics items + - Travis CI and coveralls/codecov support - Add examples to unit tests + pyqtgraph-0.9.10 From 4ddf077a4bec55f7d1fae693cc9edd8168b5f529 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 14 Sep 2016 18:18:33 -0700 Subject: [PATCH 03/28] Fixed TextItem briefly drawing with incorrect transform. (note flickering in examples/text.py) --- examples/text.py | 1 - pyqtgraph/graphicsItems/TextItem.py | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/text.py b/examples/text.py index 43302e96..bf9bd6b9 100644 --- a/examples/text.py +++ b/examples/text.py @@ -46,7 +46,6 @@ def update(): global curvePoint, index index = (index + 1) % len(x) curvePoint.setPos(float(index)/(len(x)-1)) - #text2.viewRangeChanged() text2.setText('[%0.1f, %0.1f]' % (x[index], y[index])) timer = QtCore.QTimer() diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py index 9b880940..b2587ded 100644 --- a/pyqtgraph/graphicsItems/TextItem.py +++ b/pyqtgraph/graphicsItems/TextItem.py @@ -48,6 +48,7 @@ class TextItem(GraphicsObject): self.textItem = QtGui.QGraphicsTextItem() self.textItem.setParentItem(self) self._lastTransform = None + self._lastScene = None self._bounds = QtCore.QRectF() if html is None: self.setColor(color) @@ -149,9 +150,18 @@ class TextItem(GraphicsObject): self.updateTransform() def paint(self, p, *args): - # this is not ideal because it causes another update to be scheduled. + # this is not ideal because it requires the transform to be updated at every draw. # ideally, we would have a sceneTransformChanged event to react to.. - self.updateTransform() + s = self.scene() + ls = self._lastScene + if s is not ls: + if ls is not None: + ls.sigPrepareForPaint.disconnect(self.updateTransform) + self._lastScene = s + if s is not None: + s.sigPrepareForPaint.connect(self.updateTransform) + self.updateTransform() + p.setTransform(self.sceneTransform()) if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush: p.setPen(self.border) @@ -191,5 +201,3 @@ class TextItem(GraphicsObject): self._lastTransform = pt self.updateTextPos() - - From d100c1770c514e1d92e1c76528bf432671b53594 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 14 Sep 2016 21:58:49 -0700 Subject: [PATCH 04/28] Fixed flowchart gaussian filter not accepting MetaArray input (fixes examples/Flowchart.py) --- pyqtgraph/flowchart/library/Filters.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py index 876bf858..9392b037 100644 --- a/pyqtgraph/flowchart/library/Filters.py +++ b/pyqtgraph/flowchart/library/Filters.py @@ -164,8 +164,15 @@ class Gaussian(CtrlNode): import scipy.ndimage except ImportError: raise Exception("GaussianFilter node requires the package scipy.ndimage.") - return pgfn.gaussianFilter(data, self.ctrls['sigma'].value()) + if hasattr(data, 'implements') and data.implements('MetaArray'): + info = data.infoCopy() + filt = pgfn.gaussianFilter(data.asarray(), self.ctrls['sigma'].value()) + if 'values' in info[0]: + info[0]['values'] = info[0]['values'][:filt.shape[0]] + return metaarray.MetaArray(filt, info=info) + else: + return pgfn.gaussianFilter(data, self.ctrls['sigma'].value()) class Derivative(CtrlNode): """Returns the pointwise derivative of the input""" From 8bd940489b290c00c2f276cc0a4559f265370a21 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 14 Sep 2016 21:59:41 -0700 Subject: [PATCH 05/28] Update contributors list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 68ef9ced..30268796 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Contributors * Martin Fitzpatrick * Daniel Lidstrom * Eric Dill + * Vincent LeSaux Requirements ------------ From 1899fb04735071b507a01fd403a5133f4b0e8c5b Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 21 Sep 2016 17:40:40 -0700 Subject: [PATCH 06/28] Make version strings PEP440 compliant --- CHANGELOG | 2 ++ tools/setupHelpers.py | 37 ++++++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 761e3b17..be0064ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,8 @@ pyqtgraph-0.10.0 [unreleased] the color every time the text is changed. - FFT plots skip first sample if x-axis uses log scaling - Multiprocessing system adds bytes and unicode to the default list of no-proxy data types + - Version number scheme changed to be PEP440-compliant (only affects installations from non- + release git commits) Bugfixes: - Fix for numpy API change that caused casting errors for inplace operations diff --git a/tools/setupHelpers.py b/tools/setupHelpers.py index af478d97..939bca4e 100644 --- a/tools/setupHelpers.py +++ b/tools/setupHelpers.py @@ -358,18 +358,33 @@ def getGitVersion(tagPrefix): if not os.path.isdir(os.path.join(path, '.git')): return None - gitVersion = check_output(['git', 'describe', '--tags']).strip().decode('utf-8') + v = check_output(['git', 'describe', '--tags', '--dirty', '--match=%s*'%tagPrefix]).strip().decode('utf-8') - # any uncommitted modifications? + # chop off prefix + assert v.startswith(tagPrefix) + v = v[len(tagPrefix):] + + # split up version parts + parts = v.split('-') + + # has working tree been modified? modified = False - status = check_output(['git', 'status', '--porcelain'], universal_newlines=True).strip().split('\n') - for line in status: - if line != '' and line[:2] != '??': - modified = True - break - + if parts[-1] == 'dirty': + modified = True + parts = parts[:-1] + + # have commits been added on top of last tagged version? + # (git describe adds -NNN-gXXXXXXX if this is the case) + local = None + 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] + parts = parts[:-2] + + gitVersion = '-'.join(parts) + if local is not None: + gitVersion += '+' + local if modified: - gitVersion = gitVersion + '+' + gitVersion += 'm' return gitVersion @@ -393,11 +408,11 @@ def getVersionStrings(pkg): """ ## Determine current version string from __init__.py - initVersion = getInitVersion(pkgroot='pyqtgraph') + initVersion = getInitVersion(pkgroot=pkg) ## If this is a git checkout, try to generate a more descriptive version string try: - gitVersion = getGitVersion(tagPrefix='pyqtgraph-') + gitVersion = getGitVersion(tagPrefix=pkg+'-') except: gitVersion = None sys.stderr.write("This appears to be a git checkout, but an error occurred " From 10553b8150a67b4b59f3f496ad0c66ea9f57109e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 4 Oct 2016 08:56:39 -0700 Subject: [PATCH 07/28] add argument parsing to package build script --- tools/release/build-pg-release.py | 202 ++++++++++++++++++++++-------- tools/release/common.py | 11 +- 2 files changed, 150 insertions(+), 63 deletions(-) diff --git a/tools/release/build-pg-release.py b/tools/release/build-pg-release.py index b9ae5bdd..8c5d8161 100755 --- a/tools/release/build-pg-release.py +++ b/tools/release/build-pg-release.py @@ -1,31 +1,89 @@ #!/usr/bin/python -from common import * +import os, sys, argparse, random +from common import shell, ssh -usage = """ -Usage: build_pg_release.py x.y.z - * Will attempt to clone branch release-x.y.z from %s. - * Will attempt to contact windows host at %s (suggest running bitvise ssh server). -""" % (sourcedir, winhost) +description="Build release packages for pyqtgraph." +epilog = """ +Package build is done in several steps: -if len(sys.argv) != 2: - print usage - sys.exit(-1) -version = sys.argv[1] -if re.match(r'\d+\.\d+.*', version) is None: - print 'Invalid version number "%s".' % version + * Attempt to clone branch release-x.y.z from %s + * Merge release branch into master + * Write new version numbers into the source + * Roll over unreleased CHANGELOG entries + * Commit and tag new release + * Build HTML documentation + * Build source package + * Build deb packages (if running on Linux) + * Build exe packages (if running on Windows, or if a Windows + server is configured) + +Building source packages requires: + + * + * + * python-sphinx + +Building deb packages requires several dependencies: + + * build-essential + * python-all, python3-all + * python-stdeb, python3-stdeb + +""" + +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +build_dir = os.path.join(path, 'release-build') +pkg_dir = os.path.join(path, 'release-packages') + +ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) +ap.add_argument('version', help='The x.y.z version to generate release packages for. ' + 'There must be a corresponding pyqtgraph-x.y.z branch in the source repository.') +ap.add_argument('--source-repo', metavar='', help='Repository from which release and master branches will be cloned. Default is the repo containing this script.', default=path) +ap.add_argument('--build-dir', metavar='', help='Directory where packages will be staged and built. Default is source_root/release-build.', default=build_dir) +ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be stored. Default is source_root/release-packages.', default=pkg_dir) +ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', action='store_const', const=True, default=False) +ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False) +ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False) +ap.add_argument('--win-host', metavar='', help='user@hostname to build .exe installers via Windows SSH server.', default=None) +ap.add_argument('--self-host', metavar='', help='user@hostname for Windows server to access localhost (for git clone).', default=None) + +args = ap.parse_args() + +if os.path.exists(args.build_dir): + sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) sys.exit(-1) + + + +#if len(sys.argv) < 2: + #print usage + #sys.exit(-1) +version = args.version +#if re.match(r'\d+\.\d+.*', version) is None: + #print 'Invalid version number "%s".' % version + #sys.exit(-1) + +vars = { + 'ver': args.version, + 'bld': args.build_dir, + 'src': args.source_repo, + 'pkgdir': args.pkg_dir, + 'win': args.win_host, + 'self': args.self_host, +} # Clone source repository and tag the release branch shell(''' - # Clone and merge release branch + # Clone and merge release branch into previous master + mkdir -p {bld} cd {bld} rm -rf pyqtgraph - git clone --depth 1 -b master http://github.com/pyqtgraph/pyqtgraph - cd {bld}/pyqtgraph + git clone --depth 1 -b master {src} pyqtgraph + cd pyqtgraph git checkout -b release-{ver} git pull {src} release-{ver} git checkout master @@ -33,7 +91,6 @@ shell(''' # Write new version number into the source sed -i "s/__version__ = .*/__version__ = '{ver}'/" pyqtgraph/__init__.py - #sed -i "s/ version=.*,/ version='{ver}',/" setup.py # now automated sed -i "s/version = .*/version = '{ver}'/" doc/source/conf.py sed -i "s/release = .*/release = '{ver}'/" doc/source/conf.py @@ -54,55 +111,94 @@ shell(''' # package source distribution python setup.py sdist - - # test pip install source distribution - rm -rf release-{ver}-virtenv - virtualenv --system-site-packages release-{ver}-virtenv - . release-{ver}-virtenv/bin/activate - echo "PATH: $PATH" - echo "ENV: $VIRTUAL_ENV" - pip install --no-index dist/pyqtgraph-{ver}.tar.gz - deactivate - # build deb packages - #python setup.py --command-packages=stdeb.command bdist_deb - python setup.py --command-packages=stdeb.command sdist_dsc - cd deb_dist/pyqtgraph-{ver} - sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control - dpkg-buildpackage - cd ../../ - mv deb_dist dist/pyqtgraph-{ver}-deb + mkdir -p {pkgdir} + cp dist/*.tar.gz {pkgdir} + + # source package build complete. '''.format(**vars)) - -# build windows installers -if winhost is not None: - shell("# Build windows executables") - ssh(winhost, ''' - rmdir /s/q pyqtgraph-build - git clone {self}:{bld}/pyqtgraph pyqtgraph-build - cd pyqtgraph-build - python setup.py build --plat-name=win32 bdist_wininst - python setup.py build --plat-name=win-amd64 bdist_wininst - exit - '''.format(**vars)) - + +if args.skip_pip_test: + vars['pip_test'] = 'skipped' +else: shell(''' - scp {win}:pyqtgraph-build/dist/*.exe {bld}/pyqtgraph/dist/ + # test pip install source distribution + rm -rf release-{ver}-virtenv + virtualenv --system-site-packages release-{ver}-virtenv + . release-{ver}-virtenv/bin/activate + echo "PATH: $PATH" + echo "ENV: $VIRTUAL_ENV" + pip install --no-index --no-deps dist/pyqtgraph-{ver}.tar.gz + deactivate + + # pip install test passed '''.format(**vars)) + vars['pip_test'] = 'passed' -print """ +if 'linux' in sys.platform and not args.no_deb: + shell(''' + # build deb packages + cd {bld} + python setup.py --command-packages=stdeb.command sdist_dsc + cd deb_dist/pyqtgraph-{ver} + sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control + dpkg-buildpackage + cd ../../ + mv deb_dist {pkgdir}/pyqtgraph-{ver}-deb + + # deb package build complete. + '''.format(**vars)) + vars['deb_status'] = 'built' +else: + vars['deb_status'] = 'skipped' + + +# build windows installers locally if possible, otherwise try configured windows server +vars['winpath'] = None +if (sys.platform == 'win32' or winhost is not None) and not args.no_exe: + shell("# Build windows executables") + if sys.platform == 'win32': + shell(""" + cd {bld} + python setup.py build --plat-name=win32 bdist_wininst + python setup.py build --plat-name=win-amd64 bdist_wininst + cp dist/*.exe {pkgdir} + """.format(**vars)) + vars['exe_status'] = 'built' + else: + vars['winpath'] = 'pyqtgraph-build_%x' % random.randint(0, 1e12) + ssh(winhost, ''' + git clone {self}:{bld}/pyqtgraph {winpath} + cd {winpath} + python setup.py build --plat-name=win32 bdist_wininst + python setup.py build --plat-name=win-amd64 bdist_wininst + exit + '''.format(**vars)) + + shell(''' + scp {win}:{winpath}/dist/*.exe {pkgdir} + '''.format(**vars)) + vars['exe_status'] = 'built' +else: + vars['exe_status'] = 'skipped' + + +print(""" ======== Build complete. ========= -* Dist files in {bld}/pyqtgraph/dist -""".format(**vars) +* Source package: built +* Pip install test: {pip_test} +* Debian packages: {deb_status} +* Windows installers: {exe_status} +* Package files in {pkgdir} +""".format(**vars)) -if winhost is not None: - print """ * Dist files on windows host at {win}:pyqtgraph-build/dist - """.format(**vars) +if vars['winpath'] is not None: + print(""" * Dist files on windows host at {win}:{winpath}""".format(**vars)) diff --git a/tools/release/common.py b/tools/release/common.py index 75b5d775..9597cf50 100644 --- a/tools/release/common.py +++ b/tools/release/common.py @@ -1,15 +1,6 @@ -import os, sys, getopt, re +import os import subprocess as sp -try: - from release_config import * -except ImportError: - print "Error: could not import release_config! See the README..\n\n" - raise - - -vars = dict(ver=version, src=sourcedir, bld=builddir, win=winhost, self=selfhost) - def shell(cmd): """Run each line of a shell script; raise an exception if any line returns From ee46c150301ef6175b49e5aed1ce898b3c379d77 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 4 Oct 2016 17:51:08 -0700 Subject: [PATCH 08/28] cleanup, script seems to be working --- tools/release/REAME.md | 15 ++++++--------- tools/release/build-pg-release.py | 10 ++++++---- tools/release/release_config.example.py | 18 ------------------ tools/release/{common.py => shell.py} | 2 +- 4 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 tools/release/release_config.example.py rename tools/release/{common.py => shell.py} (98%) diff --git a/tools/release/REAME.md b/tools/release/REAME.md index 30157813..0a9d9d80 100644 --- a/tools/release/REAME.md +++ b/tools/release/REAME.md @@ -1,25 +1,23 @@ PyQtGraph Release Procedure --------------------------- -0. Create your release_config.py based on release_config.example.py - 1. Create a release-x.x.x branch -2. Run build-release script - - creates clone of master from github +2. Run build-pg-release script + - creates clone of master - merges release branch into master - updates version numbers in code - creates pyqtgraph-x.x.x tag - creates release commit - builds source dist + - test pip install - builds windows dists - builds deb dist 3. test build files - test setup.py, pip on OSX - - test 32/64 exe on windows - - deb on linux (py2, py3) - - source install on linux (py2, py3) + - test setup.py, pip, 32/64 exe on windows + - test setup.py, pip, deb on linux (py2, py3) 4. Run upload-release script - pip upload @@ -30,5 +28,4 @@ PyQtGraph Release Procedure - update website - mailing list announcement - new conda recipe (http://conda.pydata.org/docs/build.html) - - contact deb maintainer - - other package maintainers? + - contact various package maintainers diff --git a/tools/release/build-pg-release.py b/tools/release/build-pg-release.py index 8c5d8161..e5059e67 100755 --- a/tools/release/build-pg-release.py +++ b/tools/release/build-pg-release.py @@ -1,6 +1,6 @@ #!/usr/bin/python import os, sys, argparse, random -from common import shell, ssh +from shell import shell, ssh @@ -51,6 +51,8 @@ ap.add_argument('--win-host', metavar='', help='user@hostname to build .exe inst ap.add_argument('--self-host', metavar='', help='user@hostname for Windows server to access localhost (for git clone).', default=None) args = ap.parse_args() +args.build_dir = os.path.abspath(args.build_dir) +args.pkg_dir = os.path.abspath(args.pkg_dir) if os.path.exists(args.build_dir): sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) @@ -140,7 +142,7 @@ else: if 'linux' in sys.platform and not args.no_deb: shell(''' # build deb packages - cd {bld} + cd {bld}/pyqtgraph python setup.py --command-packages=stdeb.command sdist_dsc cd deb_dist/pyqtgraph-{ver} sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control @@ -157,7 +159,7 @@ else: # build windows installers locally if possible, otherwise try configured windows server vars['winpath'] = None -if (sys.platform == 'win32' or winhost is not None) and not args.no_exe: +if (sys.platform == 'win32' or args.win_host is not None) and not args.no_exe: shell("# Build windows executables") if sys.platform == 'win32': shell(""" @@ -169,7 +171,7 @@ if (sys.platform == 'win32' or winhost is not None) and not args.no_exe: vars['exe_status'] = 'built' else: vars['winpath'] = 'pyqtgraph-build_%x' % random.randint(0, 1e12) - ssh(winhost, ''' + ssh(args.win_host, ''' git clone {self}:{bld}/pyqtgraph {winpath} cd {winpath} python setup.py build --plat-name=win32 bdist_wininst diff --git a/tools/release/release_config.example.py b/tools/release/release_config.example.py deleted file mode 100644 index 35d569e4..00000000 --- a/tools/release/release_config.example.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Example configuration file required by build-pg-release and upload-pg-release. - -Copy this file to release_config.py and edit. -""" - -# Where to find the repository from which the release files will be built. -# This repository must have a branch called release-x.y.z -sourcedir = '/home/user/pyqtgraph' - -# Where to generate build files--source packages, deb packages, .exe installers, etc. -builddir = '/home/user/pyqtgraph-build' - -# Where to archive build files (optional) -archivedir = builddir + '/archive' - -# A windows machine (typically a VM) running an SSH server for automatically building .exe installers -winhost = 'luke@192.168.56.101' diff --git a/tools/release/common.py b/tools/release/shell.py similarity index 98% rename from tools/release/common.py rename to tools/release/shell.py index 9597cf50..238eb774 100644 --- a/tools/release/common.py +++ b/tools/release/shell.py @@ -1,4 +1,4 @@ -import os +import os, sys import subprocess as sp From f4e7713a2e93b968da928fcc89a0d5f47b58475e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 5 Oct 2016 09:24:24 -0700 Subject: [PATCH 09/28] Switch to building .exe installers locally--this is now possible for 32- and 64-bit on linux --- tools/release/build-pg-release.py | 62 ++++++++----------------------- tools/release/shell.py | 2 +- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/tools/release/build-pg-release.py b/tools/release/build-pg-release.py index e5059e67..c2daa092 100755 --- a/tools/release/build-pg-release.py +++ b/tools/release/build-pg-release.py @@ -17,8 +17,7 @@ Package build is done in several steps: * Build HTML documentation * Build source package * Build deb packages (if running on Linux) - * Build exe packages (if running on Windows, or if a Windows - server is configured) + * Build Windows exe installers Building source packages requires: @@ -47,34 +46,27 @@ ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', action='store_const', const=True, default=False) ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False) ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False) -ap.add_argument('--win-host', metavar='', help='user@hostname to build .exe installers via Windows SSH server.', default=None) -ap.add_argument('--self-host', metavar='', help='user@hostname for Windows server to access localhost (for git clone).', default=None) args = ap.parse_args() args.build_dir = os.path.abspath(args.build_dir) -args.pkg_dir = os.path.abspath(args.pkg_dir) +args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version) + if os.path.exists(args.build_dir): sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) sys.exit(-1) +if os.path.exists(args.pkg_dir): + sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir) + sys.exit(-1) - -#if len(sys.argv) < 2: - #print usage - #sys.exit(-1) version = args.version -#if re.match(r'\d+\.\d+.*', version) is None: - #print 'Invalid version number "%s".' % version - #sys.exit(-1) vars = { 'ver': args.version, 'bld': args.build_dir, 'src': args.source_repo, 'pkgdir': args.pkg_dir, - 'win': args.win_host, - 'self': args.self_host, } @@ -157,32 +149,15 @@ else: vars['deb_status'] = 'skipped' -# build windows installers locally if possible, otherwise try configured windows server -vars['winpath'] = None -if (sys.platform == 'win32' or args.win_host is not None) and not args.no_exe: - shell("# Build windows executables") - if sys.platform == 'win32': - shell(""" - cd {bld} - python setup.py build --plat-name=win32 bdist_wininst - python setup.py build --plat-name=win-amd64 bdist_wininst - cp dist/*.exe {pkgdir} - """.format(**vars)) - vars['exe_status'] = 'built' - else: - vars['winpath'] = 'pyqtgraph-build_%x' % random.randint(0, 1e12) - ssh(args.win_host, ''' - git clone {self}:{bld}/pyqtgraph {winpath} - cd {winpath} - python setup.py build --plat-name=win32 bdist_wininst - python setup.py build --plat-name=win-amd64 bdist_wininst - exit - '''.format(**vars)) - - shell(''' - scp {win}:{winpath}/dist/*.exe {pkgdir} - '''.format(**vars)) - vars['exe_status'] = 'built' +if not args.no_exe: + shell(""" + # Build windows executables + cd {bld}/pyqtgraph + python setup.py build bdist_wininst --plat-name=win32 + python setup.py build bdist_wininst + cp dist/*.exe {pkgdir} + """.format(**vars)) + vars['exe_status'] = 'built' else: vars['exe_status'] = 'skipped' @@ -197,10 +172,3 @@ print(""" * Windows installers: {exe_status} * Package files in {pkgdir} """.format(**vars)) - - -if vars['winpath'] is not None: - print(""" * Dist files on windows host at {win}:{winpath}""".format(**vars)) - - - diff --git a/tools/release/shell.py b/tools/release/shell.py index 238eb774..351f7a3d 100644 --- a/tools/release/shell.py +++ b/tools/release/shell.py @@ -7,7 +7,7 @@ def shell(cmd): a nonzero value. """ pin, pout = os.pipe() - proc = sp.Popen('/bin/sh', stdin=sp.PIPE) + proc = sp.Popen('/bin/bash', stdin=sp.PIPE) for line in cmd.split('\n'): line = line.strip() if line.startswith('#'): From 5d0b6aa016f152d2b4536b98153b83e2bb522c98 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 5 Oct 2016 09:33:28 -0700 Subject: [PATCH 10/28] move release tools --- tools/{release => }/build-pg-release.py | 0 tools/{release/REAME.md => release_instructions.md} | 0 tools/{release => }/shell.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tools/{release => }/build-pg-release.py (100%) rename tools/{release/REAME.md => release_instructions.md} (100%) rename tools/{release => }/shell.py (100%) diff --git a/tools/release/build-pg-release.py b/tools/build-pg-release.py similarity index 100% rename from tools/release/build-pg-release.py rename to tools/build-pg-release.py diff --git a/tools/release/REAME.md b/tools/release_instructions.md similarity index 100% rename from tools/release/REAME.md rename to tools/release_instructions.md diff --git a/tools/release/shell.py b/tools/shell.py similarity index 100% rename from tools/release/shell.py rename to tools/shell.py From 076ffee8cceb0aec608b3904569cfc58d5ab8f51 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 6 Oct 2016 09:07:22 -0700 Subject: [PATCH 11/28] Add publishing option to release script --- tools/build-pg-release.py | 305 ++++++++++++++++++++++-------------- tools/release/setVersion.py | 26 --- 2 files changed, 190 insertions(+), 141 deletions(-) mode change 100755 => 100644 tools/build-pg-release.py delete mode 100644 tools/release/setVersion.py diff --git a/tools/build-pg-release.py b/tools/build-pg-release.py old mode 100755 new mode 100644 index c2daa092..2aa0c114 --- a/tools/build-pg-release.py +++ b/tools/build-pg-release.py @@ -19,6 +19,12 @@ Package build is done in several steps: * Build deb packages (if running on Linux) * Build Windows exe installers +Release packages may be published by using the --publish flag: + + * Uploads release files to website + * Pushes tagged git commit to github + * Uploads source package to pypi + Building source packages requires: * @@ -31,15 +37,20 @@ Building deb packages requires several dependencies: * python-all, python3-all * python-stdeb, python3-stdeb +Building windows .exe files should be possible on any OS. Note, however, that +Debian/Ubuntu systems do not include the necessary wininst*.exe files; these +must be manually copied from the Python source. + """ -path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) build_dir = os.path.join(path, 'release-build') pkg_dir = os.path.join(path, 'release-packages') ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('version', help='The x.y.z version to generate release packages for. ' 'There must be a corresponding pyqtgraph-x.y.z branch in the source repository.') +ap.add_argument('--publish', metavar='', help='Publish previously built package files (must be stored in pkg-dir/version) and tagged release commit (from build-dir).', action='store_const', const=True, default=False) ap.add_argument('--source-repo', metavar='', help='Repository from which release and master branches will be cloned. Default is the repo containing this script.', default=path) ap.add_argument('--build-dir', metavar='', help='Directory where packages will be staged and built. Default is source_root/release-build.', default=build_dir) ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be stored. Default is source_root/release-packages.', default=pkg_dir) @@ -47,128 +58,192 @@ ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False) ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False) -args = ap.parse_args() -args.build_dir = os.path.abspath(args.build_dir) -args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version) -if os.path.exists(args.build_dir): - sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) - sys.exit(-1) -if os.path.exists(args.pkg_dir): - sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir) - sys.exit(-1) - - -version = args.version - -vars = { - 'ver': args.version, - 'bld': args.build_dir, - 'src': args.source_repo, - 'pkgdir': args.pkg_dir, -} - - -# Clone source repository and tag the release branch -shell(''' - # Clone and merge release branch into previous master - mkdir -p {bld} - cd {bld} - rm -rf pyqtgraph - git clone --depth 1 -b master {src} pyqtgraph - cd pyqtgraph - git checkout -b release-{ver} - git pull {src} release-{ver} - git checkout master - git merge --no-ff --no-commit release-{ver} - - # Write new version number into the source - sed -i "s/__version__ = .*/__version__ = '{ver}'/" pyqtgraph/__init__.py - sed -i "s/version = .*/version = '{ver}'/" doc/source/conf.py - sed -i "s/release = .*/release = '{ver}'/" doc/source/conf.py - - # make sure changelog mentions unreleased changes - grep "pyqtgraph-{ver}.*unreleased.*" CHANGELOG - sed -i "s/pyqtgraph-{ver}.*unreleased.*/pyqtgraph-{ver}/" CHANGELOG - - # Commit and tag new release - git commit -a -m "PyQtGraph release {ver}" - git tag pyqtgraph-{ver} - - # Build HTML documentation - cd doc - make clean - make html - cd .. - find ./ -name "*.pyc" -delete - - # package source distribution - python setup.py sdist - - mkdir -p {pkgdir} - cp dist/*.tar.gz {pkgdir} - - # source package build complete. -'''.format(**vars)) - - -if args.skip_pip_test: - vars['pip_test'] = 'skipped' -else: - shell(''' - # test pip install source distribution - rm -rf release-{ver}-virtenv - virtualenv --system-site-packages release-{ver}-virtenv - . release-{ver}-virtenv/bin/activate - echo "PATH: $PATH" - echo "ENV: $VIRTUAL_ENV" - pip install --no-index --no-deps dist/pyqtgraph-{ver}.tar.gz - deactivate +def build(args): + if os.path.exists(args.build_dir): + sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) + sys.exit(-1) + if os.path.exists(args.pkg_dir): + sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir) + sys.exit(-1) - # pip install test passed - '''.format(**vars)) - vars['pip_test'] = 'passed' - - -if 'linux' in sys.platform and not args.no_deb: + # Clone source repository and tag the release branch shell(''' - # build deb packages - cd {bld}/pyqtgraph - python setup.py --command-packages=stdeb.command sdist_dsc - cd deb_dist/pyqtgraph-{ver} - sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control - dpkg-buildpackage - cd ../../ - mv deb_dist {pkgdir}/pyqtgraph-{ver}-deb + # Clone and merge release branch into previous master + mkdir -p {build_dir} + cd {build_dir} + rm -rf pyqtgraph + git clone --depth 1 -b master {source_repo} pyqtgraph + cd pyqtgraph + git checkout -b release-{version} + git pull {source_repo} release-{version} + git checkout master + git merge --no-ff --no-commit release-{version} - # deb package build complete. - '''.format(**vars)) - vars['deb_status'] = 'built' -else: - vars['deb_status'] = 'skipped' - + # Write new version number into the source + sed -i "s/__version__ = .*/__version__ = '{version}'/" pyqtgraph/__init__.py + sed -i "s/version = .*/version = '{version}'/" doc/source/conf.py + sed -i "s/release = .*/release = '{version}'/" doc/source/conf.py + + # make sure changelog mentions unreleased changes + grep "pyqtgraph-{version}.*unreleased.*" CHANGELOG + sed -i "s/pyqtgraph-{version}.*unreleased.*/pyqtgraph-{version}/" CHANGELOG -if not args.no_exe: + # Commit and tag new release + git commit -a -m "PyQtGraph release {version}" + git tag pyqtgraph-{version} + + # Build HTML documentation + cd doc + make clean + make html + cd .. + find ./ -name "*.pyc" -delete + + # package source distribution + python setup.py sdist + + mkdir -p {pkg_dir} + cp dist/*.tar.gz {pkg_dir} + + # source package build complete. + '''.format(**args.__dict__)) + + + if args.skip_pip_test: + args.pip_test = 'skipped' + else: + shell(''' + # test pip install source distribution + rm -rf release-{version}-virtenv + virtualenv --system-site-packages release-{version}-virtenv + . release-{version}-virtenv/bin/activate + echo "PATH: $PATH" + echo "ENV: $VIRTUAL_ENV" + pip install --no-index --no-deps dist/pyqtgraph-{version}.tar.gz + deactivate + + # pip install test passed + '''.format(**args.__dict__)) + args.pip_test = 'passed' + + + if 'linux' in sys.platform and not args.no_deb: + shell(''' + # build deb packages + cd {build_dir}/pyqtgraph + python setup.py --command-packages=stdeb.command sdist_dsc + cd deb_dist/pyqtgraph-{version} + sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control + dpkg-buildpackage + cd ../../ + mv deb_dist {pkg_dir}/pyqtgraph-{version}-deb + + # deb package build complete. + '''.format(**args.__dict__)) + args.deb_status = 'built' + else: + args.deb_status = 'skipped' + + + if not args.no_exe: + shell(""" + # Build windows executables + cd {build_dir}/pyqtgraph + python setup.py build bdist_wininst --plat-name=win32 + python setup.py build bdist_wininst + cp dist/*.exe {pkg_dir} + """.format(**args.__dict__)) + args.exe_status = 'built' + else: + args.exe_status = 'skipped' + + + print(unindent(""" + + ======== Build complete. ========= + + * Source package: built + * Pip install test: {pip_test} + * Debian packages: {deb_status} + * Windows installers: {exe_status} + * Package files in {pkg_dir} + + Next steps to publish: + + * Test all packages + * Run script again with --publish + + """).format(**args.__dict__)) + + +def publish(args): + + + if not os.path.isfile(os.path.expanduser('~/.pypirc')): + print(unindent(""" + Missing ~/.pypirc file. Should look like: + ----------------------------------------- + + [distutils] + index-servers = + pypi + + [pypi] + username:your_username + password:your_password + + """)) + sys.exit(-1) + + ### Upload everything to server shell(""" - # Build windows executables - cd {bld}/pyqtgraph - python setup.py build bdist_wininst --plat-name=win32 - python setup.py build bdist_wininst - cp dist/*.exe {pkgdir} - """.format(**vars)) - vars['exe_status'] = 'built' -else: - vars['exe_status'] = 'skipped' + # Uploading documentation.. + cd {build_dir}/pyqtgraph + rsync -rv doc/build/* pyqtgraph.org:/www/code/pyqtgraph/pyqtgraph/documentation/build/ + + # Uploading release packages to website + rsync -v {pkg_dir}/{version} pyqtgraph.org:/www/code/pyqtgraph/downloads/ + + # Push to github + git push --tags https://github.com/pyqtgraph/pyqtgraph master:master + + # Upload to pypi.. + python setup.py sdist upload + + """.format(**args.__dict__)) + + print(unindent(""" + + ======== Upload complete. ========= + + Next steps to publish: + - update website + - mailing list announcement + - new conda recipe (http://conda.pydata.org/docs/build.html) + - contact deb maintainer (gianfranco costamagna) + - other package maintainers? + + """).format(**args.__dict__)) -print(""" +def unindent(msg): + ind = 1e6 + lines = msg.split('\n') + for line in lines: + if len(line.strip()) == 0: + continue + ind = min(ind, len(line) - len(line.lstrip())) + return '\n'.join([line[ind:] for line in lines]) -======== Build complete. ========= -* Source package: built -* Pip install test: {pip_test} -* Debian packages: {deb_status} -* Windows installers: {exe_status} -* Package files in {pkgdir} -""".format(**vars)) +if __name__ == '__main__': + args = ap.parse_args() + args.build_dir = os.path.abspath(args.build_dir) + args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version) + + if args.publish: + publish(args) + else: + build(args) diff --git a/tools/release/setVersion.py b/tools/release/setVersion.py deleted file mode 100644 index b62aca01..00000000 --- a/tools/release/setVersion.py +++ /dev/null @@ -1,26 +0,0 @@ -import re, os, sys - -version = sys.argv[1] - -replace = [ - ("pyqtgraph/__init__.py", r"__version__ = .*", "__version__ = '%s'" % version), - #("setup.py", r" version=.*,", " version='%s'," % version), # setup.py automatically detects version - ("doc/source/conf.py", r"version = .*", "version = '%s'" % version), - ("doc/source/conf.py", r"release = .*", "release = '%s'" % version), - #("tools/debian/control", r"^Version: .*", "Version: %s" % version) - ] - -path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..') - -for filename, search, sub in replace: - filename = os.path.join(path, filename) - data = open(filename, 'r').read() - if re.search(search, data) is None: - print('Error: Search expression "%s" not found in file %s.' % (search, filename)) - os._exit(1) - open(filename, 'w').write(re.sub(search, sub, data)) - -print("Updated version strings to %s" % version) - - - From 81d0c64d80573d25d6010b2453d2ab35e33dddf3 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 6 Oct 2016 09:07:52 -0700 Subject: [PATCH 12/28] rename --- tools/{build-pg-release.py => pg-release.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{build-pg-release.py => pg-release.py} (100%) diff --git a/tools/build-pg-release.py b/tools/pg-release.py similarity index 100% rename from tools/build-pg-release.py rename to tools/pg-release.py From 8d8fabdf748c599e21da6f429b568504a3e014c2 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 6 Oct 2016 09:08:29 -0700 Subject: [PATCH 13/28] cleanup --- tools/release/upload-pg-release.py | 76 ------------------------------ 1 file changed, 76 deletions(-) delete mode 100755 tools/release/upload-pg-release.py diff --git a/tools/release/upload-pg-release.py b/tools/release/upload-pg-release.py deleted file mode 100755 index 7a538c87..00000000 --- a/tools/release/upload-pg-release.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/python - -from release_config import * -import os - -usage = """ -upload-pg-release.py x.y.z - - * Uploads source dist to pypi - * Uploads packages & docs to website - * Pushes new master branch to github - -""" % (source, winhost) - - - -pypi_err = """ -Missing ~/.pypirc file. Should look like: ------------------------------------------ - -[distutils] -index-servers = - pypi - -[pypi] -username:your_username -password:your_password - -""" - -if not os.path.isfile(os.path.expanduser('~/.pypirc')): - print pypi_err - sys.exit(-1) - -### Upload everything to server -shell(""" - # Uploading documentation.. - cd pyqtgraph - rsync -rv doc/build/* slice:/www/code/pyqtgraph/pyqtgraph/documentation/build/ - - # Uploading source dist to website - rsync -v dist/pyqtgraph-{ver}.tar.gz slice:/www/code/pyqtgraph/downloads/ - cp dist/pyqtgraph-{ver}.tar.gz ../archive - - # Upload deb to website - rsync -v dist/pyqtgraph-{ver}-deb/python-pyqtgraph_{ver}-1_all.deb slice:/www/code/pyqtgraph/downloads/ - - # Update APT repository.. - ssh slice "cd /www/debian; ln -sf /www/code/pyqtgraph/downloads/*.deb dev/; dpkg-scanpackages dev /dev/null | gzip -9c > dev/Packages.gz" - cp -a dist/pyqtgraph-{ver}-deb ../archive/ - - # Uploading windows executables.. - rsync -v dist/*.exe slice:/www/code/pyqtgraph/downloads/ - cp dist/*.exe ../archive/ - - # Push to github - git push --tags https://github.com/pyqtgraph/pyqtgraph master:master - - # Upload to pypi.. - python setup.py sdist upload - - -""".format(**vars)) - -print """ - -======== Upload complete. ========= - -Next steps to publish: - - update website - - mailing list announcement - - new conda recipe (http://conda.pydata.org/docs/build.html) - - contact deb maintainer (gianfranco costamagna) - - other package maintainers? - -""".format(**vars) From 8a64c04f7133c71da2dfcdb288a49ea5e7f3f953 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 14 Oct 2016 18:36:39 -0700 Subject: [PATCH 14/28] Fix version string updating and distutils 'mbcs' error --- CHANGELOG | 1 + setup.py | 63 ++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index be0064ad..df027011 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ pyqtgraph-0.10.0 [unreleased] - ColorMap forces color inputs to be sorted - Fixed memory mapping for RemoteGraphicsView in OSX - Fixed QPropertyAnimation str/bytes handling + - Fixed __version__ string update when using `setup.py install` with newer setuptools Maintenance: - Image comparison system for unit testing plus tests for several graphics items diff --git a/setup.py b/setup.py index 7ca1be26..a66b0ade 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,17 @@ except ImportError: from distutils.command import install +# Work around mbcs bug in distutils. +# http://bugs.python.org/issue10945 +import codecs +try: + codecs.lookup('mbcs') +except LookupError: + ascii = codecs.lookup('ascii') + func = lambda name, enc=ascii: {True: enc}.get(name=='mbcs') + codecs.register(func) + + path = os.path.split(__file__)[0] sys.path.insert(0, os.path.join(path, 'tools')) import setupHelpers as helpers @@ -62,11 +73,9 @@ version, forcedVersion, gitVersion, initVersion = helpers.getVersionStrings(pkg= class Build(build.build): """ * Clear build path before building - * Set version string in __init__ after building """ def run(self): - global path, version, initVersion, forcedVersion - global buildVersion + global path ## Make sure build directory is clean buildPath = os.path.join(path, self.build_lib) @@ -75,43 +84,49 @@ class Build(build.build): ret = build.build.run(self) + +class Install(install.install): + """ + * Check for previously-installed version before installing + * Set version string in __init__ after building. This helps to ensure that we + know when an installation came from a non-release code base. + """ + def run(self): + global path, version, initVersion, forcedVersion, installVersion + + name = self.config_vars['dist_name'] + path = os.path.join(self.install_libbase, 'pyqtgraph') + if os.path.exists(path): + raise Exception("It appears another version of %s is already " + "installed at %s; remove this before installing." + % (name, path)) + print("Installing to %s" % path) + rval = install.install.run(self) + + # If the version in __init__ is different from the automatically-generated # version string, then we will update __init__ in the build directory if initVersion == version: return ret try: - initfile = os.path.join(buildPath, 'pyqtgraph', '__init__.py') + initfile = os.path.join(path, '__init__.py') data = open(initfile, 'r').read() open(initfile, 'w').write(re.sub(r"__version__ = .*", "__version__ = '%s'" % version, data)) - buildVersion = version + installVersion = version except: - if forcedVersion: - raise - buildVersion = initVersion sys.stderr.write("Warning: Error occurred while setting version string in build path. " "Installation will use the original version string " "%s instead.\n" % (initVersion) ) + if forcedVersion: + raise + installVersion = initVersion sys.excepthook(*sys.exc_info()) - return ret - + + return rval -class Install(install.install): - """ - * Check for previously-installed version before installing - """ - def run(self): - name = self.config_vars['dist_name'] - path = self.install_libbase - if os.path.exists(path) and name in os.listdir(path): - raise Exception("It appears another version of %s is already " - "installed at %s; remove this before installing." - % (name, path)) - print("Installing to %s" % path) - return install.install.run(self) - setup( version=version, cmdclass={'build': Build, From c7923d3f95e9f6be2ecda896f527450aad77f858 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 14 Oct 2016 18:38:35 -0700 Subject: [PATCH 15/28] Fix parameters sending 'children' key to setOpts on restoreState (fixes error seen in relativity demo) Add debug replacement for sys.excepthook that prints full stack trace --- pyqtgraph/debug.py | 33 +++++++++++++++++++---- pyqtgraph/parametertree/Parameter.py | 3 ++- pyqtgraph/parametertree/parameterTypes.py | 4 --- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py index 43058619..0da24d7c 100644 --- a/pyqtgraph/debug.py +++ b/pyqtgraph/debug.py @@ -83,8 +83,9 @@ class Tracer(object): funcname = cls.__name__ + "." + funcname return "%s: %s %s: %s" % (callline, filename, lineno, funcname) + def warnOnException(func): - """Decorator which catches/ignores exceptions and prints a stack trace.""" + """Decorator that catches/ignores exceptions and prints a stack trace.""" def w(*args, **kwds): try: func(*args, **kwds) @@ -92,11 +93,9 @@ def warnOnException(func): printExc('Ignored exception:') return w + def getExc(indent=4, prefix='| ', skip=1): - lines = (traceback.format_stack()[:-skip] - + [" ---- exception caught ---->\n"] - + traceback.format_tb(sys.exc_info()[2]) - + traceback.format_exception_only(*sys.exc_info()[:2])) + lines = formatException(*sys.exc_info(), skip=skip) lines2 = [] for l in lines: lines2.extend(l.strip('\n').split('\n')) @@ -112,6 +111,7 @@ def printExc(msg='', indent=4, prefix='|'): print(" "*indent + prefix + '='*30 + '>>') print(exc) print(" "*indent + prefix + '='*30 + '<<') + def printTrace(msg='', indent=4, prefix='|'): """Print an error message followed by an indented stack trace""" @@ -126,7 +126,30 @@ def printTrace(msg='', indent=4, prefix='|'): def backtrace(skip=0): return ''.join(traceback.format_stack()[:-(skip+1)]) + + +def formatException(exctype, value, tb, skip=0): + """Return a list of formatted exception strings. + Similar to traceback.format_exception, but displays the entire stack trace + rather than just the portion downstream of the point where the exception is + caught. In particular, unhandled exceptions that occur during Qt signal + handling do not usually show the portion of the stack that emitted the + signal. + """ + lines = traceback.format_exception(exctype, value, tb) + lines = [lines[0]] + traceback.format_stack()[:-(skip+1)] + [' --- exception caught here ---\n'] + lines[1:] + return lines + + +def printException(exctype, value, traceback): + """Print an exception with its full traceback. + + Set `sys.excepthook = printException` to ensure that exceptions caught + inside Qt signal handlers are printed with their full stack trace. + """ + print(''.join(formatException(exctype, value, traceback, skip=1))) + def listObjs(regex='Q', typ=None): """List all objects managed by python gc with class name matching regex. diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py index 99e644b0..de9a1624 100644 --- a/pyqtgraph/parametertree/Parameter.py +++ b/pyqtgraph/parametertree/Parameter.py @@ -312,7 +312,8 @@ class Parameter(QtCore.QObject): If blockSignals is True, no signals will be emitted until the tree has been completely restored. This prevents signal handlers from responding to a partially-rebuilt network. """ - childState = state.get('children', []) + state = state.copy() + childState = state.pop('children', []) ## list of children may be stored either as list or dict. if isinstance(childState, dict): diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index d8a5f1a6..31717481 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -284,8 +284,6 @@ class WidgetParameterItem(ParameterItem): self.widget.setOpts(**opts) self.updateDisplayLabel() - - class EventProxy(QtCore.QObject): def __init__(self, qobj, callback): @@ -296,8 +294,6 @@ class EventProxy(QtCore.QObject): def eventFilter(self, obj, ev): return self.callback(obj, ev) - - class SimpleParameter(Parameter): itemClass = WidgetParameterItem From 1c219647cf2f0c2d22cc7c9e3f2f7e56be4ca87e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 17 Oct 2016 09:17:33 -0700 Subject: [PATCH 16/28] minor setup bugfix --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a66b0ade..13298cd3 100644 --- a/setup.py +++ b/setup.py @@ -105,9 +105,9 @@ class Install(install.install): # If the version in __init__ is different from the automatically-generated - # version string, then we will update __init__ in the build directory + # version string, then we will update __init__ in the install directory if initVersion == version: - return ret + return rval try: initfile = os.path.join(path, '__init__.py') From a3d62b6baebeb735c415c6c9c1cd9201391d38fa Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 17 Oct 2016 09:20:27 -0700 Subject: [PATCH 17/28] correct amd64 exe build release doc updates --- tools/pg-release.py | 11 +++++++---- tools/release_instructions.md | 19 +++++++++++-------- tools/shell.py | 10 +++++----- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/tools/pg-release.py b/tools/pg-release.py index 2aa0c114..ac32b199 100644 --- a/tools/pg-release.py +++ b/tools/pg-release.py @@ -9,7 +9,7 @@ description="Build release packages for pyqtgraph." epilog = """ Package build is done in several steps: - * Attempt to clone branch release-x.y.z from %s + * Attempt to clone branch release-x.y.z from source-repo * Merge release branch into master * Write new version numbers into the source * Roll over unreleased CHANGELOG entries @@ -37,9 +37,12 @@ Building deb packages requires several dependencies: * python-all, python3-all * python-stdeb, python3-stdeb -Building windows .exe files should be possible on any OS. Note, however, that +Note: building windows .exe files should be possible on any OS. However, Debian/Ubuntu systems do not include the necessary wininst*.exe files; these -must be manually copied from the Python source. +must be manually copied from the Python source to the distutils/command +submodule path (/usr/lib/pythonX.X/distutils/command). Additionally, it may be +necessary to rename (or copy / link) wininst-9.0-amd64.exe to +wininst-6.0-amd64.exe. """ @@ -152,7 +155,7 @@ def build(args): # Build windows executables cd {build_dir}/pyqtgraph python setup.py build bdist_wininst --plat-name=win32 - python setup.py build bdist_wininst + python setup.py build bdist_wininst --plat-name=win-amd64 cp dist/*.exe {pkg_dir} """.format(**args.__dict__)) args.exe_status = 'built' diff --git a/tools/release_instructions.md b/tools/release_instructions.md index 0a9d9d80..b3b53efa 100644 --- a/tools/release_instructions.md +++ b/tools/release_instructions.md @@ -3,26 +3,29 @@ PyQtGraph Release Procedure 1. Create a release-x.x.x branch -2. Run build-pg-release script +2. Run pyqtgraph/tools/pg-release.py script (this has only been tested on linux) - creates clone of master - merges release branch into master - updates version numbers in code - creates pyqtgraph-x.x.x tag - creates release commit - - builds source dist - - test pip install - - builds windows dists - - builds deb dist + - builds documentation + - builds source package + - tests pip install + - builds windows .exe installers (note: it may be necessary to manually + copy wininst*.exe files from the python source packages) + - builds deb package (note: official debian packages are built elsewhere; + these locally-built deb packages may be phased out) 3. test build files - test setup.py, pip on OSX - test setup.py, pip, 32/64 exe on windows - test setup.py, pip, deb on linux (py2, py3) -4. Run upload-release script - - pip upload - - github push + release +4. Run pg-release.py script again with --publish flag - website upload + - github push + release + - pip upload 5. publish - update website diff --git a/tools/shell.py b/tools/shell.py index 351f7a3d..76667980 100644 --- a/tools/shell.py +++ b/tools/shell.py @@ -11,19 +11,19 @@ def shell(cmd): for line in cmd.split('\n'): line = line.strip() if line.startswith('#'): - print '\033[33m> ' + line + '\033[0m' + print('\033[33m> ' + line + '\033[0m') else: - print '\033[32m> ' + line + '\033[0m' + print('\033[32m> ' + line + '\033[0m') if line.startswith('cd '): os.chdir(line[3:]) - proc.stdin.write(line + '\n') - proc.stdin.write('echo $? 1>&%d\n' % pout) + proc.stdin.write((line + '\n').encode('utf-8')) + proc.stdin.write(('echo $? 1>&%d\n' % pout).encode('utf-8')) ret = "" while not ret.endswith('\n'): ret += os.read(pin, 1) ret = int(ret.strip()) if ret != 0: - print "Error, bailing out." + print("\033[31mLast command returned %d; bailing out.\033[0m" % ret) sys.exit(-1) proc.stdin.close() proc.wait() From f6b00a135c2ec7b5ed8ac6fb37021396a057e338 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 18 Oct 2016 08:47:38 -0700 Subject: [PATCH 18/28] fix optics demo on python3 print warning when setuptools is not available --- examples/optics/pyoptic.py | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/optics/pyoptic.py b/examples/optics/pyoptic.py index 275877eb..0054b30f 100644 --- a/examples/optics/pyoptic.py +++ b/examples/optics/pyoptic.py @@ -89,7 +89,7 @@ def wlPen(wl): return pen -class ParamObj: +class ParamObj(object): # Just a helper for tracking parameters and responding to changes def __init__(self): self.__params = {} diff --git a/setup.py b/setup.py index 13298cd3..a59f7dd5 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ try: from setuptools import setup from setuptools.command import install except ImportError: + sys.stderr.write("Warning: could not import setuptools; falling back to distutils.\n") from distutils.core import setup from distutils.command import install From 39ebc6717d673c5138182c5c507646dd2b9b7cf3 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 25 Oct 2016 21:00:55 -0700 Subject: [PATCH 19/28] Remove rawimagewidget from doc files (this file has never been linked to the index anyway) --- doc/source/widgets/rawimagewidget.rst | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 doc/source/widgets/rawimagewidget.rst diff --git a/doc/source/widgets/rawimagewidget.rst b/doc/source/widgets/rawimagewidget.rst deleted file mode 100644 index 29fda791..00000000 --- a/doc/source/widgets/rawimagewidget.rst +++ /dev/null @@ -1,8 +0,0 @@ -RawImageWidget -============== - -.. autoclass:: pyqtgraph.RawImageWidget - :members: - - .. automethod:: pyqtgraph.RawImageWidget.__init__ - From 6ea2bce48498f8252c9aef7de4c095e6ade3d821 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 25 Oct 2016 21:01:46 -0700 Subject: [PATCH 20/28] fixup rebuildUi script to allow selection of specific ui files to rebuild --- tools/rebuildUi.py | 65 +++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/tools/rebuildUi.py b/tools/rebuildUi.py index 98751412..2887b944 100644 --- a/tools/rebuildUi.py +++ b/tools/rebuildUi.py @@ -1,30 +1,53 @@ -import os, sys -## Search the package tree for all .ui files, compile each to -## a .py for pyqt and pyside +""" +Script for compiling Qt Designer .ui files to .py + + + +""" +import os, sys, subprocess, tempfile pyqtuic = 'pyuic4' pysideuic = 'pyside-uic' pyqt5uic = 'pyuic5' -for path, sd, files in os.walk('.'): - for f in files: - base, ext = os.path.splitext(f) - if ext != '.ui': - continue - ui = os.path.join(path, f) +usage = """Compile .ui files to .py for all supported pyqt/pyside versions. - 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) + Usage: python rebuildUi.py [.ui files|search paths] - 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: - os.system('%s %s > %s' % (pysideuic, ui, py)) - print(py) + May specify a list of .ui files and/or directories to search recursively for .ui files. +""" - py = os.path.join(path, base + '_pyqt5.py') - if not os.path.exists(py) or os.stat(ui).st_mtime > os.stat(py).st_mtime: - os.system('%s %s > %s' % (pyqt5uic, ui, py)) - print(py) +args = sys.argv[1:] +if len(args) == 0: + print usage + sys.exit(-1) + +uifiles = [] +for arg in args: + if os.path.isfile(arg) and arg.endswith('.ui'): + uifiles.append(arg) + elif os.path.isdir(arg): + # recursively search for ui files in this directory + for path, sd, files in os.walk(arg): + for f in files: + if not f.endswith('.ui'): + continue + uifiles.append(os.path.join(path, f)) + else: + print('Argument "%s" is not a directory or .ui file.' % arg) + sys.exit(-1) +# rebuild all requested ui files +for ui in uifiles: + base, _ = os.path.splitext(ui) + for compiler, ext in [(pyqtuic, '_pyqt.py'), (pysideuic, '_pyside.py'), (pyqt5uic, '_pyqt5.py')]: + py = base + ext + if os.path.exists(py) and os.stat(ui).st_mtime <= os.stat(py).st_mtime: + print("Skipping %s; already compiled." % py) + else: + cmd = '%s %s > %s' % (compiler, ui, py) + print(cmd) + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError: + os.remove(py) From 56efcbe981b3f09dc563e2519315a7d7432bbce4 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 25 Oct 2016 21:03:09 -0700 Subject: [PATCH 21/28] Workaround for pyopengl import error --- pyqtgraph/widgets/RawImageWidget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py index 970b570b..35f12e8a 100644 --- a/pyqtgraph/widgets/RawImageWidget.py +++ b/pyqtgraph/widgets/RawImageWidget.py @@ -3,7 +3,9 @@ try: from ..Qt import QtOpenGL from OpenGL.GL import * HAVE_OPENGL = True -except ImportError: +except Exception: + # Would prefer `except ImportError` here, but some versions of pyopengl generate + # AttributeError upon import HAVE_OPENGL = False from .. import functions as fn From 15c58de5d6f28b7f9eb1365b71ced4a522249940 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 25 Oct 2016 23:04:26 -0700 Subject: [PATCH 22/28] py3 fix --- tools/rebuildUi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/rebuildUi.py b/tools/rebuildUi.py index 2887b944..2ce80d87 100644 --- a/tools/rebuildUi.py +++ b/tools/rebuildUi.py @@ -19,7 +19,7 @@ usage = """Compile .ui files to .py for all supported pyqt/pyside versions. args = sys.argv[1:] if len(args) == 0: - print usage + print(usage) sys.exit(-1) uifiles = [] From a0e4301b3895d55de9bbb8a2619bda1a53b097cd Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 26 Oct 2016 09:26:01 -0700 Subject: [PATCH 23/28] Fix VideoSpeedTest to disable RawImageGLWidget when openGL is unavailable --- examples/VideoSpeedTest.py | 12 ++++++++++-- examples/VideoTemplate.ui | 15 +-------------- examples/VideoTemplate_pyqt.py | 19 +++++-------------- examples/VideoTemplate_pyside.py | 20 ++++++-------------- pyqtgraph/widgets/RawImageWidget.py | 1 + 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py index 3516472f..e7189bf5 100644 --- a/examples/VideoSpeedTest.py +++ b/examples/VideoSpeedTest.py @@ -25,14 +25,22 @@ else: #QtGui.QApplication.setGraphicsSystem('raster') app = QtGui.QApplication([]) -#mw = QtGui.QMainWindow() -#mw.resize(800,800) win = QtGui.QMainWindow() win.setWindowTitle('pyqtgraph example: VideoSpeedTest') ui = VideoTemplate.Ui_MainWindow() ui.setupUi(win) win.show() + +try: + from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget +except ImportError: + ui.rawGLRadio.setEnabled(False) + ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)") +else: + ui.rawGLImg = RawImageGLWidget() + ui.stack.addWidget(ui.rawGLImg) + ui.maxSpin1.setOpts(value=255, step=1) ui.minSpin1.setOpts(value=0, step=1) diff --git a/examples/VideoTemplate.ui b/examples/VideoTemplate.ui index 6bde7fe2..7da18327 100644 --- a/examples/VideoTemplate.ui +++ b/examples/VideoTemplate.ui @@ -51,7 +51,7 @@ - 2 + 1 @@ -74,13 +74,6 @@ - - - - - - - @@ -340,12 +333,6 @@ QDoubleSpinBox
pyqtgraph
- - RawImageGLWidget - QWidget -
pyqtgraph.widgets.RawImageWidget
- 1 -
diff --git a/examples/VideoTemplate_pyqt.py b/examples/VideoTemplate_pyqt.py index e2481df7..b93bedeb 100644 --- a/examples/VideoTemplate_pyqt.py +++ b/examples/VideoTemplate_pyqt.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './examples/VideoTemplate.ui' +# Form implementation generated from reading ui file 'examples/VideoTemplate.ui' # -# Created: Mon Feb 17 20:39:30 2014 -# by: PyQt4 UI code generator 4.10.3 +# Created by: PyQt4 UI code generator 4.11.4 # # WARNING! All changes made in this file will be lost! @@ -69,14 +68,6 @@ class Ui_MainWindow(object): self.rawImg.setObjectName(_fromUtf8("rawImg")) self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) - self.page_3 = QtGui.QWidget() - self.page_3.setObjectName(_fromUtf8("page_3")) - self.gridLayout_5 = QtGui.QGridLayout(self.page_3) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.rawGLImg = RawImageGLWidget(self.page_3) - self.rawGLImg.setObjectName(_fromUtf8("rawGLImg")) - self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1) - self.stack.addWidget(self.page_3) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName(_fromUtf8("rawGLRadio")) @@ -193,7 +184,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) - self.stack.setCurrentIndex(2) + self.stack.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): @@ -217,5 +208,5 @@ class Ui_MainWindow(object): self.rgbCheck.setText(_translate("MainWindow", "RGB", None)) self.label_5.setText(_translate("MainWindow", "Image size", None)) -from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget -from pyqtgraph import GradientWidget, SpinBox, GraphicsView +from pyqtgraph import GradientWidget, GraphicsView, SpinBox +from pyqtgraph.widgets.RawImageWidget import RawImageWidget diff --git a/examples/VideoTemplate_pyside.py b/examples/VideoTemplate_pyside.py index faebd546..4af85249 100644 --- a/examples/VideoTemplate_pyside.py +++ b/examples/VideoTemplate_pyside.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './examples/VideoTemplate.ui' +# Form implementation generated from reading ui file 'examples/VideoTemplate.ui' # -# Created: Mon Feb 17 20:39:30 2014 -# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# Created: Wed Oct 26 09:21:01 2016 +# by: pyside-uic 0.2.15 running on PySide 1.2.2 # # WARNING! All changes made in this file will be lost! @@ -55,14 +55,6 @@ class Ui_MainWindow(object): self.rawImg.setObjectName("rawImg") self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) - self.page_3 = QtGui.QWidget() - self.page_3.setObjectName("page_3") - self.gridLayout_5 = QtGui.QGridLayout(self.page_3) - self.gridLayout_5.setObjectName("gridLayout_5") - self.rawGLImg = RawImageGLWidget(self.page_3) - self.rawGLImg.setObjectName("rawGLImg") - self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1) - self.stack.addWidget(self.page_3) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName("rawGLRadio") @@ -179,7 +171,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) - self.stack.setCurrentIndex(2) + self.stack.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): @@ -203,5 +195,5 @@ class Ui_MainWindow(object): self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8)) self.label_5.setText(QtGui.QApplication.translate("MainWindow", "Image size", None, QtGui.QApplication.UnicodeUTF8)) -from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget -from pyqtgraph import GradientWidget, SpinBox, GraphicsView +from pyqtgraph.widgets.RawImageWidget import RawImageWidget +from pyqtgraph import SpinBox, GradientWidget, GraphicsView diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py index 35f12e8a..657701f9 100644 --- a/pyqtgraph/widgets/RawImageWidget.py +++ b/pyqtgraph/widgets/RawImageWidget.py @@ -61,6 +61,7 @@ class RawImageWidget(QtGui.QWidget): #p.drawPixmap(self.rect(), self.pixmap) p.end() + if HAVE_OPENGL: class RawImageGLWidget(QtOpenGL.QGLWidget): """ From 453871564b234963481f3e9b690e92f95b55a707 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 1 Nov 2016 06:22:47 -0700 Subject: [PATCH 24/28] Add sysinfo print on travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index e90828f0..96d79e18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -134,6 +134,9 @@ before_script: script: - source activate test_env + + # Check system info + - python -c "import pyqtgraph as pg; pg.systemInfo()" # Run unit tests - start_test "unit tests"; From 04bbbc453a8119106d8ef7b087fffb9491ae8aba Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 1 Nov 2016 15:25:54 -0700 Subject: [PATCH 25/28] force pyqt4 install in travis (conda now defaults to pyqt5) --- .travis.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 96d79e18..2c7b7769 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,10 @@ 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.6 QT=pyqt4 TEST=standard + - PYTHON=2.7 QT=pyqt4 TEST=extra - PYTHON=2.7 QT=pyside TEST=standard - - PYTHON=3.4 QT=pyqt TEST=standard + - PYTHON=3.4 QT=pyqt5 TEST=standard # - PYTHON=3.4 QT=pyside TEST=standard # pyside isn't available for 3.4 with conda #- PYTHON=3.2 QT=pyqt5 TEST=standard @@ -56,9 +56,12 @@ install: - echo ${TEST} - echo ${PYTHON} - - if [ "${QT}" == "pyqt" ]; then + - if [ "${QT}" == "pyqt5" ]; then conda install pyqt --yes; fi; + - if [ "${QT}" == "pyqt4" ]; then + conda install pyqt=4 --yes; + fi; - if [ "${QT}" == "pyside" ]; then conda install pyside --yes; fi; From c58a177561829920fadfdcc1fbee2160f44126b3 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 1 Nov 2016 18:28:48 -0700 Subject: [PATCH 26/28] Relax image test requirements for Qt5 (there are some single-pixel shifts that we will nee new test images to cover) --- pyqtgraph/tests/image_testing.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/tests/image_testing.py b/pyqtgraph/tests/image_testing.py index f4404671..c8a41dec 100644 --- a/pyqtgraph/tests/image_testing.py +++ b/pyqtgraph/tests/image_testing.py @@ -59,7 +59,7 @@ if sys.version[0] >= '3': else: import httplib import urllib -from ..Qt import QtGui, QtCore, QtTest +from ..Qt import QtGui, QtCore, QtTest, QT_LIB from .. import functions as fn from .. import GraphicsLayoutWidget from .. import ImageItem, TextItem @@ -212,7 +212,7 @@ def assertImageApproved(image, standardFile, message=None, **kwargs): def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., - pxCount=0, maxPxDiff=None, avgPxDiff=None, + pxCount=-1, maxPxDiff=None, avgPxDiff=None, imgDiff=None): """Check that two images match. @@ -234,7 +234,8 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., pxThreshold : float Minimum value difference at which two pixels are considered different pxCount : int or None - Maximum number of pixels that may differ + Maximum number of pixels that may differ. Default is 0 for Qt4 and + 1% of image size for Qt5. maxPxDiff : float or None Maximum allowed difference between pixels avgPxDiff : float or None @@ -247,6 +248,14 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., assert im1.shape[2] == 4 assert im1.dtype == im2.dtype + if pxCount == -1: + if QT_LIB == 'PyQt5': + # Qt5 generates slightly different results; relax the tolerance + # until test images are updated. + pxCount = int(im1.shape[0] * im1.shape[1] * 0.01) + else: + pxCount = 0 + diff = im1.astype(float) - im2.astype(float) if imgDiff is not None: assert np.abs(diff).sum() <= imgDiff From fcdc2a74adb5c52edfc045d39c4753b2072bcbda Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 1 Nov 2016 20:42:11 -0700 Subject: [PATCH 27/28] Fix import error in MatplotlibWidget --- pyqtgraph/widgets/MatplotlibWidget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/widgets/MatplotlibWidget.py b/pyqtgraph/widgets/MatplotlibWidget.py index 3de063fc..30496839 100644 --- a/pyqtgraph/widgets/MatplotlibWidget.py +++ b/pyqtgraph/widgets/MatplotlibWidget.py @@ -6,7 +6,10 @@ if not USE_PYQT5: matplotlib.rcParams['backend.qt4']='PySide' from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas - from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar + try: + from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar + except ImportError: + from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar else: from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar From e21d06b4c46a0fdbc873c3a37b3cb86132c3e35b Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 4 Nov 2016 22:47:10 -0700 Subject: [PATCH 28/28] add missing example template file add note about pyside bug affecting optics example --- examples/VideoTemplate_pyqt5.py | 199 ++++++++++++++++++++++++++++++++ examples/optics/pyoptic.py | 3 +- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 examples/VideoTemplate_pyqt5.py diff --git a/examples/VideoTemplate_pyqt5.py b/examples/VideoTemplate_pyqt5.py new file mode 100644 index 00000000..63153fb5 --- /dev/null +++ b/examples/VideoTemplate_pyqt5.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'examples/VideoTemplate.ui' +# +# Created by: PyQt5 UI code generator 5.5.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(695, 798) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) + self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) + self.scaleCheck.setObjectName("scaleCheck") + self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) + self.rawRadio.setObjectName("rawRadio") + self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) + self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) + self.gfxRadio.setChecked(True) + self.gfxRadio.setObjectName("gfxRadio") + self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) + self.stack = QtWidgets.QStackedWidget(self.centralwidget) + self.stack.setObjectName("stack") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.gridLayout_3 = QtWidgets.QGridLayout(self.page) + self.gridLayout_3.setObjectName("gridLayout_3") + self.graphicsView = GraphicsView(self.page) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) + self.stack.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) + self.gridLayout_4.setObjectName("gridLayout_4") + self.rawImg = RawImageWidget(self.page_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) + self.rawImg.setSizePolicy(sizePolicy) + self.rawImg.setObjectName("rawImg") + self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_2) + self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) + self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) + self.rawGLRadio.setObjectName("rawGLRadio") + self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) + self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) + self.dtypeCombo.setObjectName("dtypeCombo") + self.dtypeCombo.addItem("") + self.dtypeCombo.addItem("") + self.dtypeCombo.addItem("") + self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) + self.label = QtWidgets.QLabel(self.centralwidget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) + self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) + self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") + self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.minSpin2 = SpinBox(self.centralwidget) + self.minSpin2.setEnabled(False) + self.minSpin2.setObjectName("minSpin2") + self.horizontalLayout_2.addWidget(self.minSpin2) + self.label_3 = QtWidgets.QLabel(self.centralwidget) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) + self.label_3.setObjectName("label_3") + self.horizontalLayout_2.addWidget(self.label_3) + self.maxSpin2 = SpinBox(self.centralwidget) + self.maxSpin2.setEnabled(False) + self.maxSpin2.setObjectName("maxSpin2") + self.horizontalLayout_2.addWidget(self.maxSpin2) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.minSpin1 = SpinBox(self.centralwidget) + self.minSpin1.setObjectName("minSpin1") + self.horizontalLayout.addWidget(self.minSpin1) + self.label_2 = QtWidgets.QLabel(self.centralwidget) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.maxSpin1 = SpinBox(self.centralwidget) + self.maxSpin1.setObjectName("maxSpin1") + self.horizontalLayout.addWidget(self.maxSpin1) + self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.minSpin3 = SpinBox(self.centralwidget) + self.minSpin3.setEnabled(False) + self.minSpin3.setObjectName("minSpin3") + self.horizontalLayout_3.addWidget(self.minSpin3) + self.label_4 = QtWidgets.QLabel(self.centralwidget) + self.label_4.setAlignment(QtCore.Qt.AlignCenter) + self.label_4.setObjectName("label_4") + self.horizontalLayout_3.addWidget(self.label_4) + self.maxSpin3 = SpinBox(self.centralwidget) + self.maxSpin3.setEnabled(False) + self.maxSpin3.setObjectName("maxSpin3") + self.horizontalLayout_3.addWidget(self.maxSpin3) + self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) + self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) + self.lutCheck.setObjectName("lutCheck") + self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) + self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) + self.alphaCheck.setObjectName("alphaCheck") + self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) + self.gradient = GradientWidget(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) + self.gradient.setSizePolicy(sizePolicy) + self.gradient.setObjectName("gradient") + self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) + self.fpsLabel = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(12) + self.fpsLabel.setFont(font) + self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fpsLabel.setObjectName("fpsLabel") + self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) + self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) + self.rgbCheck.setObjectName("rgbCheck") + self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.centralwidget) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) + self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) + self.framesSpin.setProperty("value", 10) + self.framesSpin.setObjectName("framesSpin") + self.horizontalLayout_4.addWidget(self.framesSpin) + self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) + self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus) + self.widthSpin.setMaximum(10000) + self.widthSpin.setProperty("value", 512) + self.widthSpin.setObjectName("widthSpin") + self.horizontalLayout_4.addWidget(self.widthSpin) + self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) + self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) + self.heightSpin.setMaximum(10000) + self.heightSpin.setProperty("value", 512) + self.heightSpin.setObjectName("heightSpin") + self.horizontalLayout_4.addWidget(self.heightSpin) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) + self.sizeLabel = QtWidgets.QLabel(self.centralwidget) + self.sizeLabel.setText("") + self.sizeLabel.setObjectName("sizeLabel") + self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + + self.retranslateUi(MainWindow) + self.stack.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) + self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) + self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) + self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) + self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) + self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) + self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) + self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) + self.label.setText(_translate("MainWindow", "Data type")) + self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) + self.label_3.setText(_translate("MainWindow", "<--->")) + self.label_2.setText(_translate("MainWindow", "<--->")) + self.label_4.setText(_translate("MainWindow", "<--->")) + self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) + self.alphaCheck.setText(_translate("MainWindow", "alpha")) + self.fpsLabel.setText(_translate("MainWindow", "FPS")) + self.rgbCheck.setText(_translate("MainWindow", "RGB")) + self.label_5.setText(_translate("MainWindow", "Image size")) + +from pyqtgraph import GradientWidget, GraphicsView, SpinBox +from pyqtgraph.widgets.RawImageWidget import RawImageWidget diff --git a/examples/optics/pyoptic.py b/examples/optics/pyoptic.py index 0054b30f..c2cb2ba2 100644 --- a/examples/optics/pyoptic.py +++ b/examples/optics/pyoptic.py @@ -109,7 +109,8 @@ class ParamObj(object): pass def __getitem__(self, item): - return self.getParam(item) + # bug in pyside 1.2.2 causes getitem to be called inside QGraphicsObject.parentItem: + return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-441 def getParam(self, param): return self.__params[param]