Files
baijiahao_data_crawl/venv/Lib/site-packages/mitmproxy/utils/signals.py
“shengyudong” 322ac74336 2025-12-25 upload
2025-12-25 11:16:59 +08:00

138 lines
4.1 KiB
Python

"""
This module provides signals, which are a simple dispatching system that allows any number of interested parties
to subscribe to events ("signals").
This is similar to the Blinker library (https://pypi.org/project/blinker/), with the following changes:
- provides only a small subset of Blinker's functionality
- supports type hints
- supports async receivers.
"""
from __future__ import annotations
import asyncio
import inspect
import weakref
from collections.abc import Awaitable
from collections.abc import Callable
from typing import Any
from typing import cast
from typing import Generic
from typing import ParamSpec
from typing import TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def make_weak_ref(obj: Any) -> weakref.ReferenceType:
"""
Like weakref.ref(), but using weakref.WeakMethod for bound methods.
"""
if hasattr(obj, "__self__"):
return cast(weakref.ref, weakref.WeakMethod(obj))
else:
return weakref.ref(obj)
# We're running into https://github.com/python/mypy/issues/6073 here,
# which is why the base class is a mixin and not a generic superclass.
class _SignalMixin:
def __init__(self) -> None:
self.receivers: list[weakref.ref[Callable]] = []
def connect(self, receiver: Callable) -> None:
"""
Register a signal receiver.
The signal will only hold a weak reference to the receiver function.
"""
receiver = make_weak_ref(receiver)
self.receivers.append(receiver)
def disconnect(self, receiver: Callable) -> None:
self.receivers = [r for r in self.receivers if r() != receiver]
def notify(self, *args, **kwargs):
cleanup = False
for ref in self.receivers:
r = ref()
if r is not None:
yield r(*args, **kwargs)
else:
cleanup = True
if cleanup:
self.receivers = [r for r in self.receivers if r() is not None]
class _SyncSignal(Generic[P], _SignalMixin):
def connect(self, receiver: Callable[P, None]) -> None:
assert not inspect.iscoroutinefunction(receiver)
super().connect(receiver)
def disconnect(self, receiver: Callable[P, None]) -> None:
super().disconnect(receiver)
def send(self, *args: P.args, **kwargs: P.kwargs) -> None:
for ret in super().notify(*args, **kwargs):
assert ret is None or not inspect.isawaitable(ret)
class _AsyncSignal(Generic[P], _SignalMixin):
def connect(self, receiver: Callable[P, Awaitable[None] | None]) -> None:
super().connect(receiver)
def disconnect(self, receiver: Callable[P, Awaitable[None] | None]) -> None:
super().disconnect(receiver)
async def send(self, *args: P.args, **kwargs: P.kwargs) -> None:
await asyncio.gather(
*[
aws
for aws in super().notify(*args, **kwargs)
if aws is not None and inspect.isawaitable(aws)
]
)
# noinspection PyPep8Naming
def SyncSignal(receiver_spec: Callable[P, None]) -> _SyncSignal[P]:
"""
Create a synchronous signal with the given function signature for receivers.
Example:
s = SyncSignal(lambda event: None) # all receivers must accept a single "event" argument.
def receiver(event):
print(event)
s.connect(receiver)
s.send("foo") # prints foo
s.send(event="bar") # prints bar
def receiver2():
...
s.connect(receiver2) # mypy complains about receiver2 not having the right signature
s2 = SyncSignal(lambda: None) # this signal has no arguments
s2.send()
"""
return cast(_SyncSignal[P], _SyncSignal())
# noinspection PyPep8Naming
def AsyncSignal(receiver_spec: Callable[P, Awaitable[None] | None]) -> _AsyncSignal[P]:
"""
Create an signal that supports both regular and async receivers:
Example:
s = AsyncSignal(lambda event: None)
async def receiver(event):
print(event)
s.connect(receiver)
await s.send("foo") # prints foo
"""
return cast(_AsyncSignal[P], _AsyncSignal())