diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 964fd149..26dc9a7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,28 +54,23 @@ jobs: name: Unit tests needs: [ pre-commit, towncrier, package ] runs-on: ${{ matrix.platform }} - continue-on-error: ${{ matrix.experimental }} + continue-on-error: false strategy: fail-fast: false matrix: platform: [ # X86-64 runners - "macos-12", "macos-13", + "macos-13", # M1 runners - "macos-14" + "macos-14", "macos-15" ] - python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev" ] - include: - - experimental: false - - python-version: "3.13-dev" - experimental: true - + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ] exclude: - # actions/setup-python doesn't provide Python3.8 or 3.9 for M1. + # actions/setup-python doesn't provide Python 3.9 for M1. - platform: "macos-14" - python-version : "3.8" + python-version : "3.9" - - platform: "macos-14" + - platform: "macos-15" python-version : "3.9" steps: @@ -88,6 +83,7 @@ jobs: uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Get packages uses: actions/download-artifact@v4.1.8 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 208ea3cc..d05af1c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v3.18.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/psf/black-pre-commit-mirror rev: 24.10.0 hooks: diff --git a/changes/529.feature.rst b/changes/529.feature.rst new file mode 100644 index 00000000..9e939324 --- /dev/null +++ b/changes/529.feature.rst @@ -0,0 +1 @@ +Support for Python 3.14 was added. diff --git a/changes/529.removal.rst b/changes/529.removal.rst new file mode 100644 index 00000000..0a50c369 --- /dev/null +++ b/changes/529.removal.rst @@ -0,0 +1 @@ +Python 3.8 is no longer a supported platform. diff --git a/pyproject.toml b/pyproject.toml index bec88c98..c8c9d162 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dynamic = ["version"] name = "rubicon-objc" description = "A bridge between an Objective C runtime environment and Python." readme = "README.rst" -requires-python = ">= 3.8" +requires-python = ">= 3.9" license.text = "New BSD" authors = [ {name="Russell Keith-Magee", email="russell@keith-magee.com"}, @@ -29,12 +29,12 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Objective C", "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", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development", ] @@ -48,9 +48,7 @@ Source = "https://github.com/beeware/rubicon-objc" [project.optional-dependencies] dev = [ - # Pre-commit 3.6.0 deprecated support for Python 3.8 - "pre-commit == 3.5.0 ; python_version < '3.9'", - "pre-commit == 4.0.1 ; python_version >= '3.9'", + "pre-commit == 4.0.1", "pytest == 8.3.3", "setuptools_scm == 8.1.0", "tox == 4.21.2", diff --git a/src/rubicon/objc/ctypes_patch.py b/src/rubicon/objc/ctypes_patch.py index d3eea426..42f1af5b 100644 --- a/src/rubicon/objc/ctypes_patch.py +++ b/src/rubicon/objc/ctypes_patch.py @@ -21,10 +21,10 @@ # This module relies on the layout of a few internal Python and ctypes # structures. Because of this, it's possible (but not all that likely) that # things will break on newer/older Python versions. -if sys.version_info < (3, 6) or sys.version_info >= (3, 14): +if sys.version_info < (3, 6) or sys.version_info >= (3, 15): v = sys.version_info warnings.warn( - "rubicon.objc.ctypes_patch has only been tested with Python 3.6 through 3.13. " + "rubicon.objc.ctypes_patch has only been tested with Python 3.6 through 3.15. " f"You are using Python {v.major}.{v.minor}.{v.micro}. Most likely things will " "work properly, but you may experience crashes if Python's internals have " "changed significantly." diff --git a/src/rubicon/objc/eventloop.py b/src/rubicon/objc/eventloop.py index 86136a30..306111a3 100644 --- a/src/rubicon/objc/eventloop.py +++ b/src/rubicon/objc/eventloop.py @@ -1,10 +1,10 @@ """PEP 3156 event loop based on CoreFoundation.""" import contextvars +import sys import threading from asyncio import ( DefaultEventLoopPolicy, - SafeChildWatcher, coroutines, events, tasks, @@ -16,6 +16,9 @@ from .runtime import load_library, objc_id from .types import CFIndex +if sys.version_info < (3, 14): + from asyncio import SafeChildWatcher + __all__ = [ "EventLoopPolicy", "CocoaLifecycle", @@ -695,30 +698,39 @@ def _new_default_loop(self): loop._policy = self return loop - def _init_watcher(self): - with events._lock: - if self._watcher is None: # pragma: no branch - self._watcher = SafeChildWatcher() - if threading.current_thread() == threading.main_thread(): - self._watcher.attach_loop(self._default_loop) + if sys.version_info < (3, 14): - def get_child_watcher(self): - """Get the watcher for child processes. + def _init_watcher(self): + with events._lock: + if self._watcher is None: # pragma: no branch + self._watcher = SafeChildWatcher() + if threading.current_thread() == threading.main_thread(): + self._watcher.attach_loop(self._default_loop) - If not yet set, a :class:`~asyncio.SafeChildWatcher` object is - automatically created. - """ - if self._watcher is None: - self._init_watcher() + def get_child_watcher(self): + """Get the watcher for child processes. + + If not yet set, a :class:`~asyncio.SafeChildWatcher` object is + automatically created. + + .. note:: + Child watcher support was removed in Python 3.14 + """ + if self._watcher is None: + self._init_watcher() + + return self._watcher - return self._watcher + def set_child_watcher(self, watcher): + """Set the watcher for child processes. - def set_child_watcher(self, watcher): - """Set the watcher for child processes.""" - if self._watcher is not None: - self._watcher.close() + .. note:: + Child watcher support was removed in Python 3.14 + """ + if self._watcher is not None: + self._watcher.close() - self._watcher = watcher + self._watcher = watcher class CFLifecycle: diff --git a/tox.ini b/tox.ini index 2c00e7ed..d0652225 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ extend-ignore = E203, [tox] -envlist = towncrier-check,pre-commit,docs{,-lint,-all},py{38,39,310,311,312,313} +envlist = towncrier-check,pre-commit,docs{,-lint,-all},py{39,310,311,312,313,314} skip_missing_interpreters = true [testenv:pre-commit] @@ -21,7 +21,7 @@ wheel_build_env = .pkg extras = dev commands = pre-commit run --all-files --show-diff-on-failure --color=always -[testenv:py{,38,39,310,311,312,313}] +[testenv:py{,39,310,311,312,313,314}] package = wheel wheel_build_env = .pkg depends = pre-commit