From 445fd15f769f966cb7df0d0ed0c4cfbe59a7852e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 16 Dec 2019 08:29:19 +0200 Subject: [PATCH] Drop Python 3.6 support (#29978) --- .coveragerc | 1 - .readthedocs.yml | 2 +- .travis.yml | 10 ++-- azure-pipelines-ci.yml | 6 +-- homeassistant/__main__.py | 14 ++--- homeassistant/const.py | 6 +-- homeassistant/monkey_patch.py | 73 --------------------------- homeassistant/package_constraints.txt | 3 +- homeassistant/util/async_.py | 21 +------- pyproject.toml | 2 +- requirements_all.txt | 1 - script/gen_requirements_all.py | 2 +- setup.cfg | 5 +- setup.py | 1 - tests/util/test_async.py | 7 +-- 15 files changed, 19 insertions(+), 135 deletions(-) delete mode 100644 homeassistant/monkey_patch.py diff --git a/.coveragerc b/.coveragerc index c6e7182d326..e16a622cc61 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,7 +5,6 @@ omit = homeassistant/__main__.py homeassistant/helpers/signal.py homeassistant/helpers/typing.py - homeassistant/monkey_patch.py homeassistant/scripts/*.py homeassistant/util/async.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 923a03f03dd..0303f84d51c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,7 +4,7 @@ build: image: latest python: - version: 3.6 + version: 3.7 setup_py_install: true requirements_file: requirements_docs.txt diff --git a/.travis.yml b/.travis.yml index 2660d805726..6add8c15bfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,13 @@ addons: matrix: fast_finish: true include: - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=lint - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30 - - python: "3.6.1" + - python: "3.7.0" env: TOXENV=typing - - python: "3.6.1" - env: TOXENV=py36 - - python: "3.7" + - python: "3.7.0" env: TOXENV=py37 cache: diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 5a289cbbc70..464b1079957 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -14,8 +14,6 @@ pr: resources: containers: - - container: 36 - image: homeassistant/ci-azure:3.6 - container: 37 image: homeassistant/ci-azure:3.7 repositories: @@ -25,7 +23,7 @@ resources: endpoint: 'home-assistant' variables: - name: PythonMain - value: '36' + value: '37' - group: codecov stages: @@ -108,8 +106,6 @@ stages: strategy: maxParallel: 3 matrix: - Python36: - python.container: '36' Python37: python.container: '37' container: $[ variables['python.container'] ] diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index bf3042c3f88..bcc97252255 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,5 +1,6 @@ """Start Home Assistant.""" import argparse +import asyncio import os import platform import subprocess @@ -7,7 +8,6 @@ import sys import threading from typing import TYPE_CHECKING, Any, Dict, List -from homeassistant import monkey_patch from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ if TYPE_CHECKING: @@ -16,7 +16,6 @@ if TYPE_CHECKING: def set_loop() -> None: """Attempt to use different loop.""" - import asyncio from asyncio.events import BaseDefaultEventLoopPolicy if sys.platform == "win32": @@ -345,11 +344,6 @@ def main() -> int: """Start Home Assistant.""" validate_python() - monkey_patch_needed = sys.version_info[:3] < (3, 6, 3) - if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1": - monkey_patch.disable_c_asyncio() - monkey_patch.patch_weakref_tasks() - set_loop() # Run a simple daemon runner process on Windows to handle restarts @@ -383,13 +377,11 @@ def main() -> int: if args.pid_file: write_pid(args.pid_file) - from homeassistant.util.async_ import asyncio_run - - exit_code = asyncio_run(setup_and_run_hass(config_dir, args)) + exit_code = asyncio.run(setup_and_run_hass(config_dir, args)) if exit_code == RESTART_EXIT_CODE and not args.runner: try_to_restart() - return exit_code # type: ignore + return exit_code if __name__ == "__main__": diff --git a/homeassistant/const.py b/homeassistant/const.py index ba7e7a66126..15dc5a099bc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -4,10 +4,10 @@ MINOR_VERSION = 104 PATCH_VERSION = "0.dev0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) -REQUIRED_PYTHON_VER = (3, 6, 1) +REQUIRED_PYTHON_VER = (3, 7, 0) # Truthy date string triggers showing related deprecation warning messages. -REQUIRED_NEXT_PYTHON_VER = (3, 7, 0) -REQUIRED_NEXT_PYTHON_DATE = "December 15, 2019" +REQUIRED_NEXT_PYTHON_VER = (3, 8, 0) +REQUIRED_NEXT_PYTHON_DATE = "" # Format for platform files PLATFORM_FORMAT = "{platform}.{domain}" diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py deleted file mode 100644 index c6e2e66ab13..00000000000 --- a/homeassistant/monkey_patch.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Monkey patch Python to work around issues causing segfaults. - -Under heavy threading operations that schedule calls into -the asyncio event loop, Task objects are created. Due to -a bug in Python, GC may have an issue when switching between -the threads and objects with __del__ (which various components -in HASS have). - -This monkey-patch removes the weakref.Weakset, and replaces it -with an object that ignores the only call utilizing it (the -Task.__init__ which calls _all_tasks.add(self)). It also removes -the __del__ which could trigger the future objects __del__ at -unpredictable times. - -The side-effect of this manipulation of the Task is that -Task.all_tasks() is no longer accurate, and there will be no -warning emitted if a Task is GC'd while in use. - -Related Python bugs: - - https://bugs.python.org/issue26617 -""" -import sys -from typing import Any - - -def patch_weakref_tasks() -> None: - """Replace weakref.WeakSet to address Python 3 bug.""" - # pylint: disable=no-self-use, protected-access - import asyncio.tasks - - class IgnoreCalls: - """Ignore add calls.""" - - def add(self, other: Any) -> None: - """No-op add.""" - return - - asyncio.tasks.Task._all_tasks = IgnoreCalls() # type: ignore - try: - del asyncio.tasks.Task.__del__ - except: # noqa: E722 pylint: disable=bare-except - pass - - -def disable_c_asyncio() -> None: - """Disable using C implementation of asyncio. - - Required to be able to apply the weakref monkey patch. - - Requires Python 3.6+. - """ - - class AsyncioImportFinder: - """Finder that blocks C version of asyncio being loaded.""" - - PATH_TRIGGER = "_asyncio" - - def __init__(self, path_entry: str) -> None: - if path_entry != self.PATH_TRIGGER: - raise ImportError() - - def find_module(self, fullname: str, path: Any = None) -> None: - """Find a module.""" - if fullname == self.PATH_TRIGGER: - raise ModuleNotFoundError() - - sys.path_hooks.append(AsyncioImportFinder) - sys.path.insert(0, AsyncioImportFinder.PATH_TRIGGER) - - try: - import _asyncio # noqa: F401 - except ImportError: - pass diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index afc92f92ef7..c0b7933fcca 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -7,7 +7,6 @@ async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 certifi>=2019.11.28 -contextvars==2.4;python_version<"3.7" cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 @@ -29,7 +28,7 @@ zeroconf==0.24.0 pycryptodome>=3.6.6 -# Breaks Python 3.6 and is not needed for our supported Python versions +# Not needed for our supported Python versions enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index b1d3a7dd8e7..212c2bff910 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,33 +1,14 @@ """Asyncio backports for Python 3.6 compatibility.""" -import asyncio from asyncio import coroutines, ensure_future from asyncio.events import AbstractEventLoop import concurrent.futures import logging import threading -from typing import Any, Awaitable, Callable, Coroutine, TypeVar +from typing import Any, Callable, Coroutine _LOGGER = logging.getLogger(__name__) -try: - # pylint: disable=invalid-name - asyncio_run = asyncio.run # type: ignore -except AttributeError: - _T = TypeVar("_T") - - def asyncio_run(main: Awaitable[_T], *, debug: bool = False) -> _T: - """Minimal re-implementation of asyncio.run (since 3.7).""" - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.set_debug(debug) - try: - return loop.run_until_complete(main) - finally: - asyncio.set_event_loop(None) - loop.close() - - def fire_coroutine_threadsafe(coro: Coroutine, loop: AbstractEventLoop) -> None: """Submit a coroutine object to a given event loop. diff --git a/pyproject.toml b/pyproject.toml index 7a75060c8e9..7c0c5eeb433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [tool.black] -target-version = ["py36", "py37", "py38"] +target-version = ["py37", "py38"] exclude = 'generated' diff --git a/requirements_all.txt b/requirements_all.txt index 33cc5c725a8..f0b547b4a25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,6 @@ async_timeout==3.0.1 attrs==19.3.0 bcrypt==3.1.7 certifi>=2019.11.28 -contextvars==2.4;python_version<"3.7" importlib-metadata==0.23 jinja2>=2.10.3 PyJWT==1.7.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index c18b51ba5d1..0dfefc958c3 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -58,7 +58,7 @@ CONSTRAINT_PATH = os.path.join( CONSTRAINT_BASE = """ pycryptodome>=3.6.6 -# Breaks Python 3.6 and is not needed for our supported Python versions +# Not needed for our supported Python versions enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome diff --git a/setup.cfg b/setup.cfg index bb2b1652ffa..f9e9852812c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ classifier = Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: OS Independent - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Topic :: Home Automation @@ -57,7 +56,7 @@ forced_separate = tests combine_as_imports = true [mypy] -python_version = 3.6 +python_version = 3.7 ignore_errors = true follow_imports = silent ignore_missing_imports = true @@ -65,7 +64,7 @@ warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.loader,homeassistant.__main__,homeassistant.monkey_patch,homeassistant.requirements,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] +[mypy-homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*] ignore_errors = false check_untyped_defs = true disallow_incomplete_defs = true diff --git a/setup.py b/setup.py index f9cb24d1e91..720406ab91b 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ REQUIRES = [ "attrs==19.3.0", "bcrypt==3.1.7", "certifi>=2019.11.28", - 'contextvars==2.4;python_version<"3.7"', "importlib-metadata==0.23", "jinja2>=2.10.3", "PyJWT==1.7.1", diff --git a/tests/util/test_async.py b/tests/util/test_async.py index ec16fa2ae67..098b04a3048 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,6 +1,5 @@ """Tests for async util methods from Python source.""" import asyncio -import sys from unittest import TestCase from unittest.mock import MagicMock, patch @@ -112,11 +111,7 @@ class RunThreadsafeTests(TestCase): """Wait 0.05 second and return a + b.""" yield from asyncio.sleep(0.05, loop=self.loop) if cancel: - if sys.version_info[:2] >= (3, 7): - current_task = asyncio.current_task - else: - current_task = asyncio.tasks.Task.current_task - current_task(self.loop).cancel() + asyncio.current_task(self.loop).cancel() yield return self.add_callback(a, b, fail, invalid)