This is an automated email from the ASF dual-hosted git repository. ebenizzy pushed a commit to branch remove-3.9-support in repository https://gitbox.apache.org/repos/asf/hamilton.git
commit 86af3701f309b39fb677ba8310da7a11cc5f77a3 Author: Elijah ben Izzy <[email protected]> AuthorDate: Sun Oct 19 10:50:54 2025 -0700 Removes all 3.8/3.9 references --- examples/hello_world/my_dag.png | Bin 63755 -> 68544 bytes examples/styling_visualization/dag.png | Bin 341276 -> 297452 bytes hamilton/cli/__main__.py | 8 +---- hamilton/htypes.py | 6 +--- hamilton/plugins/h_spark.py | 6 +--- plugin_tests/h_spark/test_h_spark.py | 2 -- tests/function_modifiers/test_adapters.py | 14 ++------ tests/function_modifiers/test_expanders.py | 36 +++++++++------------ .../pandera/test_pandera_data_quality.py | 3 -- tests/plugins/test_dlt_extensions.py | 19 +++-------- tests/plugins/test_huggingface_extensions.py | 3 -- tests/plugins/test_mlflow_extension.py | 15 ++------- tests/resources/nodes_with_future_annotation.py | 8 +---- tests/test_graph_types.py | 7 ---- tests/test_type_utils.py | 34 +++---------------- 15 files changed, 33 insertions(+), 128 deletions(-) diff --git a/examples/hello_world/my_dag.png b/examples/hello_world/my_dag.png index b77a634a..77e64e42 100644 Binary files a/examples/hello_world/my_dag.png and b/examples/hello_world/my_dag.png differ diff --git a/examples/styling_visualization/dag.png b/examples/styling_visualization/dag.png index 80da1082..ceda0c39 100644 Binary files a/examples/styling_visualization/dag.png and b/examples/styling_visualization/dag.png differ diff --git a/hamilton/cli/__main__.py b/hamilton/cli/__main__.py index 41c411c6..e5321bcb 100644 --- a/hamilton/cli/__main__.py +++ b/hamilton/cli/__main__.py @@ -19,16 +19,10 @@ import dataclasses import json import logging import os -import sys import warnings from pathlib import Path from pprint import pprint -from typing import Any, Callable, List, Optional - -if sys.version_info < (3, 9): - from typing_extensions import Annotated -else: - from typing import Annotated +from typing import Annotated, Any, Callable, List, Optional import typer diff --git a/hamilton/htypes.py b/hamilton/htypes.py index 6849ed56..ad374243 100644 --- a/hamilton/htypes.py +++ b/hamilton/htypes.py @@ -18,14 +18,10 @@ import inspect import sys import typing -from typing import Any, Iterable, Optional, Protocol, Tuple, Type, TypeVar, Union +from typing import Any, Iterable, Literal, Optional, Protocol, Tuple, Type, TypeVar, Union import typing_inspect -if sys.version_info >= (3, 9): - from typing import Literal -else: - Literal = None from hamilton.registry import COLUMN_TYPE, DF_TYPE_AND_COLUMN_TYPES BASE_ARGS_FOR_GENERICS = (typing.T,) diff --git a/hamilton/plugins/h_spark.py b/hamilton/plugins/h_spark.py index 3d0f7e33..ae494319 100644 --- a/hamilton/plugins/h_spark.py +++ b/hamilton/plugins/h_spark.py @@ -18,7 +18,6 @@ import functools import inspect import logging -import sys from types import CodeType, FunctionType, ModuleType from typing import Any, Callable, Collection, Dict, List, Optional, Set, Tuple, Type, Union @@ -229,10 +228,7 @@ def python_to_spark_type(python_type: Type[Union[int, float, bool, str, bytes]]) raise ValueError("Unsupported Python type: " + str(python_type)) -if sys.version_info < (3, 9): - _list = (List[int], List[float], List[bool], List[str], List[bytes]) -else: - _list = (list[int], list[float], list[bool], list[str], list[bytes]) +_list = (list[int], list[float], list[bool], list[str], list[bytes]) def get_spark_type(return_type: Any) -> types.DataType: diff --git a/plugin_tests/h_spark/test_h_spark.py b/plugin_tests/h_spark/test_h_spark.py index 0fcb4456..8a36d5ab 100644 --- a/plugin_tests/h_spark/test_h_spark.py +++ b/plugin_tests/h_spark/test_h_spark.py @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -import sys import numpy as np import pandas as pd @@ -356,7 +355,6 @@ def test_get_spark_type_basic_types(return_type, expected_spark_type): # 2. Lists of basic Python types [email protected](sys.version_info < (3, 9), reason="requires python 3.9 or higher") @pytest.mark.parametrize( "return_type,expected_spark_type", [ diff --git a/tests/function_modifiers/test_adapters.py b/tests/function_modifiers/test_adapters.py index e95311a4..56052d5b 100644 --- a/tests/function_modifiers/test_adapters.py +++ b/tests/function_modifiers/test_adapters.py @@ -711,14 +711,8 @@ def test_load_from_with_multiple_inputs(): assert len(fg) == 5 -import sys - -if sys.version_info >= (3, 9): - dict_ = dict - tuple_ = tuple -else: - dict_ = Dict - tuple_ = Tuple +dict_ = dict +tuple_ = tuple # Mock functions for dataloader & datasaver testing @@ -770,10 +764,6 @@ def test_dl_validate_incorrect_functions(func): dl.validate(func) [email protected]( - sys.version_info < (3, 9, 0), - reason="dataloader not guarenteed to work with subscripted tuples on 3.8", -) def test_dl_validate_with_correct_function(): dl = dataloader() try: diff --git a/tests/function_modifiers/test_expanders.py b/tests/function_modifiers/test_expanders.py index ee9d1b17..bbc2fef2 100644 --- a/tests/function_modifiers/test_expanders.py +++ b/tests/function_modifiers/test_expanders.py @@ -38,10 +38,6 @@ from hamilton.node import DependencyType # TODO: Move/refactor for more general use skipif = pytest.mark.skipif -prior_to_py39 = { - "condition": sys.version_info < (3, 9, 0), - "reason": "Python 3.9+ required for this test", -} prior_to_py311 = { "condition": sys.version_info < (3, 11, 0), "reason": "Python 3.11+ required for this test", @@ -377,9 +373,9 @@ class MyDictInheritanceBadCase(TypedDict): ("MyDict", ()), ("MyDict", {"test2": str}), ("MyDictInheritance", {"test": InheritedObject}), - pytest.param("dict[str, int]", ("A", "B"), marks=skipif(**prior_to_py39)), - pytest.param("dict[str, int]", (["A", "B"]), marks=skipif(**prior_to_py39)), - pytest.param("dict", {"A": str, "B": int}, marks=skipif(**prior_to_py39)), + ("dict[str, int]", ("A", "B")), + ("dict[str, int]", (["A", "B"])), + ("dict", {"A": str, "B": int}), ], ) def test_extract_fields_valid_annotations_for_inferred_types(return_type_str, fields): @@ -408,10 +404,10 @@ def test_extract_fields_valid_annotations_for_inferred_types(return_type_str, fi ("pd.DataFrame", {"A": int}), ("MyDictBad", {"A": int}), ("MyDictInheritanceBadCase", {"A": SomeObject}), - pytest.param("dict", ("A", "B"), marks=skipif(**prior_to_py39)), - pytest.param("dict", (["A", "B"]), marks=skipif(**prior_to_py39)), - pytest.param("dict", (["A"]), marks=skipif(**prior_to_py39)), - pytest.param("dict", (["A", "B", "C"]), marks=skipif(**prior_to_py39)), + ("dict", ("A", "B")), + ("dict", (["A", "B"])), + ("dict", (["A"])), + ("dict", (["A", "B", "C"])), ], ) def test_extract_fields_invalid_annotations_for_inferred_types(return_type_str, fields): @@ -781,9 +777,9 @@ def test_unpack_fields_transform_on_indeterminate_tuple(): ("Tuple[int, int]", ("A", "B")), ("Tuple[int, int, str]", ("A", "B", "C")), ("Tuple[int, ...]", ("A", "B")), - pytest.param("tuple[int, int]", ("A", "B"), marks=skipif(**prior_to_py39)), - pytest.param("tuple[int, int, str]", ("A", "B", "C"), marks=skipif(**prior_to_py39)), - pytest.param("tuple[int, ...]", ("A", "B"), marks=skipif(**prior_to_py39)), + ("tuple[int, int]", ("A", "B")), + ("tuple[int, int, str]", ("A", "B", "C")), + ("tuple[int, ...]", ("A", "B")), ], ) def test_unpack_fields_valid_type_annotations(return_type_str, fields): @@ -807,11 +803,11 @@ def test_unpack_fields_valid_type_annotations(return_type_str, fields): pytest.param("Tuple[...]", ("A", "B", "C"), marks=skipif(**prior_to_py311)), pytest.param("Tuple[int, int, ...]", ("A", "B"), marks=skipif(**prior_to_py311)), pytest.param("Tuple[..., int, int]", ("A", "B"), marks=skipif(**prior_to_py311)), - pytest.param("tuple", ("A",), marks=skipif(**prior_to_py39)), - pytest.param("tuple[int, int]", ("A", "B", "C"), marks=skipif(**prior_to_py39)), - pytest.param("tuple[...]", ("A", "B", "C"), marks=skipif(**prior_to_py39)), - pytest.param("tuple[int, int, ...]", ("A", "B"), marks=skipif(**prior_to_py39)), - pytest.param("tuple[..., int, int]", ("A", "B"), marks=skipif(**prior_to_py39)), + ("tuple", ("A",)), + ("tuple[int, int]", ("A", "B", "C")), + ("tuple[...]", ("A", "B", "C")), + ("tuple[int, int, ...]", ("A", "B")), + ("tuple[..., int, int]", ("A", "B")), ], ) def test_unpack_fields_invalid_type_annotations(return_type_str, fields): @@ -1112,9 +1108,7 @@ def test_inject_misconfigured_param_type_dict(): annotation.validate(foo) [email protected](**prior_to_py39) def test_inject_misconfigured_param_untyped_generic_list(): - # NOTE: Stricter typing rules for generics were introduced in Python 3.9. def foo(x: List) -> int: return sum(x) diff --git a/tests/integrations/pandera/test_pandera_data_quality.py b/tests/integrations/pandera/test_pandera_data_quality.py index ef346776..ac1a91ed 100644 --- a/tests/integrations/pandera/test_pandera_data_quality.py +++ b/tests/integrations/pandera/test_pandera_data_quality.py @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -import sys import dask.dataframe as dd import numpy as np @@ -165,7 +164,6 @@ def test_pandera_decorator_fails_without_annotation(): h_pandera.check_output().get_validators(n) [email protected](sys.version_info < (3, 9), reason="requires python3.9 or higher") def test_pandera_decorator_dask_df(): """Validates that the function can be annotated with a dask dataframe type it'll work appropriately. @@ -216,7 +214,6 @@ def test_pandera_decorator_dask_df(): assert not result_success.passes [email protected](sys.version_info < (3, 9), reason="requires python3.9 or higher") @pytest.mark.xfail( reason="some weird import issue leads to key error in pandera, can't recreate outside of the series decorator" ) diff --git a/tests/plugins/test_dlt_extensions.py b/tests/plugins/test_dlt_extensions.py index fa899fc7..96795aae 100644 --- a/tests/plugins/test_dlt_extensions.py +++ b/tests/plugins/test_dlt_extensions.py @@ -15,24 +15,15 @@ # specific language governing permissions and limitations # under the License. -import sys from pathlib import Path -import pytest - -PY38_OR_BELOW = sys.version_info < (3, 9) -pytestmark = pytest.mark.skipif( - PY38_OR_BELOW, reason="Breaks for python 3.8 and below due to backports dependency." -) - -if not PY38_OR_BELOW: - import dlt - from dlt.destinations import filesystem - - from hamilton.plugins.dlt_extensions import DltDestinationSaver, DltResourceLoader - +import dlt import pandas as pd import pyarrow as pa +import pytest +from dlt.destinations import filesystem + +from hamilton.plugins.dlt_extensions import DltDestinationSaver, DltResourceLoader def pandas_df(): diff --git a/tests/plugins/test_huggingface_extensions.py b/tests/plugins/test_huggingface_extensions.py index 664ab122..7b52446a 100644 --- a/tests/plugins/test_huggingface_extensions.py +++ b/tests/plugins/test_huggingface_extensions.py @@ -16,11 +16,9 @@ # under the License. import pathlib -import sys import lancedb import numpy as np -import pytest from datasets import Dataset, DatasetDict from hamilton.plugins import huggingface_extensions @@ -59,7 +57,6 @@ def test_hfds_parquet_saver(tmp_path: pathlib.Path): assert saver.applies_to(Dataset) [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") def test_hfds_lancedb_saver(tmp_path: pathlib.Path): db_client = lancedb.connect(tmp_path / "lancedb") saver = huggingface_extensions.HuggingFaceDSLanceDBSaver(db_client, "test_table") diff --git a/tests/plugins/test_mlflow_extension.py b/tests/plugins/test_mlflow_extension.py index 8b89212b..d98d3ed4 100644 --- a/tests/plugins/test_mlflow_extension.py +++ b/tests/plugins/test_mlflow_extension.py @@ -15,27 +15,16 @@ # specific language governing permissions and limitations # under the License. -import sys from pathlib import Path -import pytest - -PY38_OR_BELOW = sys.version_info < (3, 9) -pytestmark = pytest.mark.skipif( - PY38_OR_BELOW, reason="Breaks for python 3.8 and below due to backports dependency." -) - -if not PY38_OR_BELOW: - import mlflow - - from hamilton.plugins.mlflow_extensions import MLFlowModelLoader, MLFlowModelSaver - +import mlflow import numpy as np import pytest from sklearn.base import BaseEstimator from sklearn.linear_model import LinearRegression from hamilton.io.materialization import from_, to +from hamilton.plugins.mlflow_extensions import MLFlowModelLoader, MLFlowModelSaver # TODO move these tests to `plugin_tests` because the required read-writes can get # complicated and tests are time consuming. diff --git a/tests/resources/nodes_with_future_annotation.py b/tests/resources/nodes_with_future_annotation.py index f74ede77..c577e80d 100644 --- a/tests/resources/nodes_with_future_annotation.py +++ b/tests/resources/nodes_with_future_annotation.py @@ -17,17 +17,11 @@ from __future__ import annotations -import sys -from typing import List, Tuple - from hamilton.function_modifiers import dataloader from hamilton.htypes import Collect, Parallelizable """Tests future annotations with common node types""" -tuple_ = Tuple if sys.version_info < (3, 9, 0) else tuple -list_ = List if sys.version_info < (3, 9, 0) else list - def parallelized() -> Parallelizable[int]: yield 1 @@ -44,6 +38,6 @@ def collected(standard: Collect[int]) -> int: @dataloader() -def sample_dataloader() -> tuple_[list_[str], dict]: +def sample_dataloader() -> tuple[list[str], dict]: """Grouping here as the rest test annotations""" return ["a", "b", "c"], {} diff --git a/tests/test_graph_types.py b/tests/test_graph_types.py index a152716f..a289a266 100644 --- a/tests/test_graph_types.py +++ b/tests/test_graph_types.py @@ -216,28 +216,24 @@ def test_json_serializable_dict(): json.dumps(hamilton_node.as_dict()) [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") def test_remove_docstring(func_a: str, func_a_docstring: str): func_a_no_whitespace = func_a.strip() stripped = graph_types._remove_docs_and_comments(func_a_docstring) assert func_a_no_whitespace == stripped [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") def test_remove_multiline(func_a: str, func_a_multiline: str): func_a_no_whitespace = func_a.strip() stripped = graph_types._remove_docs_and_comments(func_a_multiline) assert func_a_no_whitespace == stripped [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") def test_remove_comment(func_a: str, func_a_comment: str): func_a_no_whitespace = func_a.strip() stripped = graph_types._remove_docs_and_comments(func_a_comment) assert func_a_no_whitespace == stripped [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") @pytest.mark.parametrize("strip", [True, False]) def test_different_hash_function_body(func_a: str, func_a_body: str, strip: bool): """Gives different hash for different function body""" @@ -246,7 +242,6 @@ def test_different_hash_function_body(func_a: str, func_a_body: str, strip: bool assert func_a_hash != func_a_body_hash [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") @pytest.mark.parametrize("strip", [True, False]) def test_different_hash_docstring(func_a: str, func_a_docstring: str, strip: bool): """Same hash if strip docstring, else different hash""" @@ -255,7 +250,6 @@ def test_different_hash_docstring(func_a: str, func_a_docstring: str, strip: boo assert (func_a_hash == func_a_docstring_hash) is (True if strip else False) [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") @pytest.mark.parametrize("strip", [True, False]) def test_different_hash_multiline_docstring(func_a: str, func_a_multiline: str, strip: bool): """Same hash if strip multiline docstring, else different hash""" @@ -264,7 +258,6 @@ def test_different_hash_multiline_docstring(func_a: str, func_a_multiline: str, assert (func_a_hash == func_a_multiline_hash) is (True if strip else False) [email protected](sys.version_info < (3, 9), reason="requires Python 3.9 or higher") @pytest.mark.parametrize("strip", [True, False]) def test_different_hash_comment(func_a: str, func_a_comment: str, strip: bool): """Same hash if strip comment, else different hash""" diff --git a/tests/test_type_utils.py b/tests/test_type_utils.py index a49e47e1..5ff1cb13 100644 --- a/tests/test_type_utils.py +++ b/tests/test_type_utils.py @@ -16,9 +16,8 @@ # under the License. import collections -import sys import typing -from typing import Any, Dict, List, Union +from typing import Annotated, Any, Dict, List, Union import pandas as pd import pytest @@ -26,11 +25,6 @@ import pytest from hamilton import htypes from hamilton.htypes import check_instance -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated - class X: pass @@ -308,24 +302,12 @@ def test_check_input_type_match(node_type, input_value): assert actual is True -# We cannot parameterize this as the parameterization cannot be -# included if the [email protected]( - sys.version_info < (3, 9, 0), - reason="Type hinting generics in standard collections " "is only supported in 3.9+", -) def test_check_input_types_subscripted_generics_dict_str_Any(): """Tests check_input_type of SimplePythonDataFrameGraphAdapter""" actual = htypes.check_input_type(dict[str, typing.Any], {}) assert actual is True -# We cannot parameterize this as the parameterization cannot be -# included if the [email protected]( - sys.version_info < (3, 9, 0), - reason="Type hinting generics in standard collections " "is only supported in 3.9+", -) def test_check_input_types_subscripted_generics_list_Any(): """Tests check_input_type of SimplePythonDataFrameGraphAdapter""" actual = htypes.check_input_type(list[typing.Any], []) @@ -340,10 +322,8 @@ def test_check_instance_with_non_generic_type(): def test_check_instance_with_generic_list_type(): assert check_instance([1, 2, 3], List[int]) assert not check_instance([1, 2, "3"], List[int]) - if sys.version_info >= (3, 9): - # skip 3.8 -- not worth fixing - assert check_instance([1, 2, 3], List) - assert check_instance([1, 2, "3"], List) + assert check_instance([1, 2, 3], List) + assert check_instance([1, 2, "3"], List) def test_check_instance_with_list_type(): @@ -354,10 +334,8 @@ def test_check_instance_with_list_type(): def test_check_instance_with_generic_dict_type(): assert check_instance({"key1": 1, "key2": 2}, Dict[str, int]) assert not check_instance({"key1": 1, "key2": "2"}, Dict[str, int]) - if sys.version_info >= (3, 9): - # skip 3.8 -- not worth fixing - assert check_instance({"key1": 1, "key2": 2}, Dict) - assert check_instance({"key1": 1, "key2": "2"}, Dict) + assert check_instance({"key1": 1, "key2": 2}, Dict) + assert check_instance({"key1": 1, "key2": "2"}, Dict) def test_check_instance_with_dict_type(): @@ -391,7 +369,6 @@ def test_check_instance_with_union_type(): assert not check_instance({"key1": 1, "key2": 2}, Union[int, str]) [email protected](sys.version_info < (3, 9), reason="requires python 3.9 or higher") def test_check_instance_with_union_type_and_literal(): from typing import Literal @@ -400,7 +377,6 @@ def test_check_instance_with_union_type_and_literal(): assert not check_instance("c", Union[Literal["a"], Literal["b"]]) [email protected](sys.version_info < (3, 9), reason="requires python 3.9 or higher") def test_non_generic_dict_and_list(): assert check_instance([1, 2, 3], list[int]) assert not check_instance([1, 2, "3"], list[int])
