Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Déclenchement d'évèment avec le design pattern observer (Résolu, Pattern Singleton)

+3 votes

J'ai créé un daemon qui écoute sur un socket unix et qui attend des commandes. Ces commandes sont définies par des workers, appelés dans une classe workers. La classe workers permet d'encapsuler un système de sauvegarde/restauration en entrée/sortie des différents workers suivant l'état renvoyer par lesdits worker. Certains workers héritent d'une classe BckObservable permettant d'appeler une fonction pour sauvegarder certain fichier spécifique qui ne sont pas présent dans la liste des fichiers de base à sauvegarder. J'ai utilisé le design pattern observer pour faire ça. L'instance de mon objet backup doit être le même durant tout le traitement du worker et c'est là que j'ai un soucis. En effet, pour un de mes worker j'ai besoin de d'avoir accès à mon objet backup non pas dans mon worker mais dans un objet appelé dans le worker, sachant que je ne passe pas mon objet backup à mes workers.

Avec les noms de classe pour correspondre au code que je vais mettre à la fin ça donnerait ceci : Pouvoir déclencher un evènement dans SystemConf() via l'observateur BckObservable(). Le soucis c'est que si je veux ajouter un observateur sur SystemConf() il faudrait que je passe mon objet Backup() (Déclarer dans le __enter__ de Worker()) dans l'objet ServerConfiguration(), ce que je souhaite éviter pour centraliser le backup dans ma classe Workers()

Et bien sûr voilà un code d'exemple sur le fonctionnement du bousin:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import absolute_import
import os
import shutil
from collections import defaultdict


# Implémentation du design pattern observer
class BckObservable(object):
    def __init__(self):
        self.observers = defaultdict(list)

    def add_bck_observer(self, obs, event):
        if not hasattr(obs, 'specific_save'):
            raise ValueError("First argument must be object with specific_save method")
        self.observers[event].append(obs)

    def specific_backup(self, event, path):
        for obs in self.observers[event]:
            obs.specific_save(path)


# Classe de backup
class Backup(object):
    def __init__(self):
        self.backup_path = '/opt/backup'
        self._files_backup_path = os.path.join(self.backup_path, 'files')
        self.base_backup_path = ['/etc/shinken', '/var/lib/shinken']

    # Fonction lancer dès qu'il y a besoin de sauvegarde, représenter par la variable need_save dans Workers
    def save(self):
        self._save_files(self.base_backup_path)

    # Fonction lancer de manière indépendante via l'observer
    def specific_save(self, path):
        self._save_files(path)

    # Copy les fichier dans le répertoire de backup
    def _save_files(self, save_paths):
        if isinstance(save_paths, unicode) or isinstance(save_paths, str):
            save_paths = [save_paths]
        if not isinstance(save_paths, list):
            raise TypeError('Paths must be a list')

        for path in save_paths:
            backup_path = os.path.join(self._files_backup_path, path.lstrip('/'))
            shutil.copy(path, backup_path)

    # Fonction lancer en cas d'erreur dans le process pour restaurer les fichiers à leur état initial
    def restore(self):
        for root, dirs, files in os.walk(self._files_backup_path):
            backup_path = os.path.join(root, files)
            restore_path = backup_path.replace(self._files_backup_path, '')
            shutil.copy(backup_path, restore_path)


class Workers(object):
    # need_save est un booléen permettant de savoir si il y a besoin  de sauvegarde
    # worker est la classe à lancer, pour les besoin de l'exemple worker sera toujours pareil
    def __init__(self, need_save, *data):
        self.need_save = need_save
        self.worker = ServerConfiguration(*data)

    # Lance la sauvegarde si besoin
    def __enter__(self):
        if self.need_save:
            self.backup = Backup()
            self.backup.save()

    # Lance la restauration si besoin
    def __exit__(self, exc_type, exc_val, exc_tb):
        if all((exc_type, exc_val, exc_tb)):
            if self.need_save:
                self.backup.restore()

    # Ajoute un observer Backup() sur l'event specific_backup
    # Appel la méthode call() du worker
    def call_worker(self):
        if hasattr(self.worker, 'add_bck_observer'):
            self.worker.add_bck_observer(self.backup, 'specific_backup')
        self.worker.call()


class ServerConfiguration(BckObservable):
    def __init__(self, data):
        super(ServerConfiguration, self).__init__()
        self.data = data

    # Appel une classe de traitement
    def call(self):
        sc = SystemConf()
        sc.call()


class SystemConf(BckObservable):
    # Lancement de la fonction specific_backup -> ne fonctionne pas
    # Il n'y a pas d'observer sur cette classe, il est sur le parent
    def __init__(self):
        super(SystemConf, self).__init__()
        paths_list = ['/etc/network/interfaces', '/etc/hosts']
        self.specific_backup('specific_backup', paths_list)

    def call(self):
        print "SystemConf traitement"


if __name__ == '__main__':
    wk = Workers(True, 'Some data')
    with wk:
        wk.call_worker()
demandé 12-Mai par Takka (242 points)
edité 24-Mai par Takka

2 Réponses

+2 votes
 
Meilleure réponse

J'ai finalement trouver un moyen de faire, j'ai fait en sorte que ma classe Backup() devienne un Singleton. Je peux ainsi l'appelé depuis différent endroit tout en gardant une seule instance de mon objet.

Pour ce faire j'ai utiliser le code suivant, que j'hérite sur ma classe ayant besoin d'être un Singleton :

class Singleton(object):
    instance = None

    def __new__(cls):
        """
            Standard python construction method
        """
        if not cls.instance:
            cls.instance = object.__new__(cls)
        return cls.instance
répondu 24-Mai par Takka (242 points)
+1 vote

J'aurai tendance à dire que ce pattern singleton est une adaptation d'une pensée C++ ou autre.

En Python, chaque module est un singleton.
Implémente ta classe, puis crée une instance

class Backup(object):
    ....

backup = Backup()

tu retrouve ce principe dans le module pickle et le module log par exemple

L'avantage :
- tu as bien un singleton gère tout seul par python dans ton module
- si tu veux dérivée ou préparer un autre système de Backup, tu peux crée une autre instance en appelant la classe

Le problème du pattern singleton utilisé :
si tu dérive ta classe, tu n'aura pas deux singletons pour chaque classe, mais un pour l'une ou l'autre ( la première appelé ), ce qui est loin d'être intuitif

répondu 31-Mai par Kermit (206 points)
...