Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/hamilton-lsp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
python-version: ['3.10', '3.11', '3.12', '3.13']
defaults:
run:
working-directory: dev_tools/language_server
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/hamilton-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ jobs:
os:
- ubuntu-latest
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
env:
UV_PRERELEASE: "allow"
HAMILTON_TELEMETRY_ENABLED: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/hamilton-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
python-version: ['3.10', '3.11', '3.12', '3.13']
defaults:
run:
working-directory: ui/sdk
Expand Down
8 changes: 1 addition & 7 deletions hamilton/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@
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

Expand Down
6 changes: 1 addition & 5 deletions hamilton/htypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,)
Expand Down
8 changes: 4 additions & 4 deletions hamilton/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ def __init__(
# assume optional values passed
self._default_parameter_values = optional_values if optional_values else {}
else:
# TODO -- remove this when we no longer support 3.8 -- 10/14/2024
type_hint_kwargs = {} if sys.version_info < (3, 9) else {"include_extras": True}
type_hint_kwargs: dict[str, Any] = {"include_extras": True}
if sys.version_info >= (3, 13):
type_hint_kwargs["globalns"] = callabl.__globals__
input_types = typing.get_type_hints(callabl, **type_hint_kwargs)
signature = inspect.signature(callabl)
for key, value in signature.parameters.items():
Expand Down Expand Up @@ -291,8 +292,7 @@ def from_fn(fn: Callable, name: str = None) -> "Node":
"""
if name is None:
name = fn.__name__
# TODO -- remove this when we no longer support 3.8 -- 10/14/2024
type_hint_kwargs = {} if sys.version_info < (3, 9) else {"include_extras": True}
type_hint_kwargs = {"include_extras": True}
return_type = typing.get_type_hints(fn, **type_hint_kwargs).get("return")
if return_type is None:
raise ValueError(f"Missing type hint for return value in function {fn.__qualname__}.")
Expand Down
6 changes: 1 addition & 5 deletions hamilton/plugins/h_spark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 0 additions & 2 deletions plugin_tests/h_spark/test_h_spark.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.

import sys

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -356,7 +355,6 @@ def test_get_spark_type_basic_types(return_type, expected_spark_type):


# 2. Lists of basic Python types
@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 or higher")
@pytest.mark.parametrize(
"return_type,expected_spark_type",
[
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ version = "1.89.0" # NOTE: keep this in sync with hamilton/version.py
# dynamic = ["version"]
description = "Hamilton, the micro-framework for creating dataframes."
readme = "README.md"
requires-python = ">=3.8.1, <4"
requires-python = ">=3.10.1, <4"
license = {text = "Apache-2.0"}
keywords = ["hamilton"]
authors = [
Expand All @@ -39,11 +39,10 @@ classifiers = [
"Natural Language :: English",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13"
]
dependencies = [
"numpy",
Expand Down
14 changes: 2 additions & 12 deletions tests/function_modifiers/test_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,14 +711,8 @@ def fn(data1: dict, data2: dict) -> dict:
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
Expand Down Expand Up @@ -770,10 +764,6 @@ def test_dl_validate_incorrect_functions(func):
dl.validate(func)


@pytest.mark.skipif(
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:
Expand Down
36 changes: 15 additions & 21 deletions tests/function_modifiers/test_expanders.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@

# 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",
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -408,10 +404,10 @@ def function() -> return_type: # type: ignore
("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):
Expand Down Expand Up @@ -781,9 +777,9 @@ def dummy() -> Tuple[int, ...]:
("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):
Expand All @@ -807,11 +803,11 @@ def function() -> return_type:
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):
Expand Down Expand Up @@ -1112,9 +1108,7 @@ def foo(x: int) -> int:
annotation.validate(foo)


@pytest.mark.skipif(**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)

Expand Down
3 changes: 0 additions & 3 deletions tests/integrations/pandera/test_pandera_data_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.

import sys

import dask.dataframe as dd
import numpy as np
Expand Down Expand Up @@ -165,7 +164,6 @@ def foo() -> pd.DataFrame:
h_pandera.check_output().get_validators(n)


@pytest.mark.skipif(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.

Expand Down Expand Up @@ -216,7 +214,6 @@ def foo(fail: bool = False) -> dd.DataFrame:
assert not result_success.passes


@pytest.mark.skipif(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"
)
Expand Down
19 changes: 5 additions & 14 deletions tests/plugins/test_dlt_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
3 changes: 0 additions & 3 deletions tests/plugins/test_huggingface_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -59,7 +57,6 @@ def test_hfds_parquet_saver(tmp_path: pathlib.Path):
assert saver.applies_to(Dataset)


@pytest.mark.skipif(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")
Expand Down
15 changes: 2 additions & 13 deletions tests/plugins/test_mlflow_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 1 addition & 7 deletions tests/resources/nodes_with_future_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"], {}
Loading
Loading