diff --git a/.github/workflows/run-core-traits-tests.yml b/.github/workflows/run-core-traits-tests.yml index aeb66484c..cce6ffe65 100644 --- a/.github/workflows/run-core-traits-tests.yml +++ b/.github/workflows/run-core-traits-tests.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-alpha.3'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/run-traits-tests.yml b/.github/workflows/run-traits-tests.yml index 4b8aee6a1..0dbd13444 100644 --- a/.github/workflows/run-traits-tests.yml +++ b/.github/workflows/run-traits-tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test-from-pypi.yml b/.github/workflows/test-from-pypi.yml index b5c200c4c..e4b653715 100644 --- a/.github/workflows/test-from-pypi.yml +++ b/.github/workflows/test-from-pypi.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] python-architecture: [x86, x64] exclude: - os: macos-latest @@ -68,7 +68,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] python-architecture: [x86, x64] exclude: - os: macos-latest diff --git a/README.rst b/README.rst index cc6ffa5df..f3b105a44 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ inherited by any subclass derived from the class. Dependencies ------------ -Traits requires Python >= 3.6. +Traits requires Python >= 3.8. Traits has the following optional dependencies: diff --git a/docs/source/traits_user_manual/intro.rst b/docs/source/traits_user_manual/intro.rst index d556f3130..35d56fd64 100644 --- a/docs/source/traits_user_manual/intro.rst +++ b/docs/source/traits_user_manual/intro.rst @@ -164,7 +164,7 @@ where the Traits package has been used, it has proven valuable for enhancing programmers' ability to understand code, during both concurrent development and maintenance. -The Traits |version| package works with versions 3.5 and later of +The Traits |version| package works with versions 3.8 and later of Python. It is similar in some ways to the Python property language feature. Standard Python properties provide the similar capabilities to the Traits package, but with more work on the part of the programmer. diff --git a/etstool.py b/etstool.py index 1aa8b4384..76b7917ff 100644 --- a/etstool.py +++ b/etstool.py @@ -51,7 +51,7 @@ python etstool.py test-all -Currently supported runtime values are ``3.6``. Not all +Currently supported runtime values are ``3.8``. Not all combinations of runtimes will work, but the tasks will fail with a clear error if that is the case. @@ -93,22 +93,20 @@ "enthought_sphinx_theme", "flake8", "flake8_ets", + "lark-parser", + "mypy", "numpy", + "pyside6", "Sphinx", "sphinx_copybutton", "traitsui", } # Dependencies on the Python runtime. -runtime_dependencies = { - "3.6": {"pyqt5", "lark_parser", "mypy"}, - "3.8": {"pyside6"}, -} +runtime_dependencies = {} # Dependencies that need to be installed from PyPI -pypi_dependencies = { - "3.8": {"lark-parser", "mypy"} -} +pypi_dependencies = {} # Dependencies we install from source for testing source_dependencies = {"traitsui"} @@ -118,7 +116,7 @@ "gnureadline", } -supported_runtimes = ["3.6", "3.8"] +supported_runtimes = ["3.8"] default_runtime = "3.8" github_url_fmt = "git+http://github.com/enthought/{0}.git#egg={0}" diff --git a/setup.py b/setup.py index cdef17aac..0898a7b95 100644 --- a/setup.py +++ b/setup.py @@ -276,13 +276,6 @@ def get_long_description(): Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - 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 :: Implementation :: CPython Topic :: Scientific/Engineering Topic :: Software Development @@ -301,11 +294,6 @@ def get_long_description(): "Documentation": "https://docs.enthought.com/traits", "Source Code": "https://github.com/enthought/traits", }, - install_requires=[ - # We need typing-extensions for SupportsIndex; once we no longer - # support Python < 3.8, we can drop this requirement. - 'typing-extensions; python_version<"3.8"', - ], extras_require={ "docs": [ "enthought-sphinx-theme", @@ -317,11 +305,9 @@ def get_long_description(): "flake8", "flake8-ets", "mypy", - # NumPy is not yet available for Python 3.12, but that should be - # fixed soon: https://github.com/numpy/numpy/issues/23808 - "numpy; python_version < '3.12'", + "numpy", "pyface", - "PySide6; python_version >= '3.7' and python_version < '3.12'", + "PySide6", "setuptools", "Sphinx>=2.1.0", "traitsui", @@ -359,6 +345,6 @@ def get_long_description(): }, license="BSD", packages=setuptools.find_packages(include=["traits", "traits.*"]), - python_requires=">=3.6", + python_requires=">=3.8", zip_safe=False, ) diff --git a/traits/ctraits.c b/traits/ctraits.c index 6be022a45..25639bdef 100644 --- a/traits/ctraits.c +++ b/traits/ctraits.c @@ -808,18 +808,10 @@ static void has_traits_dealloc(has_traits_object *obj) { PyObject_GC_UnTrack(obj); -#if PY_VERSION_HEX < 0x03080000 - Py_TRASHCAN_SAFE_BEGIN(obj); -#else Py_TRASHCAN_BEGIN(obj, has_traits_dealloc); -#endif has_traits_clear(obj); Py_TYPE(obj)->tp_free((PyObject *)obj); -#if PY_VERSION_HEX < 0x03080000 - Py_TRASHCAN_SAFE_END(obj); -#else Py_TRASHCAN_END -#endif } /*----------------------------------------------------------------------------- @@ -3400,10 +3392,9 @@ validate_trait_integer( Here float-like means: - is an instance of float, or - - can be converted to a float via its type's __float__ method - - Note: as of Python 3.8, objects having an __index__ method but - no __float__ method can also be converted to float. + - can be converted to a float via its type's __float__ method, or + - can be converted to an int (and from there to a float) via its type's + __index__ method */ static PyObject * @@ -3470,8 +3461,7 @@ validate_trait_float( - can be converted to a a complex number via its type's __complex__ method, or - can be converted to a float via its type's __float__ method, or - - (for Python >= 3.8) can be converted to an int via its type's __index__ - method. + - can be converted to an int via its type's __index__ method. In other words, these should be exactly the Python objects that are accepted by the standard functions in the cmath module. @@ -3505,8 +3495,8 @@ _ctraits_validate_complex_number(PyObject *self, PyObject *value) /*----------------------------------------------------------------------------- | Verifies that a Python value is convertible to a complex number. | -| Will convert anything whose type has a __complex__, __float__ or (for -| Python >= 3.8) __index__ method to a Python complex number. Returns a Python +| Will convert anything whose type has a __complex__, __float__ or +| __index__ method to a Python complex number. Returns a Python | object of exact type "complex". Raises TraitError with a suitable message if | the given value isn't convertible to a complex number. | diff --git a/traits/tests/test_complex.py b/traits/tests/test_complex.py index 99be4ad97..990c97849 100644 --- a/traits/tests/test_complex.py +++ b/traits/tests/test_complex.py @@ -26,16 +26,6 @@ def __index__(self): return self._value -# Python versions < 3.8 don't support conversion of something with __index__ -# to complex. -try: - complex(IntegerLike(3)) -except TypeError: - complex_accepts_index = False -else: - complex_accepts_index = True - - class FloatLike: def __init__(self, value): self._value = value @@ -108,10 +98,6 @@ class ComplexSubclass(complex): self.assertIs(type(a.value), complex) self.assertEqual(a.value, complex(5.0, 12.0)) - @unittest.skipUnless( - complex_accepts_index, - "complex does not support __index__ for this Python version", - ) def test_accepts_integer_like(self): a = self.test_class() a.value = IntegerLike(3) diff --git a/traits/tests/test_extended_trait_change.py b/traits/tests/test_extended_trait_change.py index 6ff0c2045..a3cba3505 100644 --- a/traits/tests/test_extended_trait_change.py +++ b/traits/tests/test_extended_trait_change.py @@ -586,10 +586,9 @@ def test_instance_list_value(self): AssertionError, msg="Behavior of a bug (#537) is not reproduced."): # Handlers with arguments are unexpectedly called, but one of the - # handlers fails, leading to the rest of the handlers - # not to be called. Actual behavior depends on dictionary ordering - # (Python <3.6) or the order of handlers defined in - # InstanceValueListener (Python >= 3.6) + # handlers fails, leading to the rest of the handlers not to be + # called. Actual behavior depends on the order of handlers defined + # in InstanceValueListener. self.assertEqual(inst.calls, {0: 1, 1: 0, 2: 0, 3: 0, 4: 0}) self.assertEqual(inst.ref.value, [0, 1, 2, 3]) @@ -646,10 +645,9 @@ def test_instance_dict_value(self): AssertionError, msg="Behavior of a bug (#537) is not reproduced."): # Handlers with arguments are unexpectedly called, but one of the - # handlers fails, leading to the rest of the handlers - # not to be called. Actual behavior depends on dictionary ordering - # (Python <3.6) or the order of handlers defined in - # InstanceValueListener (Python >= 3.6) + # handlers fails, leading to the rest of the handlers not to be + # called. Actual behavior depends on the order of handlers defined + # in InstanceValueListener. self.assertEqual(inst.calls, {0: 1, 1: 0, 2: 0, 3: 0, 4: 0}) self.assertEqual(inst.ref.value, {0: 0, 1: 1, 2: 2, 3: 3}) diff --git a/traits/tests/test_float.py b/traits/tests/test_float.py index 86b1921c0..43ae67b4b 100644 --- a/traits/tests/test_float.py +++ b/traits/tests/test_float.py @@ -26,16 +26,6 @@ def __index__(self): return self._value -# Python versions < 3.8 don't support conversion of something with __index__ -# to float. -try: - float(IntegerLike(3)) -except TypeError: - float_accepts_index = False -else: - float_accepts_index = True - - class MyFloat(object): def __init__(self, value): self._value = value @@ -111,10 +101,6 @@ def test_accepts_int(self): self.assertIs(type(a.value_or_none), float) self.assertEqual(a.value_or_none, 2.0) - @unittest.skipUnless( - float_accepts_index, - "float does not support __index__ for this Python version", - ) def test_accepts_integer_like(self): a = self.test_class() a.value = IntegerLike(3) diff --git a/traits/tests/test_map.py b/traits/tests/test_map.py index 2d59511ae..7601b03cc 100644 --- a/traits/tests/test_map.py +++ b/traits/tests/test_map.py @@ -71,8 +71,8 @@ class Person(HasTraits): p = Person() - # Since we're using Python >= 3.6, we can rely on dictionaries - # being ordered, and then the default is predictable. + # Default is predictable because in all supported versions of + # Python, dictionary insertion order is preserved. self.assertEqual(p.married, "yes") self.assertEqual(p.married_, 1) @@ -86,8 +86,8 @@ class Person(HasTraits): shadow_value = p.married_ primary_value = p.married - # For Python >= 3.6, dictionary ordering and hence the default - # value are predictable. + # In all recent Python versions, dictionary ordering is deterministic + # and hence the default value is predictable. self.assertEqual(primary_value, "yes") self.assertEqual(shadow_value, 1) diff --git a/traits/tests/test_prefix_map.py b/traits/tests/test_prefix_map.py index 414f3b97c..378f4e844 100644 --- a/traits/tests/test_prefix_map.py +++ b/traits/tests/test_prefix_map.py @@ -63,8 +63,8 @@ class Person(HasTraits): p = Person() - # Since we're using Python >= 3.6, we can rely on dictionaries - # being ordered, and then the default is predictable. + # In all recent Python versions, dictionary ordering is deterministic + # and hence the default value is predictable. self.assertEqual(p.married, "yes") self.assertEqual(p.married_, 1) diff --git a/traits/tests/test_regression.py b/traits/tests/test_regression.py index 96a43a227..fc51ff0a8 100644 --- a/traits/tests/test_regression.py +++ b/traits/tests/test_regression.py @@ -291,10 +291,7 @@ def test_subclasses_weakref(self): _create_subclass() gc.collect() - # In Python < 3.6, we can end up seeing the same subclasses but in - # a different order, so use assertCountEqual rather than assertEqual. - # Ref: enthought/traits#1282. - self.assertCountEqual(previous_subclasses, HasTraits.__subclasses__()) + self.assertEqual(previous_subclasses, HasTraits.__subclasses__()) def test_leaked_property_tuple(self): """ the property ctrait constructor shouldn't leak a tuple. """ diff --git a/traits/trait_base.py b/traits/trait_base.py index fca5c5991..b5161e120 100644 --- a/traits/trait_base.py +++ b/traits/trait_base.py @@ -172,8 +172,11 @@ def safe_contains(value, container): TypeError. In these cases we make the (reasonable) assumption that the value is _not_ contained in the container. """ - # Do a LBYL check for Enums, to avoid the DeprecationWarning issued - # by Python 3.7. Ref: enthought/traits#853. + # Enums have an awkward history here. A containment check of an element + # against an enum.Enum subclass variously returns, raises, and/or warns + # depending on the Python version. So we do a LBYL check to bypass the + # warnings and errors. + # Ref: enthought/traits#853. if isinstance(container, enum.EnumMeta): if not isinstance(value, enum.Enum): return False diff --git a/traits/trait_types.pyi b/traits/trait_types.pyi index a6d2e8793..88f95685d 100644 --- a/traits/trait_types.pyi +++ b/traits/trait_types.pyi @@ -10,7 +10,6 @@ import datetime from pathlib import PurePath as _PurePath -import sys from typing import ( Any as _Any, Callable as _CallableType, @@ -20,6 +19,7 @@ from typing import ( Sequence as _Sequence, Set as _SetType, SupportsFloat, + SupportsIndex, Tuple as _Tuple, Type as _Type, TypeVar, @@ -27,13 +27,6 @@ from typing import ( ) from uuid import UUID as _UUID -# Once we no longer support Python 3.6 or Python 3.7, we can import -# SupportsIndex from typing instead of typing_extensions. -if sys.version_info < (3, 8): - from typing_extensions import SupportsIndex -else: - from typing import SupportsIndex - from .trait_type import _TraitType SetTypes: _Any