diff --git a/ci/deps/actions-311-minimum_versions.yaml b/ci/deps/actions-311-minimum_versions.yaml index 3ff7601f0c6b3..75395c0a20372 100644 --- a/ci/deps/actions-311-minimum_versions.yaml +++ b/ci/deps/actions-311-minimum_versions.yaml @@ -46,7 +46,6 @@ dependencies: - pyarrow=12.0.1 - pyiceberg=0.7.1 - pymysql=1.1.0 - - pyqt=5.15.9 - pyreadstat=1.2.6 - pytables=3.8.0 - python-calamine=0.1.7 @@ -62,4 +61,5 @@ dependencies: - zstandard=0.22.0 - pip: + - PyQt6==6.7.1 - tzdata==2023.3 diff --git a/ci/deps/actions-311.yaml b/ci/deps/actions-311.yaml index f1a16bfc97656..21ca6c22f1237 100644 --- a/ci/deps/actions-311.yaml +++ b/ci/deps/actions-311.yaml @@ -39,7 +39,6 @@ dependencies: - numexpr>=2.9.0 - odfpy>=1.4.1 - qtpy>=2.3.0 - - pyqt>=5.15.9 - openpyxl>=3.1.2 - psycopg2>=2.9.9 - pyarrow>=12.0.1 @@ -60,4 +59,5 @@ dependencies: - zstandard>=0.22.0 - pip: + - PyQt6>=6.7.1 - tzdata>=2023.3 diff --git a/ci/deps/actions-312.yaml b/ci/deps/actions-312.yaml index 3222f372182ac..0d9084ae0c157 100644 --- a/ci/deps/actions-312.yaml +++ b/ci/deps/actions-312.yaml @@ -39,7 +39,6 @@ dependencies: - numexpr>=2.9.0 - odfpy>=1.4.1 - qtpy>=2.3.0 - - pyqt>=5.15.9 - openpyxl>=3.1.2 - psycopg2>=2.9.9 - pyarrow>=12.0.1 @@ -60,4 +59,5 @@ dependencies: - zstandard>=0.22.0 - pip: + - PyQt6>=6.7.1 - tzdata>=2023.3 diff --git a/ci/deps/actions-313-downstream_compat.yaml b/ci/deps/actions-313-downstream_compat.yaml index 663d98020b942..44032054994cc 100644 --- a/ci/deps/actions-313-downstream_compat.yaml +++ b/ci/deps/actions-313-downstream_compat.yaml @@ -44,7 +44,6 @@ dependencies: - pyarrow>=12.0.1 - pyiceberg>=0.7.1 - pymysql>=1.1.0 - - pyqt>=5.15.9 - pyreadstat>=1.2.6 - pytables>=3.8.0 - python-calamine>=0.1.7 @@ -71,4 +70,5 @@ dependencies: - pandas-datareader - pyyaml - pip: + - PyQt6>=6.7.1 - tzdata>=2023.3 diff --git a/ci/deps/actions-313.yaml b/ci/deps/actions-313.yaml index 050d71cbfe45e..370f496f001f1 100644 --- a/ci/deps/actions-313.yaml +++ b/ci/deps/actions-313.yaml @@ -40,7 +40,6 @@ dependencies: - numexpr>=2.9.0 - odfpy>=1.4.1 - qtpy>=2.3.0 - - pyqt>=5.15.9 - openpyxl>=3.1.2 - psycopg2>=2.9.9 - pyarrow>=12.0.1 @@ -60,4 +59,5 @@ dependencies: - zstandard>=0.22.0 - pip: + - PyQt6>=6.7.1 - tzdata>=2023.3 diff --git a/doc/source/getting_started/install.rst b/doc/source/getting_started/install.rst index 0002ed869eb31..921dfa67667b9 100644 --- a/doc/source/getting_started/install.rst +++ b/doc/source/getting_started/install.rst @@ -340,12 +340,12 @@ Clipboard Installable with ``pip install "pandas[clipboard]"``. -======================================================================================== ================== =============== ============== -Dependency Minimum Version pip extra Notes -======================================================================================== ================== =============== ============== -`PyQt4 `__/`PyQt5 `__ 5.15.9 clipboard Clipboard I/O -`qtpy `__ 2.3.0 clipboard Clipboard I/O -======================================================================================== ================== =============== ============== +======================================================================================================================================== ================== =============== ============== +Dependency Minimum Version pip extra Notes +======================================================================================================================================== ================== =============== ============== +`PyQt4 `__/`PyQt5 `__/`PyQt6 `__ 5.15.9 clipboard Clipboard I/O +`qtpy `__ 2.3.0 clipboard Clipboard I/O +======================================================================================================================================== ================== =============== ============== .. note:: diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 52038ad4b66c1..82df68490bcba 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -3928,7 +3928,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 PyQt5, PyQt4 or qtpy) on Linux to use these methods. + You may need to install xclip or xsel (with PyQt6, PyQt5, PyQt4 or qtpy) on Linux to use these methods. .. _io.pickle: diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 03ad8ed162c95..1a100b0934a5a 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -214,6 +214,7 @@ Other enhancements - :meth:`pandas.concat` will raise a ``ValueError`` when ``ignore_index=True`` and ``keys`` is not ``None`` (:issue:`59274`) - :py:class:`frozenset` elements in pandas objects are now natively printed (:issue:`60690`) - Add ``"delete_rows"`` option to ``if_exists`` argument in :meth:`DataFrame.to_sql` deleting all records of the table before inserting data (:issue:`37210`). +- Added PyQt6 support to resolve ARM64 container build issues (:issue:`61037`) - Added half-year offset classes :class:`HalfYearBegin`, :class:`HalfYearEnd`, :class:`BHalfYearBegin` and :class:`BHalfYearEnd` (:issue:`60928`) - Added support to read and write from and to Apache Iceberg tables with the new :func:`read_iceberg` and :meth:`DataFrame.to_iceberg` functions (:issue:`61383`) - Errors occurring during SQL I/O will now throw a generic :class:`.DatabaseError` instead of the raw Exception type from the underlying driver manager library (:issue:`60748`) diff --git a/environment.yml b/environment.yml index 29ce9e8a03446..e46d5d5b8c238 100644 --- a/environment.yml +++ b/environment.yml @@ -18,7 +18,6 @@ dependencies: - pytest-xdist>=3.4.0 - pytest-qt>=4.4.0 - pytest-localserver - - pyqt>=5.15.9 - coverage # required dependencies @@ -122,4 +121,5 @@ dependencies: - jupyterlite-pyodide-kernel - pip: + - PyQt6>=6.7.1 - tzdata>=2023.3 diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index d15d9c47efe74..ee1f2ea30dfd7 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -55,7 +55,7 @@ "xlsxwriter": "3.2.0", "zstandard": "0.22.0", "qtpy": "2.3.0", - "pyqt5": "5.15.9", + "PyQt6": "6.7.1", } # A mapping from import name to package name (on PyPI) for packages where diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 6491849925e86..d19d5c7668c63 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -24,7 +24,7 @@ sudo apt-get install xsel sudo apt-get install wl-clipboard -Otherwise on Linux, you will need the PyQt5 modules installed. +Otherwise on Linux, you will need the PyQt6 modules installed. This module does not work with PyGObject yet. @@ -55,6 +55,7 @@ get_errno, sizeof, ) +import importlib import os import platform from shutil import which as _executable_exists @@ -133,18 +134,51 @@ def paste_osx_pyobjc(): return copy_osx_pyobjc, paste_osx_pyobjc -def init_qt_clipboard(): - global QApplication - # $DISPLAY should exist +def _import_module(modules: list[tuple[str, str | None]]): + """ + Attempt to import from a module from a list inorder. + + Args: + modules: A list of tuples of two elements. The first element + is the module to import from and the second element is + the object to import. If the second element is not provided, + just import the module. + + Returns: + The first successful import. - # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 - try: - from qtpy.QtWidgets import QApplication - except ImportError: + Raises: + ImportError: If couldn't import any module. + AttributeError: If a module doesn't have the expected attribute. + """ + + for module_name, attribute_name in modules: try: - from PyQt5.QtWidgets import QApplication + module = importlib.import_module(module_name) + + if attribute_name is None: + return module + return getattr(module, attribute_name) + except ImportError: - from PyQt4.QtGui import QApplication + continue + + raise ImportError( + f"No module from {tuple(module_name for module_name, _ in modules)} could be imported." + ) + + +def init_qt_clipboard(): + # $DISPLAY should exist + global QApplication + + qt_qapplication_bindings = [ + ("qtpy.QtWidgets", "QApplication"), + ("PyQt6.QtWidgets", "QApplication"), + ("PyQt5.QtWidgets", "QApplication"), + ("PyQt4.QtGui", "QApplication"), + ] + QApplication = _import_module(qt_qapplication_bindings) app = QApplication.instance() if app is None: @@ -529,7 +563,7 @@ def determine_clipboard(): Determine the OS/platform and set the copy() and paste() functions accordingly. """ - global Foundation, AppKit, qtpy, PyQt4, PyQt5 + global Foundation, AppKit # Setup for the CYGWIN platform: if ( @@ -576,25 +610,11 @@ def determine_clipboard(): return init_klipper_clipboard() try: - # 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/project/QtPy - import qtpy # check if qtpy is installed - except ImportError: - # 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: + # Verify installation of pyqt, PyQt{6,5,4} and initialize its clipboard. return init_qt_clipboard() + except ImportError: + # Ignore if Qt isn't available + pass return init_no_clipboard() @@ -618,7 +638,7 @@ def set_clipboard(clipboard): clipboard_types = { "pbcopy": init_osx_pbcopy_clipboard, "pyobjc": init_osx_pyobjc_clipboard, - "qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' + "qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', 'pyqt5' and 'pyqt6' "xclip": init_xclip_clipboard, "xsel": init_xsel_clipboard, "wl-clipboard": init_wl_clipboard, diff --git a/pyproject.toml b/pyproject.toml index 450fd06232d8c..b0ddeb13fefb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ html = ['beautifulsoup4>=4.12.3', 'html5lib>=1.1', 'lxml>=4.9.2'] xml = ['lxml>=4.9.2'] plot = ['matplotlib>=3.8.3'] output-formatting = ['jinja2>=3.1.3', 'tabulate>=0.9.0'] -clipboard = ['PyQt5>=5.15.9', 'qtpy>=2.3.0'] +clipboard = ['PyQt6>=6.7.1', 'qtpy>=2.3.0'] compression = ['zstandard>=0.22.0'] timezone = ['pytz>=2023.4'] all = ['adbc-driver-postgresql>=1.2.0', @@ -99,7 +99,7 @@ all = ['adbc-driver-postgresql>=1.2.0', 'pyarrow>=12.0.1', 'pyiceberg>=0.7.1', 'pymysql>=1.1.0', - 'PyQt5>=5.15.9', + 'PyQt6>=6.7.1', 'pyreadstat>=1.2.6', 'pytest>=7.3.2', 'pytest-xdist>=3.4.0', diff --git a/requirements-dev.txt b/requirements-dev.txt index ce0ff91b2c8b3..e0169b067d2ac 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,6 @@ pytest-cov pytest-xdist>=3.4.0 pytest-qt>=4.4.0 pytest-localserver -PyQt5>=5.15.9 coverage python-dateutil numpy<3 @@ -85,4 +84,5 @@ requests pygments jupyterlite-core jupyterlite-pyodide-kernel +PyQt6>=6.7.1 tzdata>=2023.3 diff --git a/scripts/generate_pip_deps_from_conda.py b/scripts/generate_pip_deps_from_conda.py index f84f79f4862f1..4fa6609c77288 100755 --- a/scripts/generate_pip_deps_from_conda.py +++ b/scripts/generate_pip_deps_from_conda.py @@ -30,7 +30,7 @@ "dask-core": "dask", "seaborn-base": "seaborn", "sqlalchemy": "SQLAlchemy", - "pyqt": "PyQt5", + "pyqt": "PyQt6", } diff --git a/scripts/tests/data/deps_minimum.toml b/scripts/tests/data/deps_minimum.toml index 22a30ed3a83f5..6815623bad819 100644 --- a/scripts/tests/data/deps_minimum.toml +++ b/scripts/tests/data/deps_minimum.toml @@ -71,7 +71,7 @@ html = ['beautifulsoup4>=4.9.3', 'html5lib>=1.1', 'lxml>=4.6.3'] xml = ['lxml>=4.6.3'] plot = ['matplotlib>=3.6.1'] output_formatting = ['jinja2>=3.0.0', 'tabulate>=0.8.9'] -clipboard = ['PyQt5>=5.15.1', 'qtpy>=2.3.0'] +clipboard = ['PyQt6>=6.7.1', 'qtpy>=2.3.0'] compression = ['zstandard>=0.15.2'] all = ['beautifulsoup4>=5.9.3', 'bottleneck>=1.3.2', @@ -90,7 +90,7 @@ all = ['beautifulsoup4>=5.9.3', 'psycopg2>=2.9.9', 'pyarrow>=7.0.0', 'pymysql>=1.1.0', - 'PyQt5>=5.15.1', + 'PyQt6>=6.7.1', 'pyreadstat>=1.1.2', 'pytest>=7.3.2', 'pytest-xdist>=3.4.0',