Porting from PyQt to PySide

I recently put together a small project in PyQt, but the license is something that makes me a bit unsettled. I wanted to release the sources under the MIT or BSD licenses, but the requirements of the GPL prohibit carrying out the spirit of either of these, at least in terms of practicality (no downstream commercial use). Thus, I’ve been somewhat torn: I appreciate the GPL, but I more deeply appreciate the MIT/BSD license family for the greater extent of freedom they afford. I disagree that allowing commercial use somehow precludes “freedom” because developers have to eat–and for that matter, I’ll probably purchase a PyQt license at some point in the future, if needed, because it is actively maintained by a small business.

Since starting the project, I rediscovered PySide, an LGPL-licensed alternative to PyQt that acts mostly as a drop-in replacement for the latter. There are some differences that require minor changes to your sources before it can act as a complete replacement, but they’re so minimal as to be of no consequence. Of the ones that I’ve encountered, I’ll document them here since it may not be immediately obvious (although some of the easier cases are).

Signal() and Slot() declarations

PySide’s signal and slot declarations are identical (more or less) to PyQt’s with the exception that they lack a “pyqt” prefix. Thus, the following code:

class MyDialog (QtGui.Dialog):
    progressFinished = QtCore.pyqtSignal()

Must be rewritten as:

class MyDialog (QtGui.Dialog):
    progressFinished = QtCore.Signal()

Likewise, @QtCore.pyqtSlot() decorators are supported by PySide but must be rewritten as @QtCore.Slot(). To this extent, PySide eliminates a handful of unnecessary characters and provides more obvious translation from C++.

setCheckState doesn’t accept integers

PyQt is somewhat more forgiving when passed Python data types and will usually attempt to do the right thing. PySide expects the bindings to follow their C++ cousins more or less exactly, including setting a checkbox’s state via setCheckState(). Thus, checkbox states cannot simply be assigned with the integers 0, 1, or 2 as is possible in PyQt. Instead, the developer must use QtCore.Qt.CheckState.Checked, QtCore.Qt.CheckState.PartiallyChecked, or QtCore.Qt.CheckState.Unchecked. This code does not appear to be compatible between PyQt and PySide.

Python data types may behave unexpectedly in signals

Similar to CheckState, signal arguments are not automatically wrapped (or converted) from Python data types to C++ data types. In one particular case I encountered, larger integers may generate “overflow” errors when exceeding the boundaries of a signed 32-bit integer (2.1 billion, or thereabouts). The only solution in this case is to declare the signal arguments as a Python object as per this example. Other incompatibilities may exist in signal declarations, but this is the one that I encountered fairly early on. Lists and dictionaries are correctly wrapped and behave as expected.

Most other behaviors (threads included) are identical between PyQt and PySide, which is useful for more complicated applications that require offloading activities to one or more threads and error messages appear similar enough (for the most part). More importantly, PySide is a mature enough alternative to PyQt for most uses, although I’ve yet to try it under Windows. It appears binary-only installation is supported on that platform.

No comments.

Updating PostgreSQL JSON fields via SQLAlchemy

If you’ve found this article, you may have discovered that as of PostgreSQL 9.3, there’s no immediately obvious way to easily (for some value of “easy”) update JSON columns (and their fields) in place like you sort of can with HSTORE when using SQLAlchemy. Supposedly, PostgreSQL 9.4 may be adding this feature, which means we’ll have to wait some time thereafter for support to appear in psycopg2 and, by extension, SQLAlchemy.

Update December 20th: PostgreSQL 9.5 will be adding support for incremental updates but you must use JSONB column types. The reason for this is explained in the manual but essentially boils down to this: JSON is stored as text, JSONB is pre-processed into a binary format and stored internally. Besides, you really ought to be using JSONB instead!

Anyway, as it turns out, updating JSON isn’t a big deal, and SQLAlchemy has some really great support for JSON columns. The only thing you need to do is update your JSON field as you would on any other instance if you’re using the ORM, but you still can’t update a specific subset of the JSON column (for that, it needs DBMS support). It’s that easy.

Here’s a contrived example using the Python shell:

# Fetch our object. Pretend there are no errors and it's guaranteed to exist.
>>> config = session.query(Config).filter(Config.id==1).one()
# Our object in this case is a configuration instance with some JSON data.
>>> config.values
{"do_something": true}
# Change it.
>>> config.values["do_something"] = false
>>> from sqlalchemy.orm.attributes import flag_modified
# flag_modified is necessary in this case, especially for complicated JSON structures.
# If you don't use it, you might discover that updated contents will never persist
# to the database because SQLAlchemy isn't aware that the field was changed.
>>> flag_modified(config, "values")
# Commit your changes.
>>> session.commit()

Or use the session directly

# Here's what we want to save:
>>> values
{"do_something": true}
# Change it.
>>> value["do_something"] = false
# Update it in place, for instance using the ORM (or you could the same thing using
# the expression language). You won't need flag_modified in this case, because
# you're overwriting the whole field.
>>> session.query(Config).filter(Config.id == 1).update({"values": values})
>>> session.commit()

The maddening bit about this was how long it took me to figure it out! But hey, I like to share. Hopefully this will save you some time.

Update October 16th, 2015: Included flag_modified usage for updating instances directly.


Python pip: Installing specific tags or versions from Github

Installing packages from pip is not just convenient, it’s also a requirement when working from a virtualenv. Unfortunately, there comes a time when installing a specific version of a package requires a little more finess–and often some mucking about on Github. While there are solutions to this dilemma, the cleanest and most concise one I’ve yet discovered is to modify a particular Github trick that’s documented both on Github and in a number of Python package repositories that are not presently available on PyPI:

pip install https://github.com/<username>/<package>/tarball/<tag, branch, or commit>

For example:

pip install https://github.com/surfly/gevent/tarball/1.0rc3

That’s it!

No comments.
Page 1 of 512345