diff --git a/ci/requirements-3.6_BUILD_TEST.sh b/ci/requirements-3.6_BUILD_TEST.sh index 84dd27c50d587..2a3adeff836ee 100644 --- a/ci/requirements-3.6_BUILD_TEST.sh +++ b/ci/requirements-3.6_BUILD_TEST.sh @@ -4,4 +4,4 @@ source activate pandas echo "install 36 BUILD_TEST" -conda install -n pandas -c conda-forge pyarrow dask +conda install -n pandas -c conda-forge pyarrow dask pyqt qtpy diff --git a/doc/source/install.rst b/doc/source/install.rst index c805f84d0faaa..b8968e18aecb0 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -251,12 +251,13 @@ Optional Dependencies * `Jinja2 `__: Template engine for conditional HTML formatting. * `s3fs `__: necessary for Amazon S3 access (s3fs >= 0.0.7). * `blosc `__: for msgpack compression using ``blosc`` -* One of `PyQt4 - `__, `PySide - `__, `pygtk - `__, `xsel - `__, or `xclip - `__: necessary to use +* One of + `qtpy `__ (requires PyQt or PySide), + `PyQt5 `__, + `PyQt4 `__, + `pygtk `__, + `xsel `__, or + `xclip `__: necessary to use :func:`~pandas.read_clipboard`. Most package managers on Linux distributions will have ``xclip`` and/or ``xsel`` immediately available for installation. * For Google BigQuery I/O - see `here `__ diff --git a/doc/source/io.rst b/doc/source/io.rst index c94d5bc75d4fc..5390fc3399e23 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -3053,7 +3053,7 @@ We can see that we got the same content back, which we had earlier written to th .. note:: - You may need to install xclip or xsel (with gtk or PyQt4 modules) on Linux to use these methods. + You may need to install xclip or xsel (with gtk, PyQt5, PyQt4 or qtpy) on Linux to use these methods. .. _io.pickle: diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 90032a692fd15..9b7226f0fe594 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -44,6 +44,7 @@ Other Enhancements - :class:`pandas.io.formats.style.Styler` now has method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`) - Improved wording of ``ValueError`` raised in :func:`to_datetime` when ``unit=`` is passed with a non-convertible value (:issue:`14350`) - :func:`Series.fillna` now accepts a Series or a dict as a ``value`` for a categorical dtype (:issue:`17033`) +- :func:`pandas.read_clipboard` updated to use qtpy, falling back to PyQt5 and then PyQt4, adding compatibility with Python3 and multiple python-qt bindings (:issue:`17722`) .. _whatsnew_0220.api_breaking: diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 4066a3be5e850..37d398f20ef41 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -18,7 +18,8 @@ On Linux, install xclip or xsel via package manager. For example, in Debian: sudo apt-get install xclip -Otherwise on Linux, you will need the gtk or PyQt4 modules installed. +Otherwise on Linux, you will need the gtk, qtpy or PyQt modules installed. +qtpy also requires a python-qt-bindings module: PyQt4, PyQt5, PySide, PySide2 gtk and PyQt4 modules are not available for Python 3, and this module does not work with PyGObject yet. @@ -34,9 +35,9 @@ init_klipper_clipboard, init_no_clipboard) from .windows import init_windows_clipboard -# `import PyQt4` sys.exit()s if DISPLAY is not in the environment. +# `import qtpy` sys.exit()s if DISPLAY is not in the environment. # Thus, we need to detect the presence of $DISPLAY manually -# and not load PyQt4 if it is absent. +# and not load qtpy if it is absent. HAS_DISPLAY = os.getenv("DISPLAY", False) CHECK_CMD = "where" if platform.system() == "Windows" else "which" @@ -68,9 +69,23 @@ def determine_clipboard(): return init_gtk_clipboard() try: - # Check if PyQt4 is installed - import PyQt4 # noqa + # qtpy is a small abstraction layer that lets you write + # applications using a single api call to either PyQt or PySide + # https://pypi.python.org/pypi/QtPy + import qtpy # noqa except ImportError: + # If qtpy isn't installed, fall back on importing PyQt5, or PyQt5 + try: + import PyQt5 # noqa + except ImportError: + try: + import PyQt4 # noqa + except ImportError: + pass # fail fast for all non-ImportError exceptions. + else: + return init_qt_clipboard() + else: + return init_qt_clipboard() pass else: return init_qt_clipboard() diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py index e32380a383374..285d93e3ca497 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -46,10 +46,19 @@ def paste_gtk(): def init_qt_clipboard(): # $DISPLAY should exist - from PyQt4.QtGui import QApplication - # use the global instance if it exists - app = QApplication.instance() or QApplication([]) + # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 + try: + from qtpy.QtWidgets import QApplication + except ImportError: + try: + from PyQt5.QtWidgets import QApplication + except ImportError: + from PyQt4.QtGui import QApplication + + app = QApplication.instance() + if app is None: + app = QApplication([]) def copy_qt(text): cb = app.clipboard()