diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml
index 39b9ae7a..567460c6 100644
--- a/.github/workflows/compliance.yml
+++ b/.github/workflows/compliance.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python: ['3.13']
+ python: ['3.14']
steps:
- name: Checkout
uses: actions/checkout@v5
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python: ['3.13']
+ python: ['3.14']
steps:
- name: Checkout
uses: actions/checkout@v5
diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index 6e05ae54..970c47d2 100644
--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python: ['3.13']
+ python: ['3.14']
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python: ['3.13']
+ python: ['3.14']
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/.kokoro/samples/python3.14/common.cfg b/.kokoro/samples/python3.14/common.cfg
new file mode 100644
index 00000000..712b0a5e
--- /dev/null
+++ b/.kokoro/samples/python3.14/common.cfg
@@ -0,0 +1,40 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Build logs will be here
+action {
+ define_artifacts {
+ regex: "**/*sponge_log.xml"
+ }
+}
+
+# Specify which tests to run
+env_vars: {
+ key: "RUN_TESTS_SESSION"
+ value: "py-3.14"
+}
+
+# Declare build specific Cloud project.
+env_vars: {
+ key: "BUILD_SPECIFIC_GCLOUD_PROJECT"
+ value: "python-docs-samples-tests-314"
+}
+
+env_vars: {
+ key: "TRAMPOLINE_BUILD_FILE"
+ value: "github/python-db-dtypes-pandas/.kokoro/test-samples.sh"
+}
+
+# Configure the docker image for kokoro-trampoline.
+env_vars: {
+ key: "TRAMPOLINE_IMAGE"
+ value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker"
+}
+
+# Download secrets for samples
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples"
+
+# Download trampoline resources.
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
+
+# Use the trampoline script to run in docker.
+build_file: "python-db-dtypes-pandas/.kokoro/trampoline_v2.sh"
diff --git a/.kokoro/samples/python3.14/continuous.cfg b/.kokoro/samples/python3.14/continuous.cfg
new file mode 100644
index 00000000..a1c8d975
--- /dev/null
+++ b/.kokoro/samples/python3.14/continuous.cfg
@@ -0,0 +1,6 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+env_vars: {
+ key: "INSTALL_LIBRARY_FROM_SOURCE"
+ value: "True"
+}
\ No newline at end of file
diff --git a/.kokoro/samples/python3.14/periodic-head.cfg b/.kokoro/samples/python3.14/periodic-head.cfg
new file mode 100644
index 00000000..ee3d5640
--- /dev/null
+++ b/.kokoro/samples/python3.14/periodic-head.cfg
@@ -0,0 +1,11 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+env_vars: {
+ key: "INSTALL_LIBRARY_FROM_SOURCE"
+ value: "True"
+}
+
+env_vars: {
+ key: "TRAMPOLINE_BUILD_FILE"
+ value: "github/python-db-dtypes-pandas/.kokoro/test-samples-against-head.sh"
+}
diff --git a/.kokoro/samples/python3.14/periodic.cfg b/.kokoro/samples/python3.14/periodic.cfg
new file mode 100644
index 00000000..71cd1e59
--- /dev/null
+++ b/.kokoro/samples/python3.14/periodic.cfg
@@ -0,0 +1,6 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+env_vars: {
+ key: "INSTALL_LIBRARY_FROM_SOURCE"
+ value: "False"
+}
diff --git a/.kokoro/samples/python3.14/presubmit.cfg b/.kokoro/samples/python3.14/presubmit.cfg
new file mode 100644
index 00000000..a1c8d975
--- /dev/null
+++ b/.kokoro/samples/python3.14/presubmit.cfg
@@ -0,0 +1,6 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+env_vars: {
+ key: "INSTALL_LIBRARY_FROM_SOURCE"
+ value: "True"
+}
\ No newline at end of file
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index c333038d..f52f2553 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -22,7 +22,7 @@ In order to add a feature:
documentation.
- The feature must work fully on the following CPython versions:
- 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 on both UNIX and Windows.
+ 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows.
- The feature must not add unnecessary dependencies (where
"unnecessary" is of course subjective, but new dependencies should
@@ -72,7 +72,7 @@ We use `nox `__ to instrument our tests.
- To run a single unit test::
- $ nox -s unit-3.13 -- -k
+ $ nox -s unit-3.14 -- -k
.. note::
@@ -228,6 +228,7 @@ We support:
- `Python 3.11`_
- `Python 3.12`_
- `Python 3.13`_
+- `Python 3.14`_
.. _Python 3.7: https://docs.python.org/3.7/
.. _Python 3.8: https://docs.python.org/3.8/
@@ -236,6 +237,7 @@ We support:
.. _Python 3.11: https://docs.python.org/3.11/
.. _Python 3.12: https://docs.python.org/3.12/
.. _Python 3.13: https://docs.python.org/3.13/
+.. _Python 3.14: https://docs.python.org/3.14/
Supported versions can be found in our ``noxfile.py`` `config`_.
diff --git a/noxfile.py b/noxfile.py
index 125d4f1c..e3659786 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -41,6 +41,7 @@
"3.11",
"3.12",
"3.13",
+ "3.14",
]
UNIT_TEST_STANDARD_DEPENDENCIES = [
"mock",
@@ -218,14 +219,11 @@ def prerelease(session, tests_path):
"--upgrade",
"pyarrow",
)
- # Avoid pandas==2.2.0rc0 as this version causes PyArrow to fail. Once newer
- # prerelease comes out, this constraint can be removed. See
- # https://github.com/googleapis/python-db-dtypes-pandas/issues/234
session.install(
"--prefer-binary",
"--pre",
"--upgrade",
- "pandas!=2.2.0rc0",
+ "pandas<3.0.0rc0",
)
session.install(
"mock",
diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py
index c9a3d1ec..c94b48b0 100644
--- a/samples/snippets/noxfile.py
+++ b/samples/snippets/noxfile.py
@@ -89,7 +89,7 @@ def get_pytest_env_vars() -> Dict[str, str]:
# DO NOT EDIT - automatically generated.
# All versions used to test samples.
-ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
# Any default versions that should be ignored.
IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"]
diff --git a/setup.py b/setup.py
index 093bf2ed..186f31aa 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,7 @@
"numpy >= 1.24.0, <= 2.2.6 ; python_version == '3.10'",
"numpy >= 1.24.0 ; python_version != '3.10'",
"packaging >= 24.2.0",
- "pandas >= 1.5.3",
+ "pandas >= 1.5.3, < 3.0.0",
"pyarrow >= 13.0.0",
]
@@ -69,6 +69,7 @@ def readme():
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Topic :: Database :: Front-Ends",
],
diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/test_date.py b/tests/unit/test_date.py
index a3ce43ee..4fb41a2e 100644
--- a/tests/unit/test_date.py
+++ b/tests/unit/test_date.py
@@ -137,9 +137,9 @@ def test_date_set_slice_null():
("1-3", "Bad date string: '1-3'"),
("1", "Bad date string: '1'"),
("", "Bad date string: ''"),
- ("2021-2-99", "day is out of range for month"),
+ ("2021-2-99", "day is out of range for month|day .* must be in range .*"),
("2021-99-1", "month must be in 1[.][.]12"),
- ("10000-1-1", "year 10000 is out of range"),
+ ("10000-1-1", "year 10000 is out of range|year must be in .*"),
],
)
def test_date_parsing_errors(value, error):
diff --git a/tests/unit/test_pandas_backports.py b/tests/unit/test_pandas_backports.py
index cb783045..e6d5cfaf 100644
--- a/tests/unit/test_pandas_backports.py
+++ b/tests/unit/test_pandas_backports.py
@@ -12,16 +12,39 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import builtins
import unittest.mock as mock
import db_dtypes.pandas_backports as pandas_backports
+REAL_IMPORT = builtins.__import__
+
+
+def _import_side_effect(module_name, result_return=None, result_raise=None):
+ """
+ Builds a side-effect for mocking the import function.
+ If the imported package matches `name`, it will return or raise based on
+ arguments. Otherwise, it will default to regular import behaviour
+ """
+
+ def _impl(name, *args, **kwargs):
+ if name == module_name:
+ if result_raise:
+ raise result_raise
+ else:
+ return result_return
+ else: # pragma: NO COVER
+ return REAL_IMPORT(name, *args, **kwargs)
+
+ return _impl
+
@mock.patch("builtins.__import__")
def test_import_default_module_found(mock_import):
mock_module = mock.MagicMock()
- mock_module.OpsMixin = "OpsMixin_from_module" # Simulate successful import
- mock_import.return_value = mock_module
+ mock_module.OpsMixin = "OpsMixin_from_module"
+
+ mock_import.side_effect = _import_side_effect("module_name", mock_module)
default_class = type("OpsMixin", (), {}) # Dummy class
result = pandas_backports.import_default("module_name", default=default_class)
@@ -30,7 +53,9 @@ def test_import_default_module_found(mock_import):
@mock.patch("builtins.__import__")
def test_import_default_module_not_found(mock_import):
- mock_import.side_effect = ModuleNotFoundError
+ mock_import.side_effect = _import_side_effect(
+ "module_name", result_raise=ModuleNotFoundError
+ )
default_class = type("OpsMixin", (), {}) # Dummy class
result = pandas_backports.import_default("module_name", default=default_class)
@@ -48,6 +73,7 @@ def test_import_default_force_true(mock_import):
result = pandas_backports.import_default(
"any_module_name", force=True, default=default_class
)
+ assert mock_import.call_count == 0
# Assert that the returned value is the default class itself
assert result is default_class