From aabd9a8806b3e2d32bc8d022e547bdb367abfa98 Mon Sep 17 00:00:00 2001 From: dvincentwest Date: Fri, 29 Sep 2017 13:37:35 -0500 Subject: [PATCH 01/11] 2 code changes and docstring changes to use qtpy in place of PyQt4 for the clipboard. This should prevent any conflicts with other qt-bindings packages when embedding pandas in a Qt-based gui and should also provide compatibility with Python3 since PyQt4 may not be available for the latest releases. --- pandas/io/clipboard/__init__.py | 11 ++++++----- pandas/io/clipboard/clipboards.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 4066a3be5e850..5360a1a55f0f1 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 or qtpy 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,8 +69,8 @@ def determine_clipboard(): return init_gtk_clipboard() try: - # Check if PyQt4 is installed - import PyQt4 # noqa + # Check if qtpy is installed + import qtpy # noqa except ImportError: pass else: diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py index e32380a383374..38f845062b7c6 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -46,7 +46,7 @@ def paste_gtk(): def init_qt_clipboard(): # $DISPLAY should exist - from PyQt4.QtGui import QApplication + from qtpy.QtWidgets import QApplication # use the global instance if it exists app = QApplication.instance() or QApplication([]) From b03e69c3c95cfea505fba1d52e67c1a18e39526e Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 30 Oct 2017 07:36:49 -0500 Subject: [PATCH 02/11] clipboard now checks for qtpy, the PyQt5, then PyQt4 on linux for the clipboard --- pandas/io/clipboard/__init__.py | 20 ++++++++++++++++---- pandas/io/clipboard/clipboards.py | 20 ++++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 5360a1a55f0f1..c1789540ca1d1 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -69,13 +69,25 @@ def determine_clipboard(): return init_gtk_clipboard() try: - # Check if qtpy is installed - import qtpy # 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 # check if qtpy is installed except ImportError: - pass + # If qtpy isn't installed, fall back on importing PyQt4. + try: + import PyQt5 # check if PyQt5 is installed + except ImportError: + try: + import PyQt4 # check if PyQt4 is installed + except ImportError: + pass # We want to fail fast for all non-ImportError exceptions. + else: + return init_qt_clipboard() + else: + return init_qt_clipboard() + else: return init_qt_clipboard() - if _executable_exists("xclip"): return init_xclip_clipboard() if _executable_exists("xsel"): diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py index 38f845062b7c6..3053674425490 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -46,20 +46,24 @@ def paste_gtk(): def init_qt_clipboard(): # $DISPLAY should exist - from qtpy.QtWidgets 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: + try: + from PyQt5.QtWidgets import QApplication + except: + from PyQt4.QtGui import QApplication + + app = QApplication.instance() + if app is None: + app = QApplication([]) def copy_qt(text): cb = app.clipboard() cb.setText(text) - def paste_qt(): - cb = app.clipboard() - return text_type(cb.text()) - - return copy_qt, paste_qt def init_xclip_clipboard(): From d16a20201156eadae7bd99a3135388b7fe2a8a6e Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 30 Oct 2017 07:51:29 -0500 Subject: [PATCH 03/11] updated documentation to mention updated Qt-bindings requirements --- doc/source/install.rst | 3 ++- doc/source/io.rst | 2 +- pandas/io/clipboard/__init__.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/source/install.rst b/doc/source/install.rst index c805f84d0faaa..1ffc918b2a666 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -251,7 +251,8 @@ 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 +* One of `PyQt5 + `__, `PyQt4 `__, `PySide `__, `pygtk `__, `xsel diff --git a/doc/source/io.rst b/doc/source/io.rst index 8656e617b8173..237922893ee9e 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, or Qt-bindings (PyQt5/4, PySide, qtpy)) on Linux to use these methods. .. _io.pickle: diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index c1789540ca1d1..a1658ed9ff017 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -85,7 +85,7 @@ def determine_clipboard(): return init_qt_clipboard() else: return init_qt_clipboard() - + else: return init_qt_clipboard() if _executable_exists("xclip"): From 03b7b1ae5619a62cc46ffe88d7a736ed7cd3eef6 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 30 Oct 2017 07:58:21 -0500 Subject: [PATCH 04/11] further updates to comments and documentation --- doc/source/io.rst | 2 +- pandas/io/clipboard/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/io.rst b/doc/source/io.rst index 237922893ee9e..58ff482fdf55c 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 Qt-bindings (PyQt5/4, PySide, qtpy)) 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/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index a1658ed9ff017..a7ca1c3e7cb5d 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -18,7 +18,7 @@ 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 qtpy 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, @@ -73,7 +73,7 @@ def determine_clipboard(): # https://pypi.python.org/pypi/QtPy import qtpy # check if qtpy is installed except ImportError: - # If qtpy isn't installed, fall back on importing PyQt4. + # If qtpy isn't installed, fall back on importing PyQt5 and PyQt4. try: import PyQt5 # check if PyQt5 is installed except ImportError: From eb0b0e7883dc653e5e1b769a23a36943dd54998d Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 30 Oct 2017 08:10:00 -0500 Subject: [PATCH 05/11] re-inserting paste_qt definition from init_qt_clipboard --- pandas/io/clipboard/__init__.py | 6 +++--- pandas/io/clipboard/clipboards.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index a7ca1c3e7cb5d..1f26038280492 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -73,21 +73,21 @@ def determine_clipboard(): # https://pypi.python.org/pypi/QtPy import qtpy # check if qtpy is installed except ImportError: - # If qtpy isn't installed, fall back on importing PyQt5 and PyQt4. + # If qtpy isn't installed, fall back on importing PyQt4. try: import PyQt5 # check if PyQt5 is installed except ImportError: try: import PyQt4 # check if PyQt4 is installed except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. + pass # We want to fail fast for all non-ImportError exceptions. else: return init_qt_clipboard() else: return init_qt_clipboard() - else: return init_qt_clipboard() + if _executable_exists("xclip"): return init_xclip_clipboard() if _executable_exists("xsel"): diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py index 3053674425490..6af3db7e3d5fd 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -64,6 +64,11 @@ def copy_qt(text): cb = app.clipboard() cb.setText(text) + def paste_qt(): + cb = app.clipboard() + return text_type(cb.text()) + + return copy_qt, paste_qt def init_xclip_clipboard(): From 7baacccc381db0f7dbcec4331de6379cf93706b6 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 30 Oct 2017 10:33:06 -0500 Subject: [PATCH 06/11] fixing flake8 style checking requirements for line-length and unused import statements --- pandas/io/clipboard/__init__.py | 14 ++++++++------ pandas/io/clipboard/clipboards.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 1f26038280492..37d398f20ef41 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -69,22 +69,24 @@ def determine_clipboard(): return init_gtk_clipboard() try: - # qtpy is a small abstraction layer that lets you write applications using a single api call to either PyQt or PySide. + # 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 # check if qtpy is installed + import qtpy # noqa except ImportError: - # If qtpy isn't installed, fall back on importing PyQt4. + # If qtpy isn't installed, fall back on importing PyQt5, or PyQt5 try: - import PyQt5 # check if PyQt5 is installed + import PyQt5 # noqa except ImportError: try: - import PyQt4 # check if PyQt4 is installed + import PyQt4 # noqa except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. + 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 6af3db7e3d5fd..0cf2c11c826bd 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -50,10 +50,10 @@ def init_qt_clipboard(): # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 try: from qtpy.QtWidgets import QApplication - except: + except ModuleNotFoundError: try: from PyQt5.QtWidgets import QApplication - except: + except ModuleNotFoundError: from PyQt4.QtGui import QApplication app = QApplication.instance() From 432896de390d736b5c246cff6f0e3cb5a20c0e60 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 31 Oct 2017 08:34:32 -0500 Subject: [PATCH 07/11] changed ModuleNotFoundError to ImportError --- pandas/io/clipboard/clipboards.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py index 0cf2c11c826bd..285d93e3ca497 100644 --- a/pandas/io/clipboard/clipboards.py +++ b/pandas/io/clipboard/clipboards.py @@ -50,10 +50,10 @@ def init_qt_clipboard(): # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 try: from qtpy.QtWidgets import QApplication - except ModuleNotFoundError: + except ImportError: try: from PyQt5.QtWidgets import QApplication - except ModuleNotFoundError: + except ImportError: from PyQt4.QtGui import QApplication app = QApplication.instance() From a8a91c870a9b9f78e0c63f7b5dac54a05effb9fc Mon Sep 17 00:00:00 2001 From: dvincentwest Date: Mon, 20 Nov 2017 21:03:51 -0600 Subject: [PATCH 08/11] release announcement for clipboard update --- doc/source/whatsnew/v0.22.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index c27a684984ba8..216f200b7c091 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -28,6 +28,7 @@ Other Enhancements - :class:`pandas.io.formats.style.Styler` now has method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`) - :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:`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: From f3a77a9829966538bab2dd2949937d89f7284db1 Mon Sep 17 00:00:00 2001 From: dvincentwest Date: Mon, 20 Nov 2017 21:23:19 -0600 Subject: [PATCH 09/11] what's new update moved to IO section --- doc/source/whatsnew/v0.22.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 216f200b7c091..5524de7b77304 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -28,7 +28,6 @@ Other Enhancements - :class:`pandas.io.formats.style.Styler` now has method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`) - :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:`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: @@ -128,6 +127,7 @@ I/O - Bug in :func:`read_msgpack` with a non existent file is passed in Python 2 (:issue:`15296`) - Bug in :func:`read_csv` where a ``MultiIndex`` with duplicate columns was not being mangled appropriately (:issue:`18062`) - Bug in :func:`read_sas` where a file with 0 variables gave an ``AttributeError`` incorrectly. Now it gives an ``EmptyDataError`` (:issue:`18184`) +- :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`) - - From 7573d0508fa17f2a3a98135577aece37036d8dec Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 23 Nov 2017 21:59:29 -0600 Subject: [PATCH 10/11] adding PyQt and qtpy to build requirements and update to install notes --- ci/requirements-3.6_BUILD_TEST.sh | 2 +- doc/source/install.rst | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) 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 1ffc918b2a666..b8968e18aecb0 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -251,13 +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 `PyQt5 - `__, `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 `__ From 53ee4bcf66c8c2f9e0f6c1e0ed20723cfce9cdf0 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 23 Nov 2017 22:03:23 -0600 Subject: [PATCH 11/11] moving clipboard changes back to other enhancements --- doc/source/whatsnew/v0.22.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 70a5a83bb31ef..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: @@ -146,7 +147,6 @@ I/O - Bug in :func:`read_msgpack` with a non existent file is passed in Python 2 (:issue:`15296`) - Bug in :func:`read_csv` where a ``MultiIndex`` with duplicate columns was not being mangled appropriately (:issue:`18062`) - Bug in :func:`read_sas` where a file with 0 variables gave an ``AttributeError`` incorrectly. Now it gives an ``EmptyDataError`` (:issue:`18184`) -- :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`) - -