Definition

Signal은 전체 프레임워크 내에서 어떤 이벤트가 발생했을 때, 그 이벤트의 발생을 알려주는 notification의 역할을 한다.

 

모든 signal은 django.dispatch.Signal 클래스의 인스턴스들이며, 사용자는 signal을 받을 수도 있고, 직접 signal을 만들 수도 있고, 많이 사용되는 signal 객체들을 받을 수도 있다. (주로 django.core.signals, django.db.models.signals 등 ~signals. 파일에 명시되어 있다.)

 

signal을 받거나 준다는 것은 무엇일까? signal을 받는다는 것은 어떤 이벤트가 발생했을 때 알림(notification)을 받는다는 것이고, signal을 준다는 것은 어떤 이벤트가 발생했을 때 알림을 보낸다는 것이다. signal을 받을 때는 해당 알림을 받아서 어떤 일을 할지를 receiver function으로 정의한다. 

 

반면 signal을 줄 때는 위에서 언급한 것처럼 django.dispatch.Signal 클래스의 인스턴스를 만든 뒤 Signal의 send() 함수를 이용해서 signal을 보낸다. 

 

Example

예시를 통해 살펴보자. 한 피자집에서 주문한 피자가 만들어졌을 때 소비자한테 알림을 보내려고 한다. 이때 피자집에서는 Signal을 만들고, 손님은 해당 signal을 받게 된다. 

 

우선 피자집의 입장에서 보면, 피자가 만들어졌을 때 signal 인스턴스를 생성한 뒤 send 함수를 이용해서 보내면 된다. 이때 누구에게 보내는지는 중요하지 않다. Signal을 보내는 쪽은 발신자를 특정하지 않고 보내고, 받는 쪽에서 자신이 원하는 signal만 골라서 받게 된다. 

# pizzastore.py
import django.dispatch

pizza_done = django.dispatch.Signal()
pizza_soldout = django.dispatch.Signal()


class PizzaStore:

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)

    def notify_soldout(self, toppings):
        pizza_soldout.send(sender=self.__class__, toppings=toppings)

 

손님의 입장에서는 주문한 피자가 완성되었다는 signal을 받아야 한다.

 

이때, 위에서는 간단한 예시로 PizzaStore 클래스만 정의하였지만 실제로는 여러 가게들이 메뉴가 완성되었을 때 signal을 보낼 것이다. 손님이 원하는 건 자신이 주문한 피자 가게에서 보낸 signal이므로 해당 피자 가게가 보낸 signal만 받도록 설정할 수 있다. 즉 특정 발신자(sender)가 보낸 signal만 받도록 설정할 수 있다. 

 

아래 코드에서는 PizzaStore에서 보낸 pizza_done signal에 대해서만 notify_pizza 함수를 signal과 연결시킨다. 

# customer.py
from pizzastore import PizzaStore, pizza_done
from django.dispatch import receiver

@receiver(pizza_done, sender=PizzaStore)
def notify_pizza(sender, **kwargs):
    print("Pizza is done!")
    # code

 

위에서 언급한 @(decorator)를 사용하는 방법 말고도 signal과 receiver 함수를 연결할 수 있다. 

# customer.py
from pizzastore import PizzaStore, pizza_done
from django.dispatch import receiver

def notify_pizza(sender, **kwargs):
    print("Pizza is done!")
    # code
    
pizza_done.connect(notify_pizza)

 

즉 signal과 receiver 함수를 연결하는 방법은 두 가지가 있다.

1. @(decorator)를 사용한 연결

2. connect 함수를 사용한 연결

 

sender 옵션은 필수 옵션은 아니다. sender 옵션을 지정하지 않을 경우, notify_pizza는 PizzaStore가 아닌 다른 클래스에서 생성한(sender가 다른 클래스로 되어 있는) pizza_done signal이 발생할 때에도 실행될 것이다. 

 

Signal.send(). vs Signal.send_robust()

signal을 보낼 때는 .send() 함수 외에도 .send_robust() 함수를 사용할 수 있다. 두 함수 모두 (해당 signal을 받는 receiver 함수, 해당 함수가 리턴하는 값)의 튜플 리스트를 리턴한다. 

 

다만 .send() 함수는 개별 receiver 함수로 signal을 보낼 때 에러가 발생할 경우 그 에러를 처리하지 않는다. 그래서 일부 receiver 함수에서 오류가 발생할 경우, 그 함수는 signal을 받지 못할 수 있다. 

 

반면 .send_robust() 함수는 signal을 보내는 도중 발생한 모든 에러를 처리한다(catch로 처리하는 것으로 보인다). 그래서 일부 receiver 함수에 signal을 보내다가 에러가 발생한 경우, .send_robust() 함수의 값으로 리턴되는 tuple 중 일부는 (에러가 발생한 함수, 발생한 에러의 종류)로 값이 리턴된다. 어떻게 해서든지 signal을 보낸다는 의미에서 send_robust라고 이름 붙여진 것 같다. 

 

Receiver 함수의 실행 횟수 제한

receiver 함수는 기본적으로 signal을 받을 때마다 실행되는데, 이 방식이 문제가 될 수도 있다. 

예를 들어 어떤 모델 인스턴스가 저장될 때마다 사용자에게 이메일이 가는 것보다, 한 번만 가는 것이 더 바람직하다. 

이때는 receiver 함수에 dispatch_uid라는 새로운 옵션을 선언하고 그 값으로는 해싱이 가능한 문자열이면 어떤 것이든 선언할 수 있다. 

pizza_done.connect(notify_pizza, dispatch_uid="evening_pizza")

 

참고한 포스트

https://docs.djangoproject.com/en/4.2/topics/signals/

 

'server-side > Django' 카테고리의 다른 글

models and databases  (0) 2023.09.16
python - poetry 사용하기  (0) 2023.07.12
Model.select_related() vs Model.prefetch_related()  (0) 2022.07.11
Model.objects.filter() vs Model.objects.get()  (0) 2022.07.11
models: on_delete  (0) 2022.07.05

+ Recent posts