'Django manage.py runserver graceful reloading

I am working on a Django project, which integrates a webcam and OpenCV. For the webcam access, I use following code. The webcam can be released if I use Ctrl + C to end a running server, but if the server reloads itself after the code change, the webcam can not be released properly and therefore will be not available. How can I detect hot reloading so I can close the webcam properly?

I am aware of the option of forbidding hot reloading but this is rather uncomfortable. Is there any option I can realize programmatically?

class VideoCamera(object):
    def __init__(self):
        self.video = None

    def __del__(self):
        if self.video is not None and self.video.isOpened():
            self.video.release()

    def get_frame(self):
        try:
            self.video = cv2.VideoCapture(0)
            success, image = self.video.read()
            self.video.release()
            ret, jpeg = cv2.imencode('.jpg', image)
            return jpeg.tobytes()
        except (SystemExit, KeyboardInterrupt, Exception) as e:
            self.video.release()
            raise e


Solution 1:[1]

Few ideas

1) You could connect on the file_change signal that is triggered on file change before reload takes place

https://github.com/django/django/blob/9386586f31b8a0bccf59a1bff647cd829d4e79aa/django/utils/autoreload.py#L24

def notify_file_changed(self, path):
    results = file_changed.send(sender=self, file_path=path)
    logger.debug('%s notified as changed. Signal results: %s.', path, results)
    if not any(res[1] for res in results):
        trigger_reload(path)

2) Simply monkey patch trigger reload function and inject webcam closing code

https://github.com/django/django/blob/9386586f31b8a0bccf59a1bff647cd829d4e79aa/django/utils/autoreload.py#L221

Solution 2:[2]

If your class is a singleton, which it appears to be, create an instance of your class in its file and then add Django signal handlers to perform the clean up. As it is generally not recommended to use __del__, I would recommend you add a signal (system signals like SIGINT/ctrl+C) handler which forwards it as a Django signal. This means, listen on file_changed from django.utils.autoreload and SIGINT.

To use the singleton in other files, import it with from my_app.singleton import my_singleton.

# my_app/singleton.py

class MySingleton:
    def __init__(self):
        """Init code here"""

    def close(self):
        """Shutdown code here"""

my_singleton = MySingleton()

Then, register your signal handlers for your app. This is the standard AFAIK.

# my_app/apps.py
from django.apps import AppConfig


class MyAppConfig(AppConfig):
    name = "my_app"

    def ready(self):
        import my_app.signals

The signal handler file itself, import the singleton and close it on shutdown. This is called on file_changed and SIGINT.

# my_app/signals.py
from django.dispatch import receiver
from django.utils.autoreload import file_changed
from my_app.signal_definitions import system_shutdown_signal
from my_app.singleton import my_singleton

@receiver(file_changed)
@receiver(system_shutdown_signal)
def my_shutdown_handler(sender, **kwargs):
    my_singleton.close()

Register a handler to catch the SIGINT signal from the system.

# my_app/__init__.py
import signal
import sys

from iot.signal_definitions import system_shutdown_signal


def _forward_to_django_shutdown_signal(signal, frame):
    print(f"Shutting down Django {sys.argv}")
    system_shutdown_signal.send("system")
    sys.exit(0)


signal.signal(signal.SIGINT, _forward_to_django_shutdown_signal)

The Django signal used to propagate the system SIGINT signal.

# my_app/signal_definitions.py
from django.dispatch import Signal

system_shutdown_signal = Signal()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 iklinac
Solution 2 Moritz