From 273825dab67b520ba1d8bc4e920156c9064bfe77 Mon Sep 17 00:00:00 2001 From: Mohammad Almoghrabi Date: Wed, 5 Jul 2023 12:14:47 +0300 Subject: [PATCH] Sanic on pypy (#2682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: L. Kärkkäinen <98187+Tronic@users.noreply.github.com> Co-authored-by: Adam Hopkins Co-authored-by: Adam Hopkins --- .gitignore | 1 + sanic/application/logo.py | 2 +- sanic/application/motd.py | 2 +- sanic/compat.py | 47 ++++++++++++++++++++++++++++++++++----- sanic/helpers.py | 6 +++++ sanic/log.py | 2 +- sanic/mixins/startup.py | 4 ++-- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 1972c53e..2a30c319 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ dist/* pip-wheel-metadata/ .pytest_cache/* .venv/* +venv/* .vscode/* diff --git a/sanic/application/logo.py b/sanic/application/logo.py index d3b0038e..ffedad8b 100644 --- a/sanic/application/logo.py +++ b/sanic/application/logo.py @@ -3,7 +3,7 @@ import sys from os import environ -from sanic.compat import is_atty +from sanic.helpers import is_atty BASE_LOGO = """ diff --git a/sanic/application/motd.py b/sanic/application/motd.py index a4b77999..eb0e3df7 100644 --- a/sanic/application/motd.py +++ b/sanic/application/motd.py @@ -4,7 +4,7 @@ from textwrap import indent, wrap from typing import Dict, Optional from sanic import __version__ -from sanic.compat import is_atty +from sanic.helpers import is_atty from sanic.log import logger diff --git a/sanic/compat.py b/sanic/compat.py index 693b1b78..0c7547b7 100644 --- a/sanic/compat.py +++ b/sanic/compat.py @@ -1,5 +1,6 @@ import asyncio import os +import platform import signal import sys @@ -10,6 +11,7 @@ from typing import Awaitable, Union from multidict import CIMultiDict # type: ignore from sanic.helpers import Default +from sanic.log import error_logger if sys.version_info < (3, 8): # no cov @@ -22,6 +24,7 @@ else: # no cov ] OS_IS_WINDOWS = os.name == "nt" +PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy" UVLOOP_INSTALLED = False try: @@ -73,6 +76,38 @@ def enable_windows_color_support(): kernel.SetConsoleMode(kernel.GetStdHandle(-11), 7) +def pypy_os_module_patch() -> None: + """ + The PyPy os module is missing the 'readlink' function, which causes issues + withaiofiles. This workaround replaces the missing 'readlink' function + with 'os.path.realpath', which serves the same purpose. + """ + if hasattr(os, "readlink"): + error_logger.warning( + "PyPy: Skipping patching of the os module as it appears the " + "'readlink' function has been added." + ) + return + + module = sys.modules["os"] + module.readlink = os.path.realpath # type: ignore + + +def pypy_windows_set_console_cp_patch() -> None: + """ + A patch function for PyPy on Windows that sets the console code page to + UTF-8 encodingto allow for proper handling of non-ASCII characters. This + function uses ctypes to call the Windows API functions SetConsoleCP and + SetConsoleOutputCP to set the code page. + """ + from ctypes import windll # type: ignore + + code: int = windll.kernel32.GetConsoleOutputCP() + if code != 65001: + windll.kernel32.SetConsoleCP(65001) + windll.kernel32.SetConsoleOutputCP(65001) + + class Header(CIMultiDict): """ Container used for both request and response headers. It is a subclass of @@ -86,7 +121,7 @@ class Header(CIMultiDict): `_ for more details about how to use the object. In general, it should work very similar to a regular dictionary. - """ + """ # noqa: E501 def __getattr__(self, key: str) -> str: if key.startswith("_"): @@ -112,6 +147,12 @@ if use_trio: # pragma: no cover open_async = trio.open_file CancelledErrors = tuple([asyncio.CancelledError, trio.Cancelled]) else: + if PYPY_IMPLEMENTATION: + pypy_os_module_patch() + + if OS_IS_WINDOWS: + pypy_windows_set_console_cp_patch() + from aiofiles import open as aio_open # type: ignore from aiofiles.os import stat as stat_async # type: ignore # noqa: F401 @@ -143,7 +184,3 @@ def ctrlc_workaround_for_windows(app): die = False signal.signal(signal.SIGINT, ctrlc_handler) app.add_task(stay_active) - - -def is_atty() -> bool: - return bool(sys.stdout and sys.stdout.isatty()) diff --git a/sanic/helpers.py b/sanic/helpers.py index c5c10ccc..6ed20de4 100644 --- a/sanic/helpers.py +++ b/sanic/helpers.py @@ -1,5 +1,7 @@ """Defines basics of HTTP standard.""" +import sys + from importlib import import_module from inspect import ismodule from typing import Dict @@ -157,6 +159,10 @@ def import_string(module_name, package=None): return obj() +def is_atty() -> bool: + return bool(sys.stdout and sys.stdout.isatty()) + + class Default: """ It is used to replace `None` or `object()` as a sentinel diff --git a/sanic/log.py b/sanic/log.py index fe221c78..013d9c23 100644 --- a/sanic/log.py +++ b/sanic/log.py @@ -5,7 +5,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Dict from warnings import warn -from sanic.compat import is_atty +from sanic.helpers import is_atty # Python 3.11 changed the way Enum formatting works for mixed-in types. diff --git a/sanic/mixins/startup.py b/sanic/mixins/startup.py index e7ddd3ac..3ad1c3e7 100644 --- a/sanic/mixins/startup.py +++ b/sanic/mixins/startup.py @@ -41,9 +41,9 @@ from sanic.application.logo import get_logo from sanic.application.motd import MOTD from sanic.application.state import ApplicationServerInfo, Mode, ServerStage from sanic.base.meta import SanicMeta -from sanic.compat import OS_IS_WINDOWS, StartMethod, is_atty +from sanic.compat import OS_IS_WINDOWS, StartMethod from sanic.exceptions import ServerKilled -from sanic.helpers import Default, _default +from sanic.helpers import Default, _default, is_atty from sanic.http.constants import HTTP from sanic.http.tls import get_ssl_context, process_to_context from sanic.http.tls.context import SanicSSLContext