Skip to content

Commit

Permalink
enable aiosignal dependency and add additional mechanism to wrap th…
Browse files Browse the repository at this point in the history
…e signal on current code
  • Loading branch information
harshanarayana committed Feb 1, 2020
1 parent 333b975 commit d5f6306
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 17 deletions.
15 changes: 15 additions & 0 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
from sanic.testing import SanicASGITestClient, SanicTestClient
from sanic.views import CompositionView
from sanic.websocket import ConnectionClosed, WebSocketProtocol
from sanic.signals import Namespace
from sanic.helpers import subscribe


class Sanic:
Expand Down Expand Up @@ -90,6 +92,18 @@ def __init__(
# Register alternative method names
self.go_fast = self.run

# Signal Handlers and Registry
self._signals = {
"server": Namespace(namespace="server", owner=self),
"request": Namespace(namespace="request", owner=self),
"response": Namespace(namespace="response", owner=self),
"middleware": Namespace(namespace="middleware", owner=self),
}

@property
def signals(self) -> Dict[str, Namespace]:
return self._signals

@property
def loop(self):
"""Synonymous with asyncio.get_event_loop().
Expand Down Expand Up @@ -143,6 +157,7 @@ def listener(self, event):
"""

def decorator(listener):
subscribe(event, self.signals, listener)
self.listeners[event].append(listener)
return listener

Expand Down
36 changes: 36 additions & 0 deletions sanic/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from importlib import import_module
from inspect import ismodule

from sanic.signals import Namespace
from typing import Dict, Callable


STATUS_CODES = {
100: b"Continue",
Expand Down Expand Up @@ -67,6 +70,30 @@
511: b"Network Authentication Required",
}

_EVENT_MAPPING = {
"before_server_start": {
"namespace": "server",
"context": "before",
"action": "init",
},
"after_server_start": {
"namespace": "server",
"context": "after",
"action": "init",
},
"before_server_stop": {
"namespace": "server",
"context": "before",
"action": "stop"
},
"after_server_stop": {
"namespace": "server",
"context": "after",
"action": "stop"
}
}


# According to https://tools.ietf.org/html/rfc2616#section-7.1
_ENTITY_HEADERS = frozenset(
[
Expand Down Expand Up @@ -154,3 +181,12 @@ def import_string(module_name, package=None):
if ismodule(obj):
return obj
return obj()


def subscribe(event_name: str, signals: Dict[str, Namespace], callback: Callable):
_mapped_context = _EVENT_MAPPING.get(event_name)
_namespace = signals.get(_mapped_context.get("namespace")) # type: Namespace
if _namespace:
_namespace.subscribe(
context=_mapped_context.get("context"), action=_mapped_context.get("action"), callback=callback
)
43 changes: 26 additions & 17 deletions sanic/signals.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from aiosignal import Signal
from typing import Dict, List
from sanic.request import Request
from sanic.response import HTTPResponse


class SignalData:
__slots__ = (
Expand All @@ -13,31 +10,43 @@ def __init__(self, context, exception=None, request=None, response=None, additio
self.context: str = context
self.exception: Exception = exception
self.additional_info: dict = additional_info
self.request: Request = request
self.response: HTTPResponse = response
self.request = request
self.response = response


class Namespace:
def __init__(self, namespace):
def __init__(self, namespace, owner):
self._namespace = namespace # type: str
self._signals: Dict[str, Signal] = {}
self._owner = owner
self._signals: Dict[str, Dict[str, Signal]] = {}

def signal(self, context, action):
try:
self._signals[context]
except KeyError:
self._signals[context] = {}

def signal(self, name, owner):
try:
self._signals[name].append(Signal(owner=owner))
self._signals[context][action].append(Signal(owner=self._owner))
except KeyError:
self._signals[name] = Signal(owner=owner)
self._signals[context][action] = Signal(owner=self._owner)

def register(self, name, callback):
self._signals[name].append(callback)
def subscribe(self, context, action, callback):
try:
self._signals[context][action].append(callback)
except KeyError:
self.signal(context, action)
self._signals[context][action].append(callback)

async def publish(self, name, data: SignalData = None):
async def publish(self, context, action, data: SignalData = None):
if not data:
data = {}
data.namespace = self._namespace
data.name = name
await self._signals[name].send(data=data)
data.context = context
data.action = action
await self._signals[context][action].send(data=data)

def freeze(self):
for key, signal in self._signals.items():
signal.freeze()
for _, context_map in self._signals.items():
for _, signal in context_map.items():
signal.freeze()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def open_local(paths, mode="r", encoding="utf8"):
"websockets>=7.0,<9.0",
"multidict>=4.0,<5.0",
"httpx==0.9.3",
"aiosignal==1.0.0",
]

tests_require = [
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ deps =
pytest-benchmark
uvicorn
websockets>=7.0,<8.0
aiosignal==1.0.0
commands =
pytest {posargs:tests --cov sanic}
- coverage combine --append
Expand Down

0 comments on commit d5f6306

Please sign in to comment.