This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new fcd5ab4 [CI] Phase out Python 3.8 wheel builds (#633)
fcd5ab4 is described below
commit fcd5ab4f5bd35ed3f36e547f34f7330534fe99dc
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Jun 18 14:04:41 2026 -0400
[CI] Phase out Python 3.8 wheel builds (#633)
cibuildwheel 4.x no longer accepts Python 3.8 build selectors, so the
wheel build configuration needs to start at Python 3.9.
This PR:
- raises the package Python floor to 3.9
- removes cp38 from cibuildwheel build and test-skip selectors
- updates the CI test matrix Python 3.8 leg to Python 3.9
---
.github/actions/build-wheel-for-publish/action.yml | 8 -
.github/workflows/ci_test.yml | 2 +-
pyproject.toml | 9 +-
python/tvm_ffi/container.py | 52 ++----
python/tvm_ffi/cpp/dtype.py | 2 +-
python/tvm_ffi/cpp/nvrtc.py | 2 +-
python/tvm_ffi/registry.py | 3 +-
python/tvm_ffi/stub/file_utils.py | 3 +-
python/tvm_ffi/stub/lib_state.py | 4 +-
python/tvm_ffi/testing/testing.py | 4 -
python/tvm_ffi/utils/kwargs_wrapper.py | 3 +-
tests/python/test_container.py | 10 +-
tests/python/test_dataclass_common.py | 2 +-
tests/python/test_dataclass_copy.py | 2 +-
tests/python/test_dataclass_frozen.py | 14 +-
tests/python/test_dataclass_py_class.py | 2 +-
tests/python/test_object.py | 3 +-
tests/python/test_type_converter.py | 208 +--------------------
18 files changed, 43 insertions(+), 290 deletions(-)
diff --git a/.github/actions/build-wheel-for-publish/action.yml
b/.github/actions/build-wheel-for-publish/action.yml
index d5adb6a..f51808c 100644
--- a/.github/actions/build-wheel-for-publish/action.yml
+++ b/.github/actions/build-wheel-for-publish/action.yml
@@ -45,14 +45,6 @@ inputs:
runs:
using: "composite"
steps:
- # Special handling for macOS arm64 + python 3.8.
- # Install Python 3.8 from `actions/setup-python`, which is an arm64 build.
- - name: Install Python 3.8 for macOS arm64
- if: runner.os == 'macOS' && inputs.arch == 'arm64'
- uses: actions/setup-python@v5
- with:
- python-version: 3.8
-
- name: Set up uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml
index de193aa..b63c965 100644
--- a/.github/workflows/ci_test.yml
+++ b/.github/workflows/ci_test.yml
@@ -131,7 +131,7 @@ jobs:
matrix:
include:
- {os: ubuntu-latest, arch: x86_64, python_version: '3.14t'}
- - {os: ubuntu-24.04-arm, arch: aarch64, python_version: '3.8'}
+ - {os: ubuntu-24.04-arm, arch: aarch64, python_version: '3.14'}
- {os: windows-latest, arch: AMD64, python_version: '3.9'}
- {os: macos-14, arch: arm64, python_version: '3.13'}
diff --git a/pyproject.toml b/pyproject.toml
index 8048be2..3237de4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,7 @@ classifiers = [
"Intended Audience :: Science/Research",
]
keywords = ["machine learning", "inference"]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
dependencies = ["typing-extensions>=4.5"]
[project.urls]
@@ -181,7 +181,7 @@ testpaths = ["tests"]
include = ["python/**/*.py", "tests/**/*.py"]
line-length = 100
indent-width = 4
-target-version = "py38"
+target-version = "py39"
[tool.ruff.lint]
select = [
@@ -235,10 +235,10 @@ build-verbosity = 1
# only build up to cp312, cp312
# will be abi3 and can be used in future versions
# ship 314t threaded nogil version
-build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp314t-*"]
+build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp314t-*"]
skip = ["*musllinux*"]
# we only need to test on cp312
-test-skip = ["cp38-*", "cp39-*", "cp310-*", "cp311-*"]
+test-skip = ["cp39-*", "cp310-*", "cp311-*"]
# focus on testing abi3 wheel
build-frontend = "build[uv]"
test-command = "pytest {package}/tests/python -vvs"
@@ -267,6 +267,7 @@ unused-ignore-comment = "ignore"
[tool.ty.analysis]
allowed-unresolved-imports = [
+ "tvm_ffi._version",
"torch",
"torch.*",
"torch.utils.*",
diff --git a/python/tvm_ffi/container.py b/python/tvm_ffi/container.py
index c7e1df6..1e942b8 100644
--- a/python/tvm_ffi/container.py
+++ b/python/tvm_ffi/container.py
@@ -20,7 +20,17 @@ from __future__ import annotations
import itertools
import operator
-import sys
+from collections.abc import ItemsView as ItemsViewBase
+from collections.abc import (
+ Iterable,
+ Iterator,
+ Mapping,
+ MutableMapping,
+ MutableSequence,
+ Sequence,
+)
+from collections.abc import KeysView as KeysViewBase
+from collections.abc import ValuesView as ValuesViewBase
from typing import (
Any,
Callable,
@@ -33,46 +43,6 @@ from typing import (
from . import _ffi_api, core
from .registry import register_object
-if sys.version_info >= (3, 9):
- # PEP 585 generics
- from collections.abc import (
- ItemsView as ItemsViewBase,
- )
- from collections.abc import (
- Iterable,
- Iterator,
- Mapping,
- MutableMapping,
- MutableSequence,
- Sequence,
- )
- from collections.abc import (
- KeysView as KeysViewBase,
- )
- from collections.abc import (
- ValuesView as ValuesViewBase,
- )
-else: # Python 3.8
- # workarounds for python 3.8
- # typing-module generics (subscriptable on 3.8)
- from typing import (
- ItemsView as ItemsViewBase,
- )
- from typing import (
- Iterable,
- Iterator,
- Mapping,
- MutableMapping,
- MutableSequence,
- Sequence,
- )
- from typing import (
- KeysView as KeysViewBase,
- )
- from typing import (
- ValuesView as ValuesViewBase,
- )
-
__all__ = ["Array", "Dict", "List", "Map"]
diff --git a/python/tvm_ffi/cpp/dtype.py b/python/tvm_ffi/cpp/dtype.py
index 0198110..c35ba5a 100644
--- a/python/tvm_ffi/cpp/dtype.py
+++ b/python/tvm_ffi/cpp/dtype.py
@@ -59,7 +59,7 @@ ROCM_DTYPE_MAP = {
}
[email protected]_cache(maxsize=None)
[email protected]
def _determine_backend_once() -> Literal["cpu", "cuda", "rocm"]:
try:
import torch # noqa: PLC0415
diff --git a/python/tvm_ffi/cpp/nvrtc.py b/python/tvm_ffi/cpp/nvrtc.py
index 73f76d7..1271d15 100644
--- a/python/tvm_ffi/cpp/nvrtc.py
+++ b/python/tvm_ffi/cpp/nvrtc.py
@@ -18,7 +18,7 @@
from __future__ import annotations
-from typing import Sequence
+from collections.abc import Sequence
def nvrtc_compile( # noqa: PLR0912, PLR0915
diff --git a/python/tvm_ffi/registry.py b/python/tvm_ffi/registry.py
index 07c24ee..0e28a59 100644
--- a/python/tvm_ffi/registry.py
+++ b/python/tvm_ffi/registry.py
@@ -21,7 +21,8 @@ from __future__ import annotations
import json
import sys
import warnings
-from typing import Any, Callable, Literal, Sequence, TypeVar, overload
+from collections.abc import Sequence
+from typing import Any, Callable, Literal, TypeVar, overload
from . import core
from .core import Function, TypeInfo
diff --git a/python/tvm_ffi/stub/file_utils.py
b/python/tvm_ffi/stub/file_utils.py
index b409f61..eb6cc8c 100644
--- a/python/tvm_ffi/stub/file_utils.py
+++ b/python/tvm_ffi/stub/file_utils.py
@@ -22,8 +22,9 @@ import dataclasses
import difflib
import os
import traceback
+from collections.abc import Generator, Iterable
from pathlib import Path
-from typing import Callable, Generator, Iterable
+from typing import Callable
from . import consts as C
diff --git a/python/tvm_ffi/stub/lib_state.py b/python/tvm_ffi/stub/lib_state.py
index ea0f0bc..f293651 100644
--- a/python/tvm_ffi/stub/lib_state.py
+++ b/python/tvm_ffi/stub/lib_state.py
@@ -30,7 +30,7 @@ from . import consts as C
from .utils import FuncInfo, NamedTypeSchema, ObjectInfo
[email protected]_cache(maxsize=None)
[email protected]
def object_info_from_type_key(type_key: str) -> ObjectInfo:
"""Construct an `ObjectInfo` from an object type key."""
type_info = _lookup_or_register_type_info_from_type_key(str(type_key))
@@ -108,7 +108,7 @@ def toposort_objects(type_keys: list[str]) ->
list[ObjectInfo]:
return [infos[type_key] for type_key in sorted_keys]
[email protected]_cache(maxsize=None)
[email protected]
def _func_info_from_global_name(name: str) -> FuncInfo:
"""Construct a `FuncInfo` from a global function name."""
return FuncInfo(
diff --git a/python/tvm_ffi/testing/testing.py
b/python/tvm_ffi/testing/testing.py
index c5b773a..4c6c632 100644
--- a/python/tvm_ffi/testing/testing.py
+++ b/python/tvm_ffi/testing/testing.py
@@ -41,10 +41,6 @@ from tvm_ffi.dataclasses import c_class
from .. import _ffi_api
from .. import core as tvm_ffi_core
-requires_py39 = pytest.mark.skipif(
- sys.version_info < (3, 9),
- reason="requires Python 3.9+",
-)
requires_py310 = pytest.mark.skipif(
sys.version_info < (3, 10),
reason="requires Python 3.10+",
diff --git a/python/tvm_ffi/utils/kwargs_wrapper.py
b/python/tvm_ffi/utils/kwargs_wrapper.py
index 1bad61c..08e7476 100644
--- a/python/tvm_ffi/utils/kwargs_wrapper.py
+++ b/python/tvm_ffi/utils/kwargs_wrapper.py
@@ -25,7 +25,8 @@ from __future__ import annotations
import functools
import inspect
import keyword
-from typing import Any, Callable, Iterable
+from collections.abc import Iterable
+from typing import Any, Callable
from tvm_ffi.utils.unpack_dataclass import unpack_dataclass_to_tuple
diff --git a/tests/python/test_container.py b/tests/python/test_container.py
index 82b2cfa..fd4ea8d 100644
--- a/tests/python/test_container.py
+++ b/tests/python/test_container.py
@@ -17,7 +17,7 @@
from __future__ import annotations
import pickle
-import sys
+from collections.abc import Sequence
from typing import Any
import pytest
@@ -26,14 +26,6 @@ from tvm_ffi import testing
from tvm_ffi.container import MISSING as CONTAINER_MISSING
from tvm_ffi.core import MISSING
-if sys.version_info >= (3, 9):
- # PEP 585 generics
- from collections.abc import Sequence
-else: # Python 3.8
- # workarounds for python 3.8
- # typing-module generics (subscriptable on 3.8)
- from typing import Sequence
-
def test_array() -> None:
a = tvm_ffi.convert([1, 2, 3])
diff --git a/tests/python/test_dataclass_common.py
b/tests/python/test_dataclass_common.py
index 238886e..8de6337 100644
--- a/tests/python/test_dataclass_common.py
+++ b/tests/python/test_dataclass_common.py
@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# ruff: noqa: D102, UP006, UP045
+# ruff: noqa: D102, UP006, UP035, UP045
"""Tests for :func:`tvm_ffi.dataclasses.is_dataclass`, :func:`fields`,
:func:`replace`."""
from __future__ import annotations
diff --git a/tests/python/test_dataclass_copy.py
b/tests/python/test_dataclass_copy.py
index 8324ee4..f945b39 100644
--- a/tests/python/test_dataclass_copy.py
+++ b/tests/python/test_dataclass_copy.py
@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# ruff: noqa: D102, UP006, UP045
+# ruff: noqa: D102, UP006, UP035, UP045
"""Tests for __copy__, __deepcopy__, and __replace__ on FFI objects."""
from __future__ import annotations
diff --git a/tests/python/test_dataclass_frozen.py
b/tests/python/test_dataclass_frozen.py
index 9e2a2fd..4d28ef2 100644
--- a/tests/python/test_dataclass_frozen.py
+++ b/tests/python/test_dataclass_frozen.py
@@ -16,7 +16,7 @@
# under the License.
"""Tests for frozen support in ``@py_class``."""
-# ruff: noqa: D102
+# ruff: noqa: D102, UP006, UP035, UP045
from __future__ import annotations
import copy
@@ -95,7 +95,7 @@ class TestFrozenFieldTypes:
def test_frozen_optional_field(self) -> None:
@py_class(_unique_key("opt_field"), frozen=True)
class Opt(Object):
- v: Optional[int] # noqa: UP045
+ v: Optional[int]
o1 = Opt(None)
assert o1.v is None
@@ -122,7 +122,7 @@ class TestFrozenFieldTypes:
def test_frozen_list_field(self) -> None:
@py_class(_unique_key("list_field"), frozen=True)
class HasList(Object):
- items: List[int] # noqa: UP006
+ items: List[int]
h = HasList([1, 2, 3])
assert len(h.items) == 3
@@ -132,7 +132,7 @@ class TestFrozenFieldTypes:
def test_frozen_dict_field(self) -> None:
@py_class(_unique_key("dict_field"), frozen=True)
class HasDict(Object):
- mapping: Dict[str, int] # noqa: UP006
+ mapping: Dict[str, int]
h = HasDict({"a": 1})
assert h.mapping["a"] == 1
@@ -157,7 +157,7 @@ class TestFrozenDefaults:
def test_frozen_field_with_default_factory(self) -> None:
@py_class(_unique_key("def_factory"), frozen=True)
class Cfg(Object):
- items: List[int] = field(default_factory=list) # noqa: UP006
+ items: List[int] = field(default_factory=list)
c = Cfg()
assert len(c.items) == 0
@@ -252,7 +252,7 @@ class TestEscapeHatch:
def test_escape_hatch_preserves_type_coercion(self) -> None:
@py_class(_unique_key("esc_coerce"), frozen=True)
class HasList(Object):
- items: List[int] # noqa: UP006
+ items: List[int]
h = HasList([1])
type(h).items.set(h, [10, 20]) # ty: ignore[unresolved-attribute]
@@ -610,7 +610,7 @@ class TestFrozenEdgeCases:
def test_frozen_field_with_none_default(self) -> None:
@py_class(_unique_key("none_def"), frozen=True)
class Opt(Object):
- v: Optional[int] = None # noqa: UP045
+ v: Optional[int] = None
o = Opt()
assert o.v is None
diff --git a/tests/python/test_dataclass_py_class.py
b/tests/python/test_dataclass_py_class.py
index 423965e..5a43614 100644
--- a/tests/python/test_dataclass_py_class.py
+++ b/tests/python/test_dataclass_py_class.py
@@ -16,7 +16,7 @@
# under the License.
"""Tests for Python-defined TVM-FFI types: ``@py_class`` decorator and
low-level Field API."""
-# ruff: noqa: D102, PLR0124, PLW1641, UP006, UP045
+# ruff: noqa: D102, PLR0124, PLW1641, UP006, UP035, UP045
from __future__ import annotations
import copy
diff --git a/tests/python/test_object.py b/tests/python/test_object.py
index 9ad7b3c..23df2d5 100644
--- a/tests/python/test_object.py
+++ b/tests/python/test_object.py
@@ -17,7 +17,8 @@
from __future__ import annotations
import sys
-from typing import Any, Sequence
+from collections.abc import Sequence
+from typing import Any
import pytest
import tvm_ffi
diff --git a/tests/python/test_type_converter.py
b/tests/python/test_type_converter.py
index 6adef75..82a0d7f 100644
--- a/tests/python/test_type_converter.py
+++ b/tests/python/test_type_converter.py
@@ -22,10 +22,10 @@ import collections.abc
import ctypes
import itertools
import os
-import sys
import typing
+from collections.abc import Iterator
from numbers import Integral
-from typing import Callable, Iterator, Optional, Union
+from typing import Callable, Optional, Union
import pytest
import tvm_ffi
@@ -38,10 +38,6 @@ from tvm_ffi.core import (
_to_py_class_value,
)
from tvm_ffi.dataclasses import IntEnum, StrEnum, entry
-
-# Python 3.9+ supports list[int], dict[str, int], tuple[int, ...] at runtime.
-# On 3.8, these raise TypeError("'type' object is not subscriptable").
-_PY39 = sys.version_info >= (3, 9)
from tvm_ffi.testing import (
TestIntPair,
TestObjectBase,
@@ -50,7 +46,7 @@ from tvm_ffi.testing import (
_TestCxxClassDerived,
_TestCxxClassDerivedDerived,
)
-from tvm_ffi.testing.testing import requires_py39, requires_py310
+from tvm_ffi.testing.testing import requires_py310
# ---------------------------------------------------------------------------
# Helpers
@@ -375,39 +371,32 @@ class TestUnion:
# Category 9: Containers
# ---------------------------------------------------------------------------
class TestContainers:
- @requires_py39
def test_array_list_pass(self) -> None:
"""Test array list pass."""
A(tuple[int, ...]).check_value([1, 2, 3])
- @requires_py39
def test_array_tuple_pass(self) -> None:
"""Test array tuple pass."""
A(tuple[int, ...]).check_value((1, 2, 3))
- @requires_py39
def test_array_wrong_element(self) -> None:
"""Test array wrong element."""
with pytest.raises(TypeError, match=r"element \[1\].*expected int"):
A(tuple[int, ...]).check_value([1, "x"])
- @requires_py39
def test_array_empty_pass(self) -> None:
"""Test array empty pass."""
A(tuple[int, ...]).check_value([])
- @requires_py39
def test_array_any_pass(self) -> None:
"""Test array any pass."""
A(tuple[typing.Any, ...]).check_value([1, "x", None])
- @requires_py39
def test_array_wrong_container_type(self) -> None:
"""Test array wrong container type."""
with pytest.raises(TypeError, match="expected Array"):
A(tuple[int, ...]).check_value(42)
- @requires_py39
def test_array_rejects_generator(self) -> None:
"""Generators are not accepted by Array schemas."""
@@ -418,51 +407,42 @@ class TestContainers:
with pytest.raises(TypeError, match="expected Array"):
A(tuple[int, ...]).check_value(gen())
- @requires_py39
def test_array_rejects_string(self) -> None:
"""Strings are not accepted by Array schemas."""
with pytest.raises(TypeError, match="expected Array"):
A(tuple[int, ...]).check_value("hello")
- @requires_py39
def test_list_pass(self) -> None:
"""Test list pass."""
A(list[str]).check_value(["a", "b"])
- @requires_py39
def test_map_pass(self) -> None:
"""Test map pass."""
A(tvm_ffi.Map[str, int]).check_value({"a": 1, "b": 2})
- @requires_py39
def test_map_wrong_key(self) -> None:
"""Test map wrong key."""
with pytest.raises(TypeError, match="expected str"):
A(tvm_ffi.Map[str, int]).check_value({1: 2})
- @requires_py39
def test_map_wrong_value(self) -> None:
"""Test map wrong value."""
with pytest.raises(TypeError, match="expected int"):
A(tvm_ffi.Map[str, int]).check_value({"a": "b"})
- @requires_py39
def test_map_empty_pass(self) -> None:
"""Test map empty pass."""
A(tvm_ffi.Map[str, int]).check_value({})
- @requires_py39
def test_dict_pass(self) -> None:
"""Test dict pass."""
A(dict[str, int]).check_value({"a": 1})
- @requires_py39
def test_map_wrong_container(self) -> None:
"""Test map wrong container."""
with pytest.raises(TypeError, match="expected Map"):
A(tvm_ffi.Map[str, int]).check_value([1, 2])
- @requires_py39
def test_map_rejects_non_mapping_pairs(self) -> None:
"""Lists of pairs are not accepted by Map schemas."""
with pytest.raises(TypeError, match="expected Map"):
@@ -473,23 +453,19 @@ class TestContainers:
# Category 9: Nested types
# ---------------------------------------------------------------------------
class TestNestedTypes:
- @requires_py39
def test_array_optional_int(self) -> None:
"""Test array optional int."""
A(tuple[Optional[int], ...]).check_value([1, None, 2])
- @requires_py39
def test_map_str_array_int(self) -> None:
"""Test map str array int."""
A(tvm_ffi.Map[str, tuple[int, ...]]).check_value({"a": [1, 2]})
- @requires_py39
def test_map_str_array_int_nested_fail(self) -> None:
"""Test map str array int nested fail."""
with pytest.raises(TypeError, match="expected int"):
A(tvm_ffi.Map[str, tuple[int, ...]]).check_value({"a": [1, "x"]})
- @requires_py39
def test_union_with_containers(self) -> None:
"""Test union with containers."""
schema = A(Union[int, tuple[str, ...]])
@@ -575,13 +551,11 @@ class TestErrorMessages:
with pytest.raises(TypeError, match=r"expected int, got str"):
A(int).check_value("hello")
- @requires_py39
def test_nested_array_error(self) -> None:
"""Test nested array error."""
with pytest.raises(TypeError, match=r"element \[2\].*expected int, got
str"):
A(tuple[int, ...]).check_value([1, 2, "x"])
- @requires_py39
def test_nested_map_error(self) -> None:
"""Test nested map error."""
with pytest.raises(TypeError, match=r"value for key 'b'.*expected int,
got str"):
@@ -673,7 +647,6 @@ class TestEdgeCases:
"""Test bytearray passes bytes."""
A(bytes).check_value(bytearray(b"data"))
- @requires_py39
def test_tuple_passes_array(self) -> None:
"""Tuple is accepted as a sequence type for Array."""
A(tuple[int, ...]).check_value((1, 2, 3))
@@ -701,7 +674,6 @@ class TestEdgeCases:
with pytest.raises(TypeError, match="expected int"):
A(int).check_value("hello")
- @requires_py39
def test_tuple_type_schema(self) -> None:
"""Test tuple type schema."""
schema = A(tuple[int, str])
@@ -890,27 +862,23 @@ class TestConvertSpecialTypes:
# Category 17: Container conversion results
# ---------------------------------------------------------------------------
class TestConvertContainers:
- @requires_py39
def test_array_converts_bool_elements_to_int(self) -> None:
"""Array[int] with bool elements converts them to int."""
result = _to_py_class_value(A(tuple[int, ...]).convert([True, False,
1]))
assert list(result) == [1, 0, 1]
assert all(type(x) is int for x in result)
- @requires_py39
def test_array_int_passthrough(self) -> None:
"""Array[int] with int elements returns ffi.Array."""
result = _to_py_class_value(A(tuple[int, ...]).convert([1, 2, 3]))
assert list(result) == [1, 2, 3]
- @requires_py39
def test_array_any_passthrough(self) -> None:
"""Array[Any] wraps into ffi.Array."""
original = [1, "x", None]
result = _to_py_class_value(A(tuple[typing.Any,
...]).convert(original))
assert isinstance(result, tvm_ffi.Array)
- @requires_py39
def test_map_converts_values(self) -> None:
"""Map[str, float] converts int values to float."""
result = _to_py_class_value(A(tvm_ffi.Map[str, float]).convert({"a":
1, "b": 2}))
@@ -920,33 +888,28 @@ class TestConvertContainers:
assert result["a"] == 1.0
assert result["b"] == 2.0
- @requires_py39
def test_map_any_float_converts_values(self) -> None:
"""Map[Any, float] still converts values when keys are Any."""
result = _to_py_class_value(A(tvm_ffi.Map[typing.Any,
float]).convert({"a": 1, "b": 2}))
assert isinstance(result, tvm_ffi.Map)
assert type(result["a"]) is float
- @requires_py39
def test_map_any_any_passthrough(self) -> None:
"""Map[Any, Any] wraps into ffi.Map."""
original = {"a": 1}
result = _to_py_class_value(A(tvm_ffi.Map[typing.Any,
typing.Any]).convert(original))
assert isinstance(result, tvm_ffi.Map)
- @requires_py39
def test_map_empty_dict_convert(self) -> None:
"""Empty dict converts to Map[str, int]."""
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert({}))
assert len(result) == 0
- @requires_py39
def test_dict_empty_dict_convert(self) -> None:
"""Empty dict converts to Dict[str, int]."""
result = _to_py_class_value(A(dict[str, int]).convert({}))
assert len(result) == 0
- @requires_py39
def test_tuple_converts_elements(self) -> None:
"""tuple[int, float] converts elements positionally."""
result = _to_py_class_value(A(tuple[int, float]).convert((True, 42)))
@@ -954,7 +917,6 @@ class TestConvertContainers:
assert type(result[0]) is int
assert type(result[1]) is float
- @requires_py39
def test_nested_array_in_map(self) -> None:
"""Map[str, Array[int]] recursively converts elements."""
result = _to_py_class_value(
@@ -964,7 +926,6 @@ class TestConvertContainers:
assert list(result["a"]) == [1, 0]
assert all(type(x) is int for x in result["a"])
- @requires_py39
def test_array_optional_int_all_none(self) -> None:
"""Array[Optional[int]] accepts an all-None payload."""
result = _to_py_class_value(A(tuple[Optional[int],
...]).convert([None, None, None]))
@@ -1024,19 +985,16 @@ class TestConvertRejections:
with pytest.raises(TypeError, match="expected str, got int"):
A(str).convert(42)
- @requires_py39
def test_array_rejects_wrong_element(self) -> None:
"""Test array rejects wrong element."""
with pytest.raises(TypeError, match=r"element \[1\].*expected int, got
str"):
A(tuple[int, ...]).convert([1, "x"])
- @requires_py39
def test_map_rejects_wrong_value(self) -> None:
"""Test map rejects wrong value."""
with pytest.raises(TypeError, match=r"value for key 'a'.*expected int,
got str"):
A(tvm_ffi.Map[str, int]).convert({"a": "x"})
- @requires_py39
def test_tuple_rejects_wrong_length(self) -> None:
"""Test tuple rejects wrong length."""
with pytest.raises(TypeError, match=r"expected tuple of length 2"):
@@ -1078,7 +1036,6 @@ class TestConvertNumpy:
# Category 21: Array nested with Optional/Union (inner conversion)
# ---------------------------------------------------------------------------
class TestNestedArrayComposite:
- @requires_py39
def test_array_optional_float_with_bool(self) -> None:
"""Array[Optional[float]] converts bool elements to float."""
result = _to_py_class_value(A(tuple[Optional[float],
...]).convert([True, None, 3]))
@@ -1087,7 +1044,6 @@ class TestNestedArrayComposite:
assert result[1] is None
assert type(result[2]) is float
- @requires_py39
def test_array_optional_int_with_bool(self) -> None:
"""Array[Optional[int]] converts bool elements to int."""
result = _to_py_class_value(A(tuple[Optional[int],
...]).convert([True, None, 2]))
@@ -1095,7 +1051,6 @@ class TestNestedArrayComposite:
assert type(result[0]) is int
assert result[1] is None
- @requires_py39
def test_array_union_int_str_with_bool(self) -> None:
"""Array[Union[int, str]] converts bool via int alternative."""
result = _to_py_class_value(A(tuple[Union[int, str],
...]).convert([True, "hello", False]))
@@ -1104,7 +1059,6 @@ class TestNestedArrayComposite:
assert type(result[1]) is str
assert type(result[2]) is int
- @requires_py39
def test_array_union_float_str_with_int(self) -> None:
"""Array[Union[float, str]] converts int via float alternative."""
result = _to_py_class_value(A(tuple[Union[float, str],
...]).convert([42, "hi", True]))
@@ -1112,19 +1066,16 @@ class TestNestedArrayComposite:
assert type(result[0]) is float
assert type(result[2]) is float
- @requires_py39
def test_array_optional_float_all_none(self) -> None:
"""Array[Optional[float]] with all None elements."""
result = _to_py_class_value(A(tuple[Optional[float],
...]).convert([None, None]))
assert list(result) == [None, None]
- @requires_py39
def test_array_optional_float_empty(self) -> None:
"""Array[Optional[float]] with empty list."""
result = _to_py_class_value(A(tuple[Optional[float], ...]).convert([]))
assert list(result) == []
- @requires_py39
def test_array_union_failure_in_element(self) -> None:
"""Array[Union[int, str]] fails when element matches no alternative."""
with pytest.raises(TypeError, match=r"element \[1\].*got float"):
@@ -1135,7 +1086,6 @@ class TestNestedArrayComposite:
# Category 22: Map/Dict nested with Optional/Union (inner conversion)
# ---------------------------------------------------------------------------
class TestNestedMapComposite:
- @requires_py39
def test_map_str_optional_float_with_int(self) -> None:
"""Map[str, Optional[float]] converts int values to float."""
result = _to_py_class_value(
@@ -1145,7 +1095,6 @@ class TestNestedMapComposite:
assert result["a"] == 1.0
assert result["b"] is None
- @requires_py39
def test_map_str_union_int_str(self) -> None:
"""Map[str, Union[int, str]] converts bool values via int."""
result = _to_py_class_value(
@@ -1155,7 +1104,6 @@ class TestNestedMapComposite:
assert result["y"] == "hello"
assert type(result["x"]) is int
- @requires_py39
def test_dict_str_optional_int(self) -> None:
"""Dict[str, Optional[int]] with bool conversion."""
result = _to_py_class_value(
@@ -1166,7 +1114,6 @@ class TestNestedMapComposite:
assert result["c"] == 42
assert type(result["a"]) is int
- @requires_py39
def test_map_str_optional_float_failure(self) -> None:
"""Map[str, Optional[float]] fails for non-float non-None value."""
with pytest.raises(TypeError, match="expected float"):
@@ -1177,21 +1124,18 @@ class TestNestedMapComposite:
# Category 23: Nested containers (container inside container)
# ---------------------------------------------------------------------------
class TestNestedContainerInContainer:
- @requires_py39
def test_array_of_array_int(self) -> None:
"""Array[Array[int]] with inner bool->int conversion."""
result = _to_py_class_value(A(tuple[tuple[int, ...],
...]).convert([[True, False], [1, 2]]))
assert [list(row) for row in result] == [[1, 0], [1, 2]]
assert all(type(x) is int for row in result for x in row)
- @requires_py39
def test_array_of_array_float(self) -> None:
"""Array[Array[float]] with inner int->float conversion."""
result = _to_py_class_value(A(tuple[tuple[float, ...],
...]).convert([[1, 2], [True, 3]]))
assert [list(row) for row in result] == [[1.0, 2.0], [1.0, 3.0]]
assert all(type(x) is float for row in result for x in row)
- @requires_py39
def test_map_str_array_float(self) -> None:
"""Map[str, Array[float]] with int->float conversion in arrays."""
result = _to_py_class_value(
@@ -1202,14 +1146,12 @@ class TestNestedContainerInContainer:
assert all(type(x) is float for x in result["a"])
assert all(type(x) is float for x in result["b"])
- @requires_py39
def test_dict_str_array_int(self) -> None:
"""Dict[str, Array[int]] with bool->int conversion."""
result = _to_py_class_value(A(dict[str, tuple[int,
...]]).convert({"a": [True, False]}))
assert list(result["a"]) == [1, 0]
assert all(type(x) is int for x in result["a"])
- @requires_py39
def test_array_of_map_str_int(self) -> None:
"""Array[Map[str, int]] with bool->int value conversion."""
result = _to_py_class_value(
@@ -1219,7 +1161,6 @@ class TestNestedContainerInContainer:
assert result[1]["y"] == 2
assert type(result[0]["x"]) is int
- @requires_py39
def test_map_str_map_str_float(self) -> None:
"""Map[str, Map[str, float]] double nested with int->float."""
result = _to_py_class_value(
@@ -1228,20 +1169,17 @@ class TestNestedContainerInContainer:
assert result["outer"]["inner"] == 42.0
assert type(result["outer"]["inner"]) is float
- @requires_py39
def test_list_of_list_int(self) -> None:
"""List[List[int]] with bool->int conversion."""
result = _to_py_class_value(A(list[list[int]]).convert([[True, 1],
[False, 2]]))
assert [list(row) for row in result] == [[1, 1], [0, 2]]
assert all(type(x) is int for row in result for x in row)
- @requires_py39
def test_nested_failure_array_of_array(self) -> None:
"""Array[Array[int]] error propagation through nested arrays."""
with pytest.raises(TypeError, match="expected int"):
A(tuple[tuple[int, ...], ...]).check_value([[1, 2], [3, "bad"]])
- @requires_py39
def test_empty_inner_containers(self) -> None:
"""Map[str, Array[int]] with empty inner arrays."""
result = _to_py_class_value(
@@ -1250,7 +1188,6 @@ class TestNestedContainerInContainer:
assert list(result["a"]) == []
assert list(result["b"]) == []
- @requires_py39
def test_array_of_array_of_array_int(self) -> None:
"""Three-level nested Array[int] conversion still works."""
schema = A(tuple[tuple[tuple[int, ...], ...], ...])
@@ -1260,7 +1197,6 @@ class TestNestedContainerInContainer:
assert list(result[0][1]) == [1, 0]
assert type(result[0][1][0]) is int
- @requires_py39
def test_map_of_map_of_array_float(self) -> None:
"""Nested map-to-array conversion still coerces inner values."""
schema = A(tvm_ffi.Map[str, tvm_ffi.Map[str, tuple[float, ...]]])
@@ -1274,7 +1210,6 @@ class TestNestedContainerInContainer:
# Category 24: Optional/Union wrapping containers
# ---------------------------------------------------------------------------
class TestOptionalUnionWrappingContainers:
- @requires_py39
def test_optional_array_int_with_conversion(self) -> None:
"""Optional[Array[int]] converts inner bool elements."""
schema = A(Optional[tuple[int, ...]])
@@ -1282,26 +1217,22 @@ class TestOptionalUnionWrappingContainers:
assert list(result) == [1, 2]
assert type(result[0]) is int
- @requires_py39
def test_optional_array_int_none(self) -> None:
"""Optional[Array[int]] accepts None."""
result = _to_py_class_value(A(Optional[tuple[int, ...]]).convert(None))
assert result is None
- @requires_py39
def test_optional_map_str_float(self) -> None:
"""Optional[Map[str, float]] converts inner int values."""
result = _to_py_class_value(A(Optional[tvm_ffi.Map[str,
float]]).convert({"a": 1}))
assert result["a"] == 1.0
assert type(result["a"]) is float
- @requires_py39
def test_optional_map_str_float_none(self) -> None:
"""Optional[Map[str, float]] accepts None."""
result = _to_py_class_value(A(Optional[tvm_ffi.Map[str,
float]]).convert(None))
assert result is None
- @requires_py39
def test_union_array_int_or_map_str_int(self) -> None:
"""Union[Array[int], Map[str, int]] matches first with conversion."""
schema = A(Union[tuple[int, ...], tvm_ffi.Map[str, int]])
@@ -1310,7 +1241,6 @@ class TestOptionalUnionWrappingContainers:
assert list(result) == [1, 2]
assert type(result[0]) is int
- @requires_py39
def test_union_array_int_or_map_str_int_dict(self) -> None:
"""Union[Array[int], Map[str, int]] matches Map for dict input."""
schema = A(Union[tuple[int, ...], tvm_ffi.Map[str, int]])
@@ -1318,7 +1248,6 @@ class TestOptionalUnionWrappingContainers:
assert result["a"] == 1
assert type(result["a"]) is int
- @requires_py39
def test_union_int_or_array_optional_float(self) -> None:
"""Union[int, Array[Optional[float]]] matches array with nested
conversions."""
schema = A(Union[int, tuple[Optional[float], ...]])
@@ -1327,7 +1256,6 @@ class TestOptionalUnionWrappingContainers:
assert type(result[0]) is float
assert result[1] is None
- @requires_py39
def test_optional_optional_array_int(self) -> None:
"""Optional[Optional[Array[int]]] with inner conversion."""
schema = A(Optional[Optional[tuple[int, ...]]])
@@ -1341,7 +1269,6 @@ class TestOptionalUnionWrappingContainers:
# Category 25: Tuple nested with other types
# ---------------------------------------------------------------------------
class TestNestedTuple:
- @requires_py39
def test_array_of_tuple_int_float(self) -> None:
"""Array[tuple[int, float]] with element-wise conversion."""
result = _to_py_class_value(
@@ -1354,7 +1281,6 @@ class TestNestedTuple:
assert result[1][0] == 2
assert result[1][1] == 1.0
- @requires_py39
def test_map_str_tuple_int_str(self) -> None:
"""Map[str, tuple[int, str]] with inner bool->int conversion."""
result = _to_py_class_value(
@@ -1364,7 +1290,6 @@ class TestNestedTuple:
assert str(result["a"][1]) == "hello"
assert type(result["a"][0]) is int
- @requires_py39
def test_tuple_of_array_int_and_map(self) -> None:
"""tuple[Array[int], Map[str, float]] nested conversion."""
schema = A(tuple[tuple[int, ...], tvm_ffi.Map[str, float]])
@@ -1374,7 +1299,6 @@ class TestNestedTuple:
assert type(result[0][0]) is int
assert type(result[1]["k"]) is float
- @requires_py39
def test_tuple_of_optional_int_and_optional_float(self) -> None:
"""tuple[Optional[int], Optional[float]] with conversions."""
schema = A(tuple[Optional[int], Optional[float]])
@@ -1383,7 +1307,6 @@ class TestNestedTuple:
assert type(result[0]) is int
assert result[1] is None
- @requires_py39
def test_tuple_nested_failure(self) -> None:
"""tuple[Array[int], str] error propagation from inner array."""
with pytest.raises(TypeError, match=r"element .0..*element
.1..*expected int"):
@@ -1394,7 +1317,6 @@ class TestNestedTuple:
# Category 26: Deep nesting (3+ levels)
# ---------------------------------------------------------------------------
class TestDeepNesting:
- @requires_py39
def test_map_str_array_optional_int(self) -> None:
"""Map[str, Array[Optional[int]]] with 3-level nesting and
conversion."""
result = _to_py_class_value(
@@ -1405,7 +1327,6 @@ class TestDeepNesting:
assert result["a"][1] is None
assert type(result["a"][2]) is int
- @requires_py39
def test_array_map_str_optional_float(self) -> None:
"""Array[Map[str, Optional[float]]] with 3-level nesting."""
result = _to_py_class_value(
@@ -1419,7 +1340,6 @@ class TestDeepNesting:
assert type(result[0]["x"]) is float
assert type(result[1]["z"]) is float
- @requires_py39
def test_optional_array_map_str_int(self) -> None:
"""Optional[Array[Map[str, int]]] 3 levels deep."""
schema = A(Optional[tuple[tvm_ffi.Map[str, int], ...]])
@@ -1430,7 +1350,6 @@ class TestDeepNesting:
assert _to_py_class_value(schema.convert(None)) is None
- @requires_py39
def test_map_str_array_array_int(self) -> None:
"""Map[str, Array[Array[int]]] 3-level container nesting."""
result = _to_py_class_value(
@@ -1439,7 +1358,6 @@ class TestDeepNesting:
assert [list(row) for row in result["m"]] == [[1, 1], [0, 2]]
assert all(type(x) is int for row in result["m"] for x in row)
- @requires_py39
def test_array_array_optional_float(self) -> None:
"""Array[Array[Optional[float]]] deep nesting with None and
conversion."""
result = _to_py_class_value(
@@ -1451,7 +1369,6 @@ class TestDeepNesting:
assert result[0][1] is None
assert type(result[1][0]) is float
- @requires_py39
def test_deep_nesting_failure_propagation(self) -> None:
"""Error from deepest level propagates with full path info."""
with pytest.raises(TypeError, match=r"value for key 'key'.*element
.1..*expected int"):
@@ -1462,7 +1379,6 @@ class TestDeepNesting:
# Category 27: FFI container inputs (tvm_ffi.Array/List/Map/Dict)
# ---------------------------------------------------------------------------
class TestFFIContainerInputs:
- @requires_py39
def test_ffi_array_with_element_conversion(self) -> None:
"""tvm_ffi.Array([True, 2]) passes Array[int] with bool->int
conversion."""
arr = tvm_ffi.Array([True, 2, 3])
@@ -1470,14 +1386,12 @@ class TestFFIContainerInputs:
assert list(result) == [1, 2, 3]
assert type(result[0]) is int
- @requires_py39
def test_ffi_array_any_passthrough(self) -> None:
"""tvm_ffi.Array passes Array[Any] as-is."""
arr = tvm_ffi.Array([1, "x", None])
result = _to_py_class_value(A(tuple[typing.Any, ...]).convert(arr))
assert result.same_as(arr)
- @requires_py39
def test_ffi_list_with_list_schema(self) -> None:
"""tvm_ffi.List passes List[int] with conversion."""
lst = tvm_ffi.List([True, 2])
@@ -1485,19 +1399,16 @@ class TestFFIContainerInputs:
assert list(result) == [1, 2]
assert type(result[0]) is int
- @requires_py39
def test_ffi_list_accepted_by_array_schema(self) -> None:
"""tvm_ffi.List passes Array schema (C++ allows cross-type via
kOtherTypeIndex)."""
lst = tvm_ffi.List([1, 2])
A(tuple[int, ...]).check_value(lst)
- @requires_py39
def test_ffi_array_accepted_by_list_schema(self) -> None:
"""tvm_ffi.Array passes List schema (C++ allows cross-type via
kOtherTypeIndex)."""
arr = tvm_ffi.Array([1, 2])
A(list[int]).check_value(arr)
- @requires_py39
def test_ffi_map_with_value_conversion(self) -> None:
"""tvm_ffi.Map passes Map[str, int] with bool->int conversion."""
m = tvm_ffi.Map({"a": True, "b": 2})
@@ -1506,14 +1417,12 @@ class TestFFIContainerInputs:
assert result["b"] == 2
assert type(result["a"]) is int
- @requires_py39
def test_ffi_map_any_any_passthrough(self) -> None:
"""tvm_ffi.Map passes Map[Any, Any] as-is."""
m = tvm_ffi.Map({"a": 1})
result = _to_py_class_value(A(tvm_ffi.Map[typing.Any,
typing.Any]).convert(m))
assert result.same_as(m)
- @requires_py39
def test_ffi_dict_with_dict_schema(self) -> None:
"""tvm_ffi.Dict passes Dict[str, float] with int->float conversion."""
d = tvm_ffi.Dict({"x": 1, "y": 2})
@@ -1522,19 +1431,16 @@ class TestFFIContainerInputs:
assert result["y"] == 2.0
assert type(result["x"]) is float
- @requires_py39
def test_ffi_dict_accepted_by_map_schema(self) -> None:
"""tvm_ffi.Dict passes Map schema (C++ allows cross-type via
kOtherTypeIndex)."""
d = tvm_ffi.Dict({"a": 1})
A(tvm_ffi.Map[str, int]).check_value(d)
- @requires_py39
def test_ffi_map_accepted_by_dict_schema(self) -> None:
"""tvm_ffi.Map passes Dict schema (C++ allows cross-type via
kOtherTypeIndex)."""
m = tvm_ffi.Map({"a": 1})
A(dict[str, int]).check_value(m)
- @requires_py39
def test_ffi_array_nested_optional_float(self) -> None:
"""tvm_ffi.Array with nested Optional[float] conversion."""
arr = tvm_ffi.Array([1, None, True])
@@ -1543,7 +1449,6 @@ class TestFFIContainerInputs:
assert type(result[0]) is float
assert result[1] is None
- @requires_py39
def test_ffi_map_nested_array_int(self) -> None:
"""tvm_ffi.Map with value being a Python list, converted as
Array[int]."""
# Map values are already stored; create a map with array values
@@ -1552,14 +1457,12 @@ class TestFFIContainerInputs:
assert list(result["k"]) == [1, 2]
assert type(result["k"][0]) is int
- @requires_py39
def test_ffi_array_wrong_element_type(self) -> None:
"""tvm_ffi.Array with wrong element type gives clear error."""
arr = tvm_ffi.Array([1, "bad", 3])
with pytest.raises(TypeError, match=r"element \[1\].*expected int"):
A(tuple[int, ...]).check_value(arr)
- @requires_py39
def test_ffi_map_wrong_value_type(self) -> None:
"""tvm_ffi.Map with wrong value type gives clear error."""
m = tvm_ffi.Map({"a": 1, "b": "bad"})
@@ -1581,7 +1484,6 @@ class TestFFIContainerInputs:
# Category 28: Mixed Python and FFI containers in nesting
# ---------------------------------------------------------------------------
class TestMixedPythonFFIContainers:
- @requires_py39
def test_python_list_of_ffi_arrays(self) -> None:
"""Python list containing tvm_ffi.Array elements, Array[Array[int]]."""
inner1 = tvm_ffi.Array([True, 2])
@@ -1589,7 +1491,6 @@ class TestMixedPythonFFIContainers:
result = _to_py_class_value(A(tuple[tuple[int, ...],
...]).convert([inner1, inner2]))
assert [list(row) for row in result] == [[1, 2], [3, 0]]
- @requires_py39
def test_python_dict_with_ffi_array_values(self) -> None:
"""Python dict with tvm_ffi.Array values, Map[str, Array[float]]."""
val = tvm_ffi.Array([1, True])
@@ -1597,7 +1498,6 @@ class TestMixedPythonFFIContainers:
assert list(result["k"]) == [1.0, 1.0]
assert all(type(x) is float for x in result["k"])
- @requires_py39
def test_ffi_map_with_python_list_in_union(self) -> None:
"""Union[Map[str, int], Array[int]] with tvm_ffi.Map input."""
schema = A(Union[tvm_ffi.Map[str, int], tuple[int, ...]])
@@ -1606,7 +1506,6 @@ class TestMixedPythonFFIContainers:
assert result["a"] == 1
assert type(result["a"]) is int
- @requires_py39
def test_ffi_array_in_optional(self) -> None:
"""Optional[Array[int]] with tvm_ffi.Array input."""
arr = tvm_ffi.Array([True, 2])
@@ -1619,13 +1518,11 @@ class TestMixedPythonFFIContainers:
# Category 29: Error propagation through deeply nested FFI containers
# ---------------------------------------------------------------------------
class TestNestedErrorPropagation:
- @requires_py39
def test_array_array_int_inner_failure(self) -> None:
"""Error path: Array[Array[int]] -> element [1] -> element [0]."""
with pytest.raises(TypeError, match=r"element \[1\].*element
\[0\].*expected int, got str"):
A(tuple[tuple[int, ...], ...]).convert([[1], ["bad"]])
- @requires_py39
def test_map_array_int_inner_failure(self) -> None:
"""Error path: Map -> value for key 'k' -> element [2]."""
with pytest.raises(
@@ -1634,7 +1531,6 @@ class TestNestedErrorPropagation:
):
A(tvm_ffi.Map[str, tuple[int, ...]]).convert({"k": [1, 2, "bad"]})
- @requires_py39
def test_array_map_int_inner_failure(self) -> None:
"""Error path: Array -> element [0] -> value for key 'x'."""
with pytest.raises(
@@ -1643,25 +1539,21 @@ class TestNestedErrorPropagation:
):
A(tuple[tvm_ffi.Map[str, int], ...]).convert([{"x": "bad"}])
- @requires_py39
def test_optional_array_int_inner_failure(self) -> None:
"""Error path through Optional -> Array -> element."""
with pytest.raises(TypeError, match=r"element \[1\].*expected int, got
str"):
A(Optional[tuple[int, ...]]).convert([1, "bad"])
- @requires_py39
def test_tuple_array_int_inner_failure(self) -> None:
"""Error path: tuple -> element [0] -> element [1]."""
with pytest.raises(TypeError, match=r"element \[0\].*element
\[1\].*expected int, got str"):
A(tuple[tuple[int, ...], str]).convert(([1, "bad"], "ok"))
- @requires_py39
def test_deep_3_level_error(self) -> None:
"""Error at 3 levels deep: Map -> Array -> Optional -> type
mismatch."""
with pytest.raises(TypeError, match=r"value for key 'key'.*element
.1..*expected int"):
A(tvm_ffi.Map[str, tuple[Optional[int], ...]]).check_value({"key":
[1, "bad"]})
- @requires_py39
def test_ffi_array_nested_error(self) -> None:
"""Error from tvm_ffi.Array in nested context."""
arr = tvm_ffi.Array([1, "bad", 3])
@@ -1798,52 +1690,44 @@ class TestCustomObjectRejection:
# Category 33: Custom objects in containers
# ---------------------------------------------------------------------------
class TestCustomObjectInContainers:
- @requires_py39
def test_array_of_custom_objects(self) -> None:
"""Array[testing.TestIntPair] with matching elements."""
objs = [TestIntPair(1, 2), TestIntPair(3, 4)]
A(tuple[TestIntPair, ...]).check_value(objs)
- @requires_py39
def test_array_of_custom_objects_wrong_type(self) -> None:
"""Array[testing.TestIntPair] with wrong element type fails."""
objs = [TestIntPair(1, 2), _TestCxxClassBase(v_i64=1, v_i32=2)]
with pytest.raises(TypeError, match=r"element \[1\]"):
A(tuple[TestIntPair, ...]).check_value(objs)
- @requires_py39
def test_array_of_base_with_derived_elements(self) -> None:
"""Array[testing.TestObjectBase] accepts derived elements via
hierarchy."""
base = TestObjectBase(v_i64=1, v_f64=1.0, v_str="a")
derived = TestObjectDerived(v_map={"a": 1}, v_array=[1], v_i64=0,
v_f64=0.0, v_str="")
A(tuple[TestObjectBase, ...]).check_value([base, derived])
- @requires_py39
def test_map_str_to_custom_object(self) -> None:
"""Map[str, testing.TestIntPair] pass."""
objs = {"a": TestIntPair(1, 2), "b": TestIntPair(3, 4)}
A(tvm_ffi.Map[str, TestIntPair]).check_value(objs)
- @requires_py39
def test_map_str_to_custom_object_wrong_value(self) -> None:
"""Map[str, testing.TestIntPair] with int value fails."""
data = {"a": TestIntPair(1, 2), "b": 42}
with pytest.raises(TypeError, match="value for key 'b'"):
A(tvm_ffi.Map[str, TestIntPair]).check_value(data)
- @requires_py39
def test_ffi_array_of_custom_objects(self) -> None:
"""tvm_ffi.Array of custom objects passes Array[Object]."""
arr = tvm_ffi.Array([TestIntPair(1, 2), TestObjectBase(v_i64=1,
v_f64=2.0, v_str="s")])
A(tuple[tvm_ffi.core.Object, ...]).check_value(arr)
- @requires_py39
def test_ffi_array_of_custom_objects_specific_type(self) -> None:
"""tvm_ffi.Array of TestIntPair passes Array[testing.TestIntPair]."""
arr = tvm_ffi.Array([TestIntPair(1, 2), TestIntPair(3, 4)])
A(tuple[TestIntPair, ...]).check_value(arr)
- @requires_py39
def test_ffi_map_with_custom_object_values(self) -> None:
"""tvm_ffi.Map with custom object values passes."""
m = tvm_ffi.Map({"x": TestIntPair(1, 2), "y": TestIntPair(3, 4)})
@@ -1932,13 +1816,11 @@ class TestCustomObjectFromTypeIndex:
# Category 36: Custom objects in nested containers
# ---------------------------------------------------------------------------
class TestCustomObjectNestedContainers:
- @requires_py39
def test_array_of_optional_custom_object(self) -> None:
"""Array[Optional[testing.TestIntPair]] with mix of objects and
None."""
data = [TestIntPair(1, 2), None, TestIntPair(3, 4)]
A(tuple[Optional[TestIntPair], ...]).check_value(data)
- @requires_py39
def test_map_str_to_array_of_custom_objects(self) -> None:
"""Map[str, Array[testing.TestIntPair]] with nested objects."""
data = {
@@ -1947,24 +1829,20 @@ class TestCustomObjectNestedContainers:
}
A(tvm_ffi.Map[str, tuple[TestIntPair, ...]]).check_value(data)
- @requires_py39
def test_array_of_union_custom_objects(self) -> None:
"""Array[Union[testing.TestIntPair, testing.TestCxxClassBase]]."""
data = [TestIntPair(1, 2), _TestCxxClassBase(v_i64=1, v_i32=2),
TestIntPair(5, 6)]
A(tuple[Union[TestIntPair, _TestCxxClassBase], ...]).check_value(data)
- @requires_py39
def test_optional_array_of_custom_objects(self) -> None:
"""Optional[Array[testing.TestIntPair]] with array."""
data = [TestIntPair(1, 2)]
A(Optional[tuple[TestIntPair, ...]]).check_value(data)
- @requires_py39
def test_optional_array_of_custom_objects_none(self) -> None:
"""Optional[Array[testing.TestIntPair]] with None."""
A(Optional[tuple[TestIntPair, ...]]).check_value(None)
- @requires_py39
def test_nested_error_with_custom_object(self) -> None:
"""Array[testing.TestIntPair] error message includes type keys."""
data = [TestIntPair(1, 2), _TestCxxClassBase(v_i64=1, v_i32=2)]
@@ -1973,7 +1851,6 @@ class TestCustomObjectNestedContainers:
):
A(tuple[TestIntPair, ...]).check_value(data)
- @requires_py39
def test_map_nested_error_with_custom_object(self) -> None:
"""Map value error for custom object includes key and type info."""
data = {"ok": TestIntPair(1, 2), "bad": 42}
@@ -1982,7 +1859,6 @@ class TestCustomObjectNestedContainers:
):
A(tvm_ffi.Map[str, TestIntPair]).check_value(data)
- @requires_py39
def test_deep_nested_custom_objects(self) -> None:
"""Map[str, Array[Optional[testing.TestIntPair]]] deep nesting."""
data = {
@@ -1991,20 +1867,17 @@ class TestCustomObjectNestedContainers:
}
A(tvm_ffi.Map[str, tuple[Optional[TestIntPair],
...]]).check_value(data)
- @requires_py39
def test_deep_nested_custom_objects_error(self) -> None:
"""Map[str, Array[testing.TestIntPair]] error at 3 levels."""
data = {"k": [TestIntPair(1, 2), "bad"]}
with pytest.raises(TypeError, match=r"value for key 'k'.*element .1."):
A(tvm_ffi.Map[str, tuple[TestIntPair, ...]]).check_value(data)
- @requires_py39
def test_tuple_with_custom_object(self) -> None:
"""tuple[testing.TestIntPair, int, str] with custom object."""
data = (TestIntPair(1, 2), 42, "hello")
A(tuple[TestIntPair, int, str]).check_value(data)
- @requires_py39
def test_tuple_with_custom_object_wrong(self) -> None:
"""tuple[testing.TestIntPair, int] with wrong object in first
position."""
data = (_TestCxxClassBase(v_i64=1, v_i32=2), 42)
@@ -2078,31 +1951,26 @@ class TestLowercaseOrigins:
# Category 38: Cross-type container conversions (Array<->List, Map<->Dict)
# ---------------------------------------------------------------------------
class TestCrossTypeContainers:
- @requires_py39
def test_array_schema_accepts_ffi_list(self) -> None:
"""Array[int] schema accepts tvm_ffi.List (C++ kOtherTypeIndex)."""
lst = tvm_ffi.List([1, 2, 3])
A(tuple[int, ...]).check_value(lst)
- @requires_py39
def test_list_schema_accepts_ffi_array(self) -> None:
"""List[int] schema accepts tvm_ffi.Array (C++ kOtherTypeIndex)."""
arr = tvm_ffi.Array([1, 2, 3])
A(list[int]).check_value(arr)
- @requires_py39
def test_map_schema_accepts_ffi_dict(self) -> None:
"""Map[str, int] schema accepts tvm_ffi.Dict (C++ kOtherTypeIndex)."""
d = tvm_ffi.Dict({"a": 1, "b": 2})
A(tvm_ffi.Map[str, int]).check_value(d)
- @requires_py39
def test_dict_schema_accepts_ffi_map(self) -> None:
"""Dict[str, int] schema accepts tvm_ffi.Map (C++ kOtherTypeIndex)."""
m = tvm_ffi.Map({"a": 1, "b": 2})
A(dict[str, int]).check_value(m)
- @requires_py39
def test_array_schema_converts_list_elements(self) -> None:
"""Array[float] converts elements from tvm_ffi.List[int]."""
lst = tvm_ffi.List([1, 2, True])
@@ -2110,7 +1978,6 @@ class TestCrossTypeContainers:
assert list(result) == [1.0, 2.0, 1.0]
assert all(type(x) is float for x in result)
- @requires_py39
def test_list_schema_converts_array_elements(self) -> None:
"""List[float] converts elements from tvm_ffi.Array[int]."""
arr = tvm_ffi.Array([1, 2, True])
@@ -2118,7 +1985,6 @@ class TestCrossTypeContainers:
assert list(result) == [1.0, 2.0, 1.0]
assert all(type(x) is float for x in result)
- @requires_py39
def test_map_schema_converts_dict_values(self) -> None:
"""Map[str, float] converts values from tvm_ffi.Dict."""
d = tvm_ffi.Dict({"a": 1, "b": True})
@@ -2126,7 +1992,6 @@ class TestCrossTypeContainers:
assert result["a"] == 1.0
assert result["b"] == 1.0
- @requires_py39
def test_dict_schema_converts_map_values(self) -> None:
"""Dict[str, float] converts values from tvm_ffi.Map."""
m = tvm_ffi.Map({"a": 1, "b": True})
@@ -2134,14 +1999,12 @@ class TestCrossTypeContainers:
assert result["a"] == 1.0
assert result["b"] == 1.0
- @requires_py39
def test_cross_type_still_rejects_wrong_container(self) -> None:
"""Array schema still rejects non-sequence CObjects (e.g. Map)."""
m = tvm_ffi.Map({"a": 1})
with pytest.raises(TypeError, match="expected Array"):
A(tuple[int, ...]).check_value(m)
- @requires_py39
def test_cross_type_map_rejects_array(self) -> None:
"""Map schema still rejects sequence CObjects (e.g. Array)."""
arr = tvm_ffi.Array([1, 2])
@@ -2153,13 +2016,11 @@ class TestCrossTypeContainers:
# Category 39: tuple accepts list and CObject Array
# ---------------------------------------------------------------------------
class TestTupleAcceptsListAndArray:
- @requires_py39
def test_tuple_accepts_python_list(self) -> None:
"""tuple[int, str] accepts Python list input."""
result = _to_py_class_value(A(tuple[int, str]).convert([42, "hello"]))
assert list(result) == [42, "hello"]
- @requires_py39
def test_tuple_list_with_conversion(self) -> None:
"""tuple[float, int] converts list elements (bool->float,
bool->int)."""
result = _to_py_class_value(A(tuple[float, int]).convert([True,
False]))
@@ -2167,19 +2028,16 @@ class TestTupleAcceptsListAndArray:
assert type(result[0]) is float
assert type(result[1]) is int
- @requires_py39
def test_tuple_rejects_wrong_length_list(self) -> None:
"""tuple[int, str] rejects list of wrong length."""
with pytest.raises(TypeError, match="length"):
A(tuple[int, str]).check_value([1, "a", "b"])
- @requires_py39
def test_tuple_accepts_ffi_array(self) -> None:
"""tuple[int, int] accepts tvm_ffi.Array (C++ Tuple accepts
kTVMFFIArray)."""
arr = tvm_ffi.Array([1, 2])
A(tuple[int, int]).check_value(arr)
- @requires_py39
def test_tuple_ffi_array_with_conversion(self) -> None:
"""tuple[float, float] converts tvm_ffi.Array elements."""
arr = tvm_ffi.Array([1, True])
@@ -2187,14 +2045,12 @@ class TestTupleAcceptsListAndArray:
assert list(result) == [1.0, 1.0]
assert all(type(x) is float for x in result)
- @requires_py39
def test_tuple_ffi_array_wrong_length(self) -> None:
"""tuple[int, int] rejects tvm_ffi.Array of wrong length."""
arr = tvm_ffi.Array([1, 2, 3])
with pytest.raises(TypeError, match="length"):
A(tuple[int, int]).check_value(arr)
- @requires_py39
def test_tuple_rejects_ffi_map(self) -> None:
"""Tuple schema rejects Map CObject."""
m = tvm_ffi.Map({"a": 1})
@@ -2318,7 +2174,6 @@ class TestInt64Boundaries:
with pytest.raises(TypeError, match="int64 range"):
A(Optional[int]).check_value(2**63)
- @requires_py39
def test_int64_overflow_in_array_element(self) -> None:
"""Array[int] element overflow is caught with path."""
with pytest.raises(TypeError, match="int64 range"):
@@ -2981,7 +2836,6 @@ class TestValueProtocol:
class TestProtocolsInContainers:
"""Protocol-accepting values work inside containers and composites."""
- @requires_py39
def test_int_protocol_in_array(self) -> None:
"""Array[int] accepts elements with __tvm_ffi_int__."""
@@ -3011,7 +2865,6 @@ class TestProtocolsInContainers:
A(Union[TestIntPair, int]).check_value(ObjProto())
- @requires_py39
def test_value_protocol_in_array(self) -> None:
"""Array[int] elements use __tvm_ffi_value__ fallback (recursive)."""
@@ -3024,7 +2877,6 @@ class TestProtocolsInContainers:
# called per-element.
A(tuple[int, ...]).check_value([ValProto()])
- @requires_py39
def test_object_convertible_in_array(self) -> None:
"""Array[Object] elements unwrap ObjectConvertible before element
dispatch."""
inner = TestIntPair(3, 4)
@@ -3036,7 +2888,6 @@ class TestProtocolsInContainers:
result = _to_py_class_value(A(tuple[tvm_ffi.core.Object,
...]).convert([Convertible()]))
assert result[0].same_as(inner)
- @requires_py39
def test_device_protocol_in_map_value(self) -> None:
"""Map[str, Device] accepts __dlpack_device__ values."""
@@ -3053,7 +2904,6 @@ class TestProtocolsInContainers:
class TestNestedValueProtocol:
"""__tvm_ffi_value__ fallback works recursively inside containers."""
- @requires_py39
def test_value_in_array_elements(self) -> None:
"""Array[int] elements with __tvm_ffi_value__ are accepted."""
@@ -3063,7 +2913,6 @@ class TestNestedValueProtocol:
A(tuple[int, ...]).check_value([1, VP(), 3])
- @requires_py39
def test_value_in_map_values(self) -> None:
"""Map[str, int] values with __tvm_ffi_value__ are accepted."""
@@ -3073,7 +2922,6 @@ class TestNestedValueProtocol:
A(tvm_ffi.Map[str, int]).check_value({"a": VP()})
- @requires_py39
def test_value_in_map_keys(self) -> None:
"""Map[str, int] keys with __tvm_ffi_value__ are accepted."""
@@ -3083,7 +2931,6 @@ class TestNestedValueProtocol:
A(tvm_ffi.Map[str, int]).check_value({VP(): 1})
- @requires_py39
def test_value_in_tuple_positions(self) -> None:
"""tuple[int, str] positions with __tvm_ffi_value__ are accepted."""
@@ -3115,7 +2962,6 @@ class TestNestedValueProtocol:
A(Union[int, str]).check_value(VP())
- @requires_py39
def test_multi_hop_value_in_container(self) -> None:
"""Nested __tvm_ffi_value__ unwrapping inside containers."""
@@ -3128,7 +2974,6 @@ class TestNestedValueProtocol:
A(tuple[int, ...]).check_value([VP(VP(10))])
- @requires_py39
def test_value_convert_in_array(self) -> None:
"""Convert returns unwrapped values in container."""
@@ -3604,49 +3449,42 @@ class TestSTLOriginParsing:
class TestZeroCopyConversion:
"""Typed container conversion preserves identity when no elements
change."""
- @requires_py39
def test_array_int_exact_list(self) -> None:
"""Array[int] on exact Python list converts successfully."""
original = [1, 2, 3]
result = _to_py_class_value(A(tuple[int, ...]).convert(original))
assert list(result) == original
- @requires_py39
def test_array_int_needs_conversion(self) -> None:
"""Array[int] on list needing bool->int returns converted list."""
original = [1, True, 3]
result = _to_py_class_value(A(tuple[int, ...]).convert(original))
assert list(result) == [1, 1, 3]
- @requires_py39
def test_map_str_int_exact_dict(self) -> None:
"""Map[str, int] on exact dict converts successfully."""
original = {"a": 1, "b": 2}
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert(original))
assert dict(result) == original
- @requires_py39
def test_map_str_int_needs_conversion(self) -> None:
"""Map[str, int] on dict needing conversion returns converted dict."""
original = {"a": True, "b": 2}
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert(original))
assert result is not None
- @requires_py39
def test_tuple_exact_match(self) -> None:
"""tuple[int, str] on exact tuple converts successfully."""
original = (42, "hello")
result = _to_py_class_value(A(tuple[int, str]).convert(original))
assert tuple(result) == original
- @requires_py39
def test_tuple_needs_conversion(self) -> None:
"""tuple[int, str] on tuple needing conversion returns converted
tuple."""
original = (True, "hello")
result = _to_py_class_value(A(tuple[int, str]).convert(original))
assert tuple(result) == (1, "hello")
- @requires_py39
def test_list_int_exact(self) -> None:
"""List[int] on exact list converts successfully."""
original = [10, 20]
@@ -4004,7 +3842,6 @@ class TestCAny:
# Short strings have type_index=11 (SmallStr), longer ones have 65
(Str)
assert cany.type_index in (11, 65)
- @requires_py39
def test_cany_from_array(self) -> None:
"""convert(Array) returns CAny with array type_index."""
cany = A(tuple[int, ...]).convert([1, 2, 3])
@@ -4036,27 +3873,23 @@ class TestCAny:
"""to_py() round-trips str correctly."""
assert _to_py_class_value(A(str).convert("hello")) == "hello"
- @requires_py39
def test_to_py_array(self) -> None:
"""to_py() returns ffi.Array for Array convert."""
result = _to_py_class_value(A(tuple[int, ...]).convert([1, 2, 3]))
assert isinstance(result, tvm_ffi.Array)
assert list(result) == [1, 2, 3]
- @requires_py39
def test_to_py_list(self) -> None:
"""to_py() returns ffi.List for List convert."""
result = _to_py_class_value(A(list[int]).convert([1, 2, 3]))
assert isinstance(result, tvm_ffi.List)
assert list(result) == [1, 2, 3]
- @requires_py39
def test_to_py_map(self) -> None:
"""to_py() returns ffi.Map for Map convert."""
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert({"a": 1}))
assert isinstance(result, tvm_ffi.Map)
- @requires_py39
def test_to_py_dict(self) -> None:
"""to_py() returns ffi.Dict for Dict convert."""
result = _to_py_class_value(A(dict[str, int]).convert({"a": 1}))
@@ -4069,7 +3902,6 @@ class TestCAny:
assert _to_py_class_value(cany) == 42
assert _to_py_class_value(cany) == 42
- @requires_py39
def test_object_refcount_safety(self) -> None:
"""to_py() for objects properly IncRefs — no double-free."""
cany = A(tuple[int, ...]).convert([1, 2, 3])
@@ -4095,7 +3927,6 @@ class TestCAny:
cany = A(float).convert(3.14)
assert "float" in repr(cany)
- @requires_py39
def test_repr_object(self) -> None:
"""Repr shows type_index for objects."""
cany = A(tuple[int, ...]).convert([1, 2, 3])
@@ -4202,51 +4033,42 @@ class TestFromAnnotationFFITypes:
"""ctypes.c_void_p → canonical origin 'ctypes.c_void_p'."""
assert A(ctypes.c_void_p) == S("ctypes.c_void_p")
- @requires_py39
def test_array_parameterized(self) -> None:
"""tvm_ffi.Array[int] cross-equivalent to tuple[int, ...]."""
assert A(tvm_ffi.Array[int]) == A(tuple[int, ...])
- @requires_py39
def test_list_parameterized(self) -> None:
"""tvm_ffi.List[str] cross-equivalent to list[str]."""
assert A(tvm_ffi.List[str]) == A(list[str])
- @requires_py39
def test_map_parameterized(self) -> None:
"""tvm_ffi.Map[str, float]."""
assert A(tvm_ffi.Map[str, float]) == S("Map", S("str"), S("float"))
- @requires_py39
def test_dict_parameterized(self) -> None:
"""tvm_ffi.Dict[str, int] cross-equivalent to dict[str, int]."""
assert A(tvm_ffi.Dict[str, int]) == A(dict[str, int])
- @requires_py39
def test_array_too_many_args(self) -> None:
"""tvm_ffi.Array[int, str] raises TypeError."""
with pytest.raises(TypeError, match="requires 1"):
A(tvm_ffi.Array[int, str]) # type: ignore[type-arg]
- @requires_py39
def test_list_too_many_args(self) -> None:
"""tvm_ffi.List[int, str] raises TypeError."""
with pytest.raises(TypeError, match="requires 1"):
A(tvm_ffi.List[int, str]) # type: ignore[type-arg]
- @requires_py39
def test_dict_one_arg(self) -> None:
"""tvm_ffi.Dict[str] raises TypeError."""
with pytest.raises(TypeError, match="requires 2"):
A(tvm_ffi.Dict[str]) # type: ignore[type-arg]
- @requires_py39
def test_dict_three_args(self) -> None:
"""tvm_ffi.Dict[str, int, float] raises TypeError."""
with pytest.raises(TypeError, match="requires 2"):
A(tvm_ffi.Dict[str, int, float]) # type: ignore[type-arg]
- @requires_py39
def test_map_one_arg(self) -> None:
"""tvm_ffi.Map[str] raises TypeError."""
with pytest.raises(TypeError, match="requires 2"):
@@ -4289,12 +4111,10 @@ class TestFromAnnotationList:
"""Bare list."""
assert A(list).origin == "List"
- @requires_py39
def test_int(self) -> None:
"""list[int]."""
assert A(list[int]) == S("List", S("int"))
- @requires_py39
def test_nested(self) -> None:
"""list[list[int]]."""
assert A(list[list[int]]) == S("List", S("List", S("int")))
@@ -4307,7 +4127,6 @@ class TestFromAnnotationDict:
"""Bare dict."""
assert A(dict).origin == "Dict"
- @requires_py39
def test_str_int(self) -> None:
"""dict[str, int]."""
assert A(dict[str, int]) == S("Dict", S("str"), S("int"))
@@ -4316,12 +4135,10 @@ class TestFromAnnotationDict:
class TestFromAnnotationArray:
"""tuple[T, ...] → Array tests."""
- @requires_py39
def test_int(self) -> None:
"""tuple[int, ...]."""
assert A(tuple[int, ...]) == S("Array", S("int"))
- @requires_py39
def test_float(self) -> None:
"""tuple[float, ...]."""
assert A(tuple[float, ...]) == S("Array", S("float"))
@@ -4334,12 +4151,10 @@ class TestFromAnnotationTuple:
"""Bare tuple."""
assert A(tuple).origin == "tuple"
- @requires_py39
def test_int_str(self) -> None:
"""tuple[int, str]."""
assert A(tuple[int, str]) == S("tuple", S("int"), S("str"))
- @requires_py39
def test_empty(self) -> None:
"""tuple[()] stays distinct from bare tuple."""
assert A(tuple[()]) == TypeSchema("tuple", ())
@@ -4399,13 +4214,11 @@ class TestFromAnnotationErrors:
with pytest.raises(TypeError, match="Cannot convert"):
A(complex)
- @requires_py39
def test_list_too_many_args(self) -> None:
"""list[int, int, float] raises."""
with pytest.raises(TypeError, match="list takes at most 1"):
A(list[int, int, float]) # type: ignore[type-arg]
- @requires_py39
def test_dict_one_arg(self) -> None:
"""dict[str] raises."""
with pytest.raises(TypeError, match="dict requires 0 or 2"):
@@ -4421,47 +4234,40 @@ import tvm_ffi as _tvm_ffi
class TestConvertReturnFFIContainers:
"""convert() returns ffi.Array/List/Map/Dict."""
- @requires_py39
def test_array_from_list(self) -> None:
"""Array convert from Python list."""
result = _to_py_class_value(A(tuple[float, ...]).convert([1, 2, 3]))
assert isinstance(result, _tvm_ffi.Array)
assert list(result) == [1.0, 2.0, 3.0]
- @requires_py39
def test_list_from_list(self) -> None:
"""List convert from Python list."""
result = _to_py_class_value(A(list[int]).convert([1, 2, 3]))
assert isinstance(result, _tvm_ffi.List)
assert list(result) == [1, 2, 3]
- @requires_py39
def test_dict_from_dict(self) -> None:
"""Dict convert from Python dict."""
result = _to_py_class_value(A(dict[str, int]).convert({"a": 1}))
assert isinstance(result, _tvm_ffi.Dict)
- @requires_py39
def test_map_from_dict(self) -> None:
"""Map convert from Python dict."""
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert({"a": 1}))
assert isinstance(result, _tvm_ffi.Map)
- @requires_py39
def test_array_passthrough(self) -> None:
"""ffi.Array input passes through unchanged."""
arr = _tvm_ffi.Array([1, 2, 3])
result = _to_py_class_value(A(tuple[int, ...]).convert(arr))
assert result.same_as(arr)
- @requires_py39
def test_list_passthrough(self) -> None:
"""ffi.List input passes through unchanged."""
lst = _tvm_ffi.List([1, 2, 3])
result = _to_py_class_value(A(list[int]).convert(lst))
assert result.same_as(lst)
- @requires_py39
def test_array_subclass_passthrough(self) -> None:
"""ffi.Array subclasses pass through unchanged."""
@@ -4472,7 +4278,6 @@ class TestConvertReturnFFIContainers:
result = _to_py_class_value(A(tuple[int, ...]).convert(arr))
assert result.same_as(arr)
- @requires_py39
def test_list_subclass_passthrough(self) -> None:
"""ffi.List subclasses pass through unchanged."""
@@ -4483,7 +4288,6 @@ class TestConvertReturnFFIContainers:
result = _to_py_class_value(A(list[int]).convert(lst))
assert result.same_as(lst)
- @requires_py39
def test_map_subclass_passthrough(self) -> None:
"""ffi.Map subclasses pass through unchanged for Map[Any, Any]."""
@@ -4494,7 +4298,6 @@ class TestConvertReturnFFIContainers:
result = _to_py_class_value(A(tvm_ffi.Map[typing.Any,
typing.Any]).convert(m))
assert result.same_as(m)
- @requires_py39
def test_dict_subclass_passthrough(self) -> None:
"""ffi.Dict subclasses pass through unchanged for Dict[Any, Any]."""
@@ -4505,7 +4308,6 @@ class TestConvertReturnFFIContainers:
result = _to_py_class_value(A(dict[typing.Any, typing.Any]).convert(d))
assert result.same_as(d)
- @requires_py39
def test_nested_array_convert(self) -> None:
"""Nested array conversion."""
result = _to_py_class_value(A(tuple[tuple[int, ...],
...]).convert([[1, 2], [3, 4]]))
@@ -4568,25 +4370,21 @@ class TestConvertToFFITypes:
result = _to_py_class_value(A(Callable).convert(lambda x: x))
assert isinstance(result, tvm_ffi.core.Function)
- @requires_py39
def test_array_is_ffi_array(self) -> None:
"""Array[int] converts to tvm_ffi.Array."""
result = _to_py_class_value(A(tuple[int, ...]).convert([1, 2]))
assert isinstance(result, _tvm_ffi.Array)
- @requires_py39
def test_list_is_ffi_list(self) -> None:
"""List[int] converts to tvm_ffi.List."""
result = _to_py_class_value(A(list[int]).convert([1, 2]))
assert isinstance(result, _tvm_ffi.List)
- @requires_py39
def test_map_is_ffi_map(self) -> None:
"""Map[str, int] converts to tvm_ffi.Map."""
result = _to_py_class_value(A(tvm_ffi.Map[str, int]).convert({"a": 1}))
assert isinstance(result, _tvm_ffi.Map)
- @requires_py39
def test_dict_is_ffi_dict(self) -> None:
"""Dict[str, int] converts to tvm_ffi.Dict."""
result = _to_py_class_value(A(dict[str, int]).convert({"a": 1}))