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',