diff --git a/.changes/unreleased/Under the Hood-20240221-145058.yaml b/.changes/unreleased/Under the Hood-20240221-145058.yaml new file mode 100644 index 00000000000..a847bb68c53 --- /dev/null +++ b/.changes/unreleased/Under the Hood-20240221-145058.yaml @@ -0,0 +1,6 @@ +kind: Under the Hood +body: Make dbt-core compatible with Python 3.12 +time: 2024-02-21T14:50:58.983559Z +custom: + Author: l1xnan aranke + Issue: "9007" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1819146369a..db9fade87f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] env: TOXENV: "unit" @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] os: [ubuntu-20.04] split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }} include: ${{ fromJson(needs.integration-metadata.outputs.include) }} diff --git a/core/dbt/task/test.py b/core/dbt/task/test.py index 9548f633eb7..0754ca2277f 100644 --- a/core/dbt/task/test.py +++ b/core/dbt/task/test.py @@ -1,12 +1,10 @@ -from distutils.util import strtobool - import agate import daff import io import json import re from dataclasses import dataclass -from dbt.utils import _coerce_decimal +from dbt.utils import _coerce_decimal, strtobool from dbt_common.events.format import pluralize from dbt_common.dataclass_schema import dbtClassMixin import threading diff --git a/core/dbt/utils.py b/core/dbt/utils.py index b4d510da8bb..8f7509a5dec 100644 --- a/core/dbt/utils.py +++ b/core/dbt/utils.py @@ -369,3 +369,21 @@ def args_to_dict(args): dict_args[key] = var_args[key] return dict_args + + +# Taken from https://github.com/python/cpython/blob/3.11/Lib/distutils/util.py +# This is a copy of the function from distutils.util, which was removed in Python 3.12. +def strtobool(val: str) -> bool: + """Convert a string representation of truth to True or False. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError("invalid truth value %r" % (val,)) diff --git a/core/setup.py b/core/setup.py index 3bd43cd31df..190b4501e41 100644 --- a/core/setup.py +++ b/core/setup.py @@ -95,6 +95,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], python_requires=">=3.8", ) diff --git a/pytest.ini b/pytest.ini index 0760d49a55a..800dd6b9ece 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,3 +7,4 @@ env_files = testpaths = tests/functional tests/unit +pythonpath = core diff --git a/tests/unit/test_plugin_manager.py b/tests/unit/test_plugin_manager.py index 4cfe01d0dfc..bf25d810729 100644 --- a/tests/unit/test_plugin_manager.py +++ b/tests/unit/test_plugin_manager.py @@ -101,13 +101,25 @@ def test_get_nodes(self, tracking, get_nodes_plugins): nodes = pm.get_nodes() assert len(nodes.models) == 2 - assert tracking.track_plugin_get_nodes.called_once_with( - { - "plugin_name": get_nodes_plugins[0].name, - "num_model_nodes": 2, - "num_model_packages": 1, - } - ) + + expected_calls = [ + mock.call( + { + "plugin_name": get_nodes_plugins[0].name, + "num_model_nodes": 1, + "num_model_packages": 1, + } + ), + mock.call( + { + "plugin_name": get_nodes_plugins[1].name, + "num_model_nodes": 1, + "num_model_packages": 1, + } + ), + ] + + tracking.track_plugin_get_nodes.assert_has_calls(expected_calls) def test_get_manifest_artifact(self, get_artifacts_plugins): pm = PluginManager(plugins=get_artifacts_plugins)