import asyncio import os import platform import signal import sys from collections.abc import Awaitable from contextlib import contextmanager from enum import Enum from typing import Literal from multidict import CIMultiDict # type: ignore from sanic.helpers import Default from sanic.log import error_logger StartMethod = ( Default | Literal["fork"] | Literal["forkserver"] | Literal["spawn"] ) OS_IS_WINDOWS = os.name == "nt" PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy" UVLOOP_INSTALLED = False PYTHON_314_OR_LATER = sys.version_info >= (3, 14) try: import uvloop # type: ignore # noqa UVLOOP_INSTALLED = True except ImportError: pass # Python 3.11 changed the way Enum formatting works for mixed-in types. if sys.version_info < (3, 11, 0): class StrEnum(str, Enum): pass else: from enum import StrEnum # type: ignore # noqa class UpperStrEnum(StrEnum): """Base class for string enums that are case insensitive.""" def _generate_next_value_(name, start, count, last_values): return name.upper() def __eq__(self, value: object) -> bool: value = str(value).upper() return super().__eq__(value) def __hash__(self) -> int: return hash(self.value) def __str__(self) -> str: return self.value @contextmanager def use_context(method: StartMethod): from sanic import Sanic orig = Sanic.start_method Sanic.start_method = method yield Sanic.start_method = orig def enable_windows_color_support(): import ctypes kernel = ctypes.windll.kernel32 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.debug( "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 [CIMultiDict](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidictproxy) It allows for multiple values for a single key in keeping with the HTTP spec. Also, all keys are *case in-sensitive*. Please checkout [the MultiDict documentation](https://multidict.readthedocs.io/en/stable/multidict.html#multidict) 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("_"): return self.__getattribute__(key) key = key.rstrip("_").replace("_", "-") return ",".join(self.getall(key, [])) def get_all(self, key: str): """Convenience method mapped to ``getall()``.""" return self.getall(key, []) use_trio = sys.argv[0].endswith("hypercorn") and "trio" in sys.argv if use_trio: # pragma: no cover import trio # type: ignore def stat_async(path) -> Awaitable[os.stat_result]: return trio.Path(path).stat() 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 async def open_async(file, mode="r", **kwargs): return aio_open(file, mode, **kwargs) CancelledErrors = tuple([asyncio.CancelledError]) def ctrlc_workaround_for_windows(app): async def stay_active(app): """Asyncio wakeups to allow receiving SIGINT in Python""" while not die: # If someone else stopped the app, just exit if app.state.is_stopping: return # Windows Python blocks signal handlers while the event loop is # waiting for I/O. Frequent wakeups keep interrupts flowing. await asyncio.sleep(0.1) # Can't be called from signal handler, so call it from here app.stop() def ctrlc_handler(sig, frame): nonlocal die if die: raise KeyboardInterrupt("Non-graceful Ctrl+C") die = True die = False signal.signal(signal.SIGINT, ctrlc_handler) app.add_task(stay_active) def clear_function_annotate(*funcs): """Clear __annotate__ on functions for Python 3.14+ pickle compatibility. In Python 3.14, PEP 649 adds __annotate__ to functions with annotations. When methods are used in functools.partial and pickled, the __annotate__ function can cause PicklingError because pickle cannot locate it by name. This function sets __annotate__ to None on the given functions to avoid pickle issues. """ if PYTHON_314_OR_LATER: for func in funcs: if hasattr(func, "__annotate__") and func.__annotate__ is not None: func.__annotate__ = None