import abc
from threading import RLock

__all__ = ['Observer', 'Observable', 'CallbackObserver']


class Observer(abc.ABC):
    """An observer, which is observed and updated / notified on change"""
    @abc.abstractmethod
    def on_url_loaded(self, url, response, **kwargs):
        """This method is called when url's http response is received
        
        :param url: The url which is being loaded
        :param response: The html response of the http request """

    def on_parse_completed(self, url, obj, **kwargs):
        """This method is called when parsing of response html is completed, as per given configuration."""


class Observable(abc.ABC):
    """ An observable who holds a list of observers.
    Any concrete class implementing this interface can add / remove observers"""
    def __init__(self):
        self._observers = set()
        self._observers_lock = self._create_lock()

    @abc.abstractmethod
    def add_observer(self, observer: Observer):
        """ Add observer to observers list """

    def remove_observer(self, observer: Observer):
        """ Removes an observer from list of observers."""
        with self._observers_lock:
            if len(self._observers) > 0:
                self._observers.remove(observer)

    def _create_lock(self):
        return RLock()


class CallbackObserver(Observer):
    """ An observer which calls given list of callback methods,
        on completion of actual Observable's task
        url_callbacks=None : callback methods, which need to be called on loading of url is completed
        callbacks=None : callback methods, which need to be called on parsing is completed
        """

    def __init__(self, callbacks=None, url_callbacks=None):
        if callbacks is not None:
            assert hasattr(callbacks, '__iter__'), 'callbacks must be an iterable list of callback methods'
            callbacks = set(callbacks)

        if url_callbacks is not None:
            assert hasattr(url_callbacks, '__iter__'), 'url_callbacks must be a iterable list of callback methods'
            url_callbacks = set(url_callbacks)

        super().__init__()
        self._callback_lock = RLock()
        self._url_callback_lock = RLock()
        self._callback_list = callbacks or set()
        self._url_callback_list = url_callbacks or set()

    def on_url_loaded(self, url, response, *args, **kwargs):
        """Calls back the callbacks when url is loaded"""
        with self._url_callback_lock:
            for callback in self._url_callback_list:
                callback(url, response, *args, **kwargs)

    def on_parse_completed(self, url, obj, *args, **kwargs):
        with self._callback_lock:
            for callback in self._callback_list:
                callback(url, obj, *args, **kwargs)

    def add_callback(self, callback):
        """Add callback method to callbacks list..."""
        assert hasattr(callback, '__call__'), 'The callback must be an instance of callable'
        with self._callback_lock:
            self._callback_list.add(callback)

    def add_url_callback(self, callback):
        """Add callback method to url_callbacks list"""
        assert hasattr(callback, '__call__'), 'The callback must be an instance of callable'
        with self._url_callback_lock:
            self._url_callback_list.add(callback)

    def remove(self, callback):
        "Remove callback method from callbacks list..."
        assert callback in self._callback_list, f'removing unregistered {callback} callback'
        with self._callback_lock:
            self._callback_list.remove(callback)

    def remove_url_callback(self, callback):
        """Remove callback method from url callbacks list"""
        assert callback in self._url_callback_list, f'removing unregistered {callback} callback'
        with self._url_callback_lock:
            self._url_callback_list.remove(callback)
import abc
from threading import RLock

__all__ = ['Observer', 'Observable', 'CallbackObserver']


class Observer(abc.ABC):
    """An observer, which is observed and updated / notified on change"""
    @abc.abstractmethod
    def on_url_loaded(self, url, response, **kwargs):
        """This method is called when url's http response is received
        
        :param url: The url which is being loaded
        :param response: The html response of the http request """

    def on_parse_completed(self, url, obj, **kwargs):
        """This method is called when parsing of response html is completed, as per given configuration."""


class Observable(abc.ABC):
    """ An observable who holds a list of observers.
    Any concrete class implementing this interface can add / remove observers"""
    def __init__(self):
        self._observers = set()
        self._observers_lock = self._create_lock()

    @abc.abstractmethod
    def add_observer(self, observer: Observer):
        """ Add observer to observers list """

    def remove_observer(self, observer: Observer):
        """ Removes an observer from list of observers."""
        with self._observers_lock:
            if len(self._observers) > 0:
                self._observers.remove(observer)

    def _create_lock(self):
        return RLock()


class CallbackObserver(Observer):
    """ An observer which calls given list of callback methods,
        on completion of actual Observable's task
        url_callbacks=None : callback methods, which need to be called on loading of url is completed
        callbacks=None : callback methods, which need to be called on parsing is completed
        """

    def __init__(self, callbacks=None, url_callbacks=None):
        if callbacks is not None:
            assert hasattr(callbacks, '__iter__'), 'callbacks must be an iterable list of callback methods'
            callbacks = set(callbacks)

        if url_callbacks is not None:
            assert hasattr(url_callbacks, '__iter__'), 'url_callbacks must be a iterable list of callback methods'
            url_callbacks = set(url_callbacks)

        super().__init__()
        self._callback_lock = RLock()
        self._url_callback_lock = RLock()
        self._callback_list = callbacks or set()
        self._url_callback_list = url_callbacks or set()

    def on_url_loaded(self, url, response, *args, **kwargs):
        """Calls back the callbacks when url is loaded"""
        with self._url_callback_lock:
            for callback in self._url_callback_list:
                callback(url, response, *args, **kwargs)

    def on_parse_completed(self, url, obj, *args, **kwargs):
        with self._callback_lock:
            for callback in self._callback_list:
                callback(url, obj, *args, **kwargs)

    def add_callback(self, callback):
        """Add callback method to callbacks list..."""
        assert hasattr(callback, '__call__'), 'The callback must be an instance of callable'
        with self._callback_lock:
            self._callback_list.add(callback)

    def add_url_callback(self, callback):
        """Add callback method to url_callbacks list"""
        assert hasattr(callback, '__call__'), 'The callback must be an instance of callable'
        with self._url_callback_lock:
            self._url_callback_list.add(callback)

    def remove(self, callback):
        "Remove callback method from callbacks list..."
        assert callback in self._callback_list, f'removing unregistered {callback} callback'
        with self._callback_lock:
            self._callback_list.remove(callback)

    def remove_url_callback(self, callback):
        """Remove callback method from url callbacks list"""
        assert callback in self._url_callback_list, f'removing unregistered {callback} callback'
        with self._url_callback_lock:
            self._url_callback_list.remove(callback)
An observer, which is observed and updated / notified on change This method is called when url's http response is received                  :param url: The url which is being loaded         :param response: The html response of the http request This method is called when parsing of response html is completed, as per given configuration. An observable who holds a list of observers.     Any concrete class implementing this interface can add / remove observers Add observer to observers list Removes an observer from list of observers. An observer which calls given list of callback methods,         on completion of actual Observable's task         url_callbacks=None : callback methods, which need to be called on loading of url is completed         callbacks=None : callback methods, which need to be called on parsing is completed Calls back the callbacks when url is loaded Add callback method to callbacks list... Add callback method to url_callbacks list Remove callback method from url callbacks list