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