From f4d465e0866905b02ad2a29be8fba31364164b1d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 16 Jun 2023 10:48:21 +0200 Subject: [PATCH] chore: drop Python 3.7 --- .github/ISSUE_TEMPLATE/bug.md | 2 +- .github/workflows/ci.yml | 36 +--- playwright/_impl/_driver.py | 17 -- playwright/sync_api/_context_manager.py | 15 -- .../sync_api/_py37ThreadedChildWatcher.py | 166 ------------------ pyproject.toml | 4 +- setup.py | 3 +- 7 files changed, 5 insertions(+), 238 deletions(-) delete mode 100644 playwright/sync_api/_py37ThreadedChildWatcher.py diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 6580e2a32..52ebde9e5 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -10,7 +10,7 @@ assignees: '' **Context:** - Playwright Version: [what Playwright version do you use?] - Operating System: [e.g. Windows, Linux or Mac] -- Python version: [e.g. 3.7, 3.9] +- Python version: [e.g. 3.8, 3.9] - Browser: [e.g. All, Chromium, Firefox, WebKit] - Extra: [any specific details about your environment] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f55451700..48c8a7521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,34 +43,13 @@ jobs: build: name: Build timeout-minutes: 45 - env: - DEBUG: pw:* - DEBUG_FILE: pw-log.txt strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: [3.7, 3.8] + python-version: [3.8, 3.9] browser: [chromium, firefox, webkit] include: - - os: ubuntu-latest - python-version: 3.9 - browser: chromium - - os: windows-latest - python-version: 3.9 - browser: chromium - - os: macos-latest - python-version: 3.9 - browser: chromium - - os: macos-11.0 - python-version: 3.9 - browser: chromium - - os: macos-11.0 - python-version: 3.9 - browser: firefox - - os: macos-11.0 - python-version: 3.9 - browser: webkit - os: ubuntu-latest python-version: '3.10' browser: chromium @@ -129,18 +108,10 @@ jobs: - name: Test Async API if: matrix.os == 'ubuntu-latest' run: xvfb-run pytest tests/async --browser=${{ matrix.browser }} --timeout 90 - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: ${{ matrix.browser }}-${{ matrix.os }}-${{ matrix.python-version }} - path: pw-log.txt test-stable: name: Stable timeout-minutes: 45 - env: - DEBUG: pw:* - DEBUG_FILE: pw-log.txt strategy: fail-fast: false matrix: @@ -179,11 +150,6 @@ jobs: - name: Test Async API if: matrix.os == 'ubuntu-latest' run: xvfb-run pytest tests/async --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90 - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: ${{ matrix.browser-channel }}-${{ matrix.os }} - path: pw-log.txt build-conda: name: Conda Build diff --git a/playwright/_impl/_driver.py b/playwright/_impl/_driver.py index f3b911f48..d8004d296 100644 --- a/playwright/_impl/_driver.py +++ b/playwright/_impl/_driver.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import asyncio import inspect import os import sys @@ -30,22 +29,6 @@ def compute_driver_executable() -> Path: return package_path / "driver" / "playwright.sh" -if sys.version_info.major == 3 and sys.version_info.minor == 7: - if sys.platform == "win32": - # Use ProactorEventLoop in 3.7, which is default in 3.8 - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - else: - # Prevent Python 3.7 from throwing on Linux: - # RuntimeError: Cannot add child handler, the child watcher does not have a loop attached - asyncio.get_event_loop() - try: - asyncio.get_child_watcher() - except Exception: - # uvloop does not support child watcher - # see https://github.com/microsoft/playwright-python/issues/582 - pass - - def get_driver_env() -> dict: env = os.environ.copy() env["PW_LANG_NAME"] = "python" diff --git a/playwright/sync_api/_context_manager.py b/playwright/sync_api/_context_manager.py index 4249a1fa1..9813f8920 100644 --- a/playwright/sync_api/_context_manager.py +++ b/playwright/sync_api/_context_manager.py @@ -13,7 +13,6 @@ # limitations under the License. import asyncio -import sys from typing import TYPE_CHECKING, Any, Optional, cast from greenlet import greenlet @@ -50,20 +49,6 @@ def __enter__(self) -> SyncPlaywright: Please use the Async API instead.""" ) - # In Python 3.7, asyncio.Process.wait() hangs because it does not use ThreadedChildWatcher - # which is used in Python 3.8+. This is unix specific and also takes care about - # cleaning up zombie processes. See https://bugs.python.org/issue35621 - if ( - sys.version_info[0] == 3 - and sys.version_info[1] == 7 - and sys.platform != "win32" - and isinstance(asyncio.get_child_watcher(), asyncio.SafeChildWatcher) - ): - from ._py37ThreadedChildWatcher import ThreadedChildWatcher # type: ignore - - self._watcher = ThreadedChildWatcher() - asyncio.set_child_watcher(self._watcher) # type: ignore - # Create a new fiber for the protocol dispatcher. It will be pumping events # until the end of times. We will pass control to that fiber every time we # block while waiting for a response. diff --git a/playwright/sync_api/_py37ThreadedChildWatcher.py b/playwright/sync_api/_py37ThreadedChildWatcher.py deleted file mode 100644 index cd121267f..000000000 --- a/playwright/sync_api/_py37ThreadedChildWatcher.py +++ /dev/null @@ -1,166 +0,0 @@ -# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -# -------------------------------------------- -# -# 1. This LICENSE AGREEMENT is between the Python Software Foundation -# ("PSF"), and the Individual or Organization ("Licensee") accessing and -# otherwise using this software ("Python") in source or binary form and -# its associated documentation. -# -# 2. Subject to the terms and conditions of this License Agreement, PSF hereby -# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -# analyze, test, perform and/or display publicly, prepare derivative works, -# distribute, and otherwise use Python alone or in any derivative version, -# provided, however, that PSF's License Agreement and PSF's notice of copyright, -# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; -# All Rights Reserved" are retained in Python alone or in any derivative version -# prepared by Licensee. -# -# 3. In the event Licensee prepares a derivative work that is based on -# or incorporates Python or any part thereof, and wants to make -# the derivative work available to others as provided herein, then -# Licensee hereby agrees to include in any such work a brief summary of -# the changes made to Python. -# -# 4. PSF is making Python available to Licensee on an "AS IS" -# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -# INFRINGE ANY THIRD PARTY RIGHTS. -# -# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. -# -# 6. This License Agreement will automatically terminate upon a material -# breach of its terms and conditions. -# -# 7. Nothing in this License Agreement shall be deemed to create any -# relationship of agency, partnership, or joint venture between PSF and -# Licensee. This License Agreement does not grant permission to use PSF -# trademarks or trade name in a trademark sense to endorse or promote -# products or services of Licensee, or any third party. -# -# 8. By copying, installing or otherwise using Python, Licensee -# agrees to be bound by the terms and conditions of this License -# Agreement. -# -# type: ignore - -import itertools -import os -import threading -import warnings -from asyncio import AbstractChildWatcher, events -from asyncio.log import logger - - -class ThreadedChildWatcher(AbstractChildWatcher): - """Threaded child watcher implementation. - The watcher uses a thread per process - for waiting for the process finish. - It doesn't require subscription on POSIX signal - but a thread creation is not free. - The watcher has O(1) complexity, its performance doesn't depend - on amount of spawn processes. - """ - - def __init__(self): - self._pid_counter = itertools.count(0) - self._threads = {} - - def is_active(self): - return True - - def close(self): - self._join_threads() - - def _join_threads(self): - """Internal: Join all non-daemon threads""" - threads = [ - thread - for thread in list(self._threads.values()) - if thread.is_alive() and not thread.daemon - ] - for thread in threads: - thread.join() - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - def __del__(self, _warn=warnings.warn): - threads = [ - thread for thread in list(self._threads.values()) if thread.is_alive() - ] - if threads: - _warn( - f"{self.__class__} has registered but not finished child processes", - ResourceWarning, - source=self, - ) - - def add_child_handler(self, pid, callback, *args): - loop = events.get_running_loop() - thread = threading.Thread( - target=self._do_waitpid, - name=f"waitpid-{next(self._pid_counter)}", - args=(loop, pid, callback, args), - daemon=True, - ) - self._threads[pid] = thread - thread.start() - - def remove_child_handler(self, pid): - # asyncio never calls remove_child_handler() !!! - # The method is no-op but is implemented because - # abstract base classes requires it - return True - - def attach_loop(self, loop): - pass - - def _do_waitpid(self, loop, expected_pid, callback, args): - assert expected_pid > 0 - - try: - pid, status = os.waitpid(expected_pid, 0) - except ChildProcessError: - # The child process is already reaped - # (may happen if waitpid() is called elsewhere). - pid = expected_pid - returncode = 255 - logger.warning( - "Unknown child process pid %d, will report returncode 255", pid - ) - else: - returncode = _compute_returncode(status) - if loop.get_debug(): - logger.debug( - "process %s exited with returncode %s", expected_pid, returncode - ) - - if loop.is_closed(): - logger.warning("Loop %r that handles pid %r is closed", loop, pid) - else: - loop.call_soon_threadsafe(callback, pid, returncode, *args) - - self._threads.pop(expected_pid) - - -def _compute_returncode(status): - if os.WIFSIGNALED(status): - # The child process died because of a signal. - return -os.WTERMSIG(status) - elif os.WIFEXITED(status): - # The child process exited (e.g sys.exit()). - return os.WEXITSTATUS(status) - else: - # The child exited, but we don't understand its status. - # This shouldn't happen, but if it does, let's just - # return that status; perhaps that helps debug it. - return status diff --git a/pyproject.toml b/pyproject.toml index 1a2ac2e51..2c8d76843 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ asyncio_mode = "auto" [tool.mypy] ignore_missing_imports = true -python_version = "3.7" +python_version = "3.8" warn_unused_ignores = false warn_redundant_casts = true warn_unused_configs = true @@ -32,7 +32,7 @@ profile = "black" [tool.pyright] include = ["playwright", "tests/sync"] ignore = ["tests/async/", "scripts/", "examples/"] -pythonVersion = "3.7" +pythonVersion = "3.8" reportMissingImports = false reportTypedDictNotRequiredAccess = false reportCallInDefaultInitializer = true diff --git a/setup.py b/setup.py index 7d040e609..02f705604 100644 --- a/setup.py +++ b/setup.py @@ -221,7 +221,6 @@ def _download_and_extract_local_driver( "Topic :: Internet :: WWW/HTTP :: Browsers", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -229,7 +228,7 @@ def _download_and_extract_local_driver( "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], - python_requires=">=3.7", + python_requires=">=3.8", cmdclass={"bdist_wheel": PlaywrightBDistWheelCommand}, use_scm_version={ "version_scheme": "post-release",