IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Design pattern observer et accès à un objet


Sujet :

Python

  1. #1
    Membre habitué
    Homme Profil pro
    Développeur backend (python)
    Inscrit en
    mai 2014
    Messages
    74
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Vendée (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur backend (python)

    Informations forums :
    Inscription : mai 2014
    Messages : 74
    Points : 135
    Points
    135
    Par défaut Design pattern observer et accès à un objet
    Bonjour,

    Je rencontre un soucis assez complexe à résoudre dans mon implémentation de mon backend.
    J'ai créer un daemon qui écoute sur un socket unix et qui attend des commandes. Ces commandes sont défini par des workers, appelé 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 les dits worker. Certain worker hérite 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 utiliser 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 workers 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.

    Pour être plus direct, ma classe Workers appelle un de mes classes Configuration (Server), cette classe configuration appelle elle-même un objet SystemConf. C'est cette objet SystemConf qui va lancer la sauvegarde avec les fichiers spécifique, sauf que je n'arrive pas à trouver un moyen d'ajouter un observer sur cette classe depuis ma classe Workers. L'observer ayant besoin d'une instance de mon objet backup en entrée et cet objet backup étant déclarer dans ma classe workers.

    Je pense qu'avec du code ça sera plus excplicite.

    workers.py (Classe permettant d'appeler mes workers et ayant l'objet self.backup):
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
        ==============
        workers module
        ==============
        This module used  to call any worker through the same interface.
        It manage too the backup implementation around the workers
        
        ``classes``
            Workers
    """
    from __future__ import absolute_import
    from __future__ import unicode_literals
     
    # LOCAL IMPORT
    from vigie360_backend import LOGGER, DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE
    from vigie360_backend.backup.backup import Backup
    from vigie360_backend.import_settings import import_settings
    from vigie360_backend.configuration.configuration import Configuration
    from vigie360_backend.system_call.system_call import SystemCall
    from vigie360_backend.update.update import Update
     
    # DJANGO IMPORT
    from django.utils.translation import ugettext as _
     
    # OTHER IMPORT
    import traceback
    import inspect
     
    import_settings(DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE)
    # locale.setlocale(locale.LC_ALL, str(settings.LANGUAGE_CODE) + str('.utf8'))
     
    __author__ = 'tchene'
     
     
    class Workers(object):
        """
            Class Workers is a context manager who launch each worker with
            the call_worker() generic function.
            It used to catch and handle exceptions from workers.
     
            ``methods``
                __init__(self, need_save, worker, *data)
                __enter__(self)
                __exit__(self, exc_type, exc_val, exc_tb)
                __factory(worker, *data) (@staticmethod)
                call_worker(self)
        """
     
        def __init__(self, need_save, worker, *data):
            LOGGER.info('Workers object creation')
            LOGGER.debug(worker)
            LOGGER.debug(data)
            self.need_save = need_save
            self.status = '+'
            self.message = ''
            self.worker = self.__factory(worker, *data)
            LOGGER.debug('Worker type : %s' % self.worker)
     
        def __enter__(self):
            """
                This function use to launch backup process according to need_save
                value.
                
                :return: self
            """
            LOGGER.info('Enter workers context manager')
            if self.need_save:
                try:
                    self.backup = Backup()
                    self.message = self.backup.save()
                except Exception as err:
                    self.backup.clean()
                    LOGGER.error("__enter__ inspect_trace : %s" % inspect.trace())
                    LOGGER.error('An error occurred in __enter__ : %s' % err)
                    self.status = '-'
                    self.message = _("An error occurred, please contact your administrator")
                    raise err
            return self
     
        def __exit__(self, exc_type, exc_val, exc_tb):
            """
                Exceptions are caught in the exit function and used to send back
                error messages and status. Then it executes the restore process if
                needed.
                
                :param exc_type: Exception type
                :param exc_val: Exception value
                :param exc_tb: Exception traceback object
            """
            LOGGER.info('Quit workers context manager')
            if all((exc_type, exc_val, exc_tb)):
                for line in traceback.extract_tb(exc_tb):
                    LOGGER.critical('%s' % repr(line))
                LOGGER.debug('EXCEPTION_TYPE : %s' % exc_type)
                LOGGER.debug('EXCEPTION_VAL : %s' % exc_val)
                self.status = '-'
                self.message = _("An error occurred, please contact your administrator")
                try:
                    if self.need_save:
                        self.backup.restore()
                except Exception as err:
                    LOGGER.error('Worker backup error : %s in %s ' % (err, inspect.trace()[-1][3]))
            if self.need_save:
                self.backup.clean()
     
        @staticmethod
        def __factory(worker, *data):
            """
                This factory return an instance object of a worker class according
                to worker argument sent by front-end connector.
                
                :param worker: The worker name
                :type worker: unicode
                :param data: Data sent bye the front-end
                :type data: undefined
            """
            if worker == 'update': return Update(*data)
            elif worker == 'systemcall': return SystemCall(*data)
            elif worker == 'configuration': return Configuration().factory(*data)
            elif worker == 'deleteconfiguration': return Configuration().factory(deletion=True, *data)
            else: raise Exception('Command sent does not exist')
     
        def call_worker(self):
            """
                Add an observer if the worker class has one. Worker could use
                observer for launch specific action according to an event.
                Then it launch the worker function call() which return status and
                message.
            """
            LOGGER.info('Call_worker')
            if hasattr(self.worker, 'add_bck_observer'):
                self.worker.add_bck_observer(self.backup, 'specific_backup')
            if hasattr(self.worker, 'add_pkg_observer'):
                self.worker.add_pkg_observer(self.backup, 'restore_pip_packets')
                self.worker.add_pkg_observer(self.backup, 'restore_apt_packets')
            self.status, self.message = self.worker.call()

    observer.py (Implémentation du design pattern observer):
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
        ===============
        observer module
        ===============
     
        Definition of all observable used in the backend.
        Observable are part of the observer pattern.
     
        ``classes``
            _Observable(object)
            BckObservable(_Observable)
    """
    from __future__ import unicode_literals
    from __future__ import absolute_import
     
    # LOCAL IMPORT
    from vigie360_backend import LOGGER
     
    # OTHER IMPORT
    from collections import defaultdict
     
    __author__ = 'tchene'
     
    class _Observable(object):
        """
            Default observable implementation.
            You must not use this directly and create your own interface.
            
            ``methods``
                __init__(self)
                add_bck_observer(self, obs,  event)
        """
        def __init__(self):
            self.observers = defaultdict(list)  # For each event in a key, got an observers list.
     
        def add_observer(self, obs, event):
            """
                Call this method for add an observer to an event in your class.
                
                :param obs: Object which do some action on some event.
                :type obs: object
                :param event: Event name.
                :type event: unicode, str
            """
            self.observers[event].append(obs)
     
     
    class BckObservable(_Observable):
        """
            Backup observable implementation.
            Use it for specific backup.
            You must inherit this class in objects who need a specific backup
            implementation.
            
            ``methods``
                add_bck_observer(self, obs, event)
                specific_backup(self, event, path)
        """
     
        def add_bck_observer(self, obs, event):
            """
                Call this method for add an observer to an event in your class.
                
                :param obs: Object which do some action on some event.
                :type obs: object
                :param event: Event name.
                :type event: unicode, str
            """
            if not hasattr(obs, 'specific_backup'):
                raise ValueError("First argument must be object with specific_save method")
            self.observers[event].append(obs)
     
        def specific_backup(self, event, path):
            """
                Call this method in your inherited class for launch specific backup
                event.
                Call specific_save class
                
                :param event: Event name
                :type event: str, unicode
                :param path: A path or a list of path to backup. They must be
                absolute.
                :type path: str, unicode, list
            """
            LOGGER.debug('Observers : %s' % self.observers)
            for obs in self.observers[event]:
                obs.specific_save(path)
     
     
    class PkgObservable(_Observable):
        """
            Package observable implementation.
            Use it for restore packet.
            You must inherit this class in objects who need a packet restore
            implementation.
     
            ``methods``
                add_bck_observer(self, obs, event)
                specific_backup(self, event, path)
        """
     
        def add_pkg_observer(self, obs, event):
            """
                Call this method for add an observer to an event in your class.
     
                :param obs: Object which do some action on some event.
                :type obs: object
                :param event: Event name.
                :type event: unicode, str
            """
            if not hasattr(obs, 'restore_pip_packet') or not hasattr(obs, 'restore_apt_packet'):
                raise ValueError("First argument must be object with restore_pip_packet method or restore_apt_packet "
                                 "method")
            self.observers[event].append(obs)
     
        def restore_pip(self, event, packets):
            """
                Call this method in your inherited class for launch pip packet
                restore event.
                Call specific_save class
     
                :param event: Event name
                :type event: str, unicode
                :param packets: List of dictionaries with packet name and version
                :type packets: list
            """
            for obs in self.observers[event]:
                obs.restore_pip_packet(packets)
     
        def restore_apt(self, event, packets):
            """
                Call this method in your inherited class for launch specific backup
                event.
                Call specific_save class
     
                :param event: Event name
                :type event: str, unicode
                :param packets: List of dictionaries with packet name and version
                :type packets: list
            """
            for obs in self.observers[event]:
                obs.restore_apt_packet(packets)

    configuration.py (Module contenant l'appel aux classe de configuration ainsi que la classe Server) :
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    from __future__ import absolute_import
     
    # LOCAL IMPORT
    from vigie360_backend import LOGGER, DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE
    from vigie360_backend.import_settings import import_settings
    from vigie360_backend.system_call.system_call import SystemCall
    from vigie360_backend.configuration.systemconf import SystemConf
    from vigie360_backend.configuration.cfgconf import CfgConf
    from vigie360_backend.observer.observer import BckObservable
     
    # APP FILES IMPORT
    from profiles.forms import *
    from supervision.models import SuTimeperiod
    from core.models import Setting
     
    # LIBS IMPORT
    from libs.sync_db_shinken_packs.sync_db import *
    from shell_func import hash_postfix
    from simpleparser import SimpleParser
    from edit_shinken_module import raz_pnp_ip, set_pnp_ip, nagvis_htmlcgi_ip, raz_resource
    from config import dhcp_to_static, ModifBase
    from servicehandler import change_hostname
    from mongodb_custom import raz_mongodb
    from settings_parser import settings_write
     
    # DJANGO IMPORT
    from django.utils.translation import pgettext
    from django.utils.translation import ugettext as _
    from django.db import connections
    from django.core.management import call_command
     
    # OTHER IMPORT
    from json import dumps
    # from distutils.dir_util import copy_tree, remove_tree
    # from distutils.file_util import copy_file
    # from distutils.errors import DistutilsError, DistutilsFileError
    import _mysql
    import os
     
    __author__ = 'tchene'
     
    import_settings(DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE)
     
    join = os.path.join
     
     
    # noinspection PyAttributeOutsideInit
    class Configuration(object):
        def init(self, data, deletion=False):
            if data:
                LOGGER.debug('DATA CONFIGURATION : %s', data)
                LOGGER.debug('DATA TYPE CONFIGURATION : %s', type(data))
                self.data = data
            self.deletion = deletion
            if hasattr(self, 'data') and not isinstance(self.data, dict):
                raise TypeError('Data must be dict type')
     
        @classmethod
        def factory(cls, cls_type, data=None, deletion=False):
            if cls_type == 'smtp':
                return Smtp(data, deletion)
            if cls_type == 'server':
                return Server(data)
            if cls_type == 'baseconf':
                return Baseconf(data)
            if cls_type == 'raz':
                return Raz(data)
            assert 0, "Bad configuration creation: " + cls_type
     
    class Server(Configuration):
        # noinspection PyMissingConstructor
        def __init__(self, data):
            super(Server, self).init(data)
            self.cfg_file = os.path.join(settings.ROOT_DIR, 'server.cfg')
     
        def call(self):
            LOGGER.debug(self.data)
            message_plus = self.add_message_plus()
            cc = CfgConf(self.cfg_file, self.data)
            cc.call()
            sc = SystemConf(self.data['server'])
            sc.call()
            return '+', message_plus
     
        def add_message_plus(self):
            message_plus = list()
            if 'hostname' in self.data['server']:
                self.format_message_plus(message_plus, _('Hostname'), 'hostname')
            if 'ip' in self.data['server']:
                self.format_message_plus(message_plus, _('Ip address'), 'ip')
            if 'domain' in self.data['server']:
                self.format_message_plus(message_plus, _('Domain'), 'domain')
            if 'dns1' in self.data['server']:
                self.format_message_plus(message_plus, _('First DNS'), 'dns1')
            if 'dns2' in self.data['server']:
                self.format_message_plus(message_plus, _('Second DNS'), 'dns2')
            if 'gateway' in self.data['server']:
                self.format_message_plus(message_plus, _('Gateway'), 'gateway')
            if 'subnetmask' in self.data['server']:
                self.format_message_plus(message_plus, _('Subnetmask'), 'subnetmask')
            if 'country' in self.data['server']:
                self.format_message_plus(message_plus, _('Country'), 'country')
            if 'department' in self.data['server']:
                self.format_message_plus(message_plus, _('Department'), 'department')
            if 'city' in self.data['server']:
                self.format_message_plus(message_plus, _('City'), 'city')
            if 'company' in self.data['server']:
                self.format_message_plus(message_plus, _('Company'), 'company')
            return message_plus
     
        def format_message_plus(self, message_plus, desc, key):
            cp = ConfigParser.ConfigParser()
            cp.read(join(settings.ROOT_DIR, 'server.cfg'))
            if cp.has_option('server', key):
                message_plus.append(
                    desc + ': ' + cp.get('server', key) + ' -> ' + self.data['server'][key]
                    )
            else:
                message_plus.append(
                    desc + ': ' + pgettext('male', 'None') + ' -> ' + self.data['server'][key]
                    )
            return message_plus

    systemconf.py (Contient la classe lançant la sauvegarde spécifique, dans le __init__):
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
        ==============
        systemconf module
        ==============
     
        System configuration module. Import SystemConf Class and use it like in the
        example below :
     
            sc = SystemConf(
            {
                'hostname'  : 'examples',
                'domain'    : 'example.com',
                'ip'        : '192.168.1.1',
                'subnetmask': '255.255.255.0',
                'gateway'   : '192.168.0.5',
                'dns1'      : '8.8.8.8',
                'dns2'      : None
                })
            sc.call()
     
        ``classes``
            SystemConf(Utils, BckObservable)
    """
    from __future__ import unicode_literals
    from __future__ import absolute_import
     
    # LOCAL IMPORT
    from vigie360_backend import LOGGER
    from vigie360_backend.utils import Utils
    from vigie360_backend.observer import BckObservable
    from vigie360_backend.system_call.system_call import static_call
     
    # OTHER IMPORT
    import re
    import os
     
     
    join = os.path.join
     
    __author__ = 'tchene'
     
    class SystemConf(Utils, BckObservable):
        """
            System configuration class.
     
            ``methods``
                __init__(self, data)
                _create_static_properties(self)
                _create_dynamic_properties(self, data)
                call(self)
                _get_file_content(file_path) (@staticmethod)
                _write_file_content(file_path, content) (@staticmethod)
                _find_properties(pattern, string, options=None) (@staticmethod)
                _property_replacement(pattern, repl, string) (@staticmethod)
                _add_dns2(self, file_path, value)
                _del_dns2(self, file_path)
                _add_search_domain(self, file_path, value)
        """
     
        def __init__(self, data):
            super(SystemConf, self).__init__()
            self.static_properties = self._create_static_properties()
            if not isinstance(data, dict):
                raise TypeError('Data must be a dictionary')
            self.dynamic_properties = self._create_dynamic_properties(data)
            paths_list = []
            for path_list in [v['files'] for k, v in self.dynamic_properties.iteritems()]:
                paths_list.extend(path_list)
            LOGGER.debug('Conf backup paths : %s' % paths_list)
            self.specific_backup('specific_backup', paths_list)
     
        def _create_static_properties(self):
            """
                Create a static dictionary of dictionary with all properties which
                never change.
                The keys in the first dictionary are properties names, their linked
                with a dictionary which contain all the properties.
                If the property involves multiple files, their set in a list.
                (For compatibility reason, single file are in a list too)
                The add_func, del_func and regex list are in the same order than
                the files list.
                The services list indicate which services will be restart at the end
                of the server configuration.
     
                :return: Static properties dict object
                :rtype: dict
            """
            return {
                "hostname"  : {
                    "add_func": [
                                    None,
                                    None
                                ],
                    "del_func": [
                                    None,
                                    None
                                ],
                    "files"   : [
                                    '/etc/hosts',
                                    '/etc/hostname'
                                ],
                    "regex"   : [
                                    (r'^127\.0\.1\.1\s*(\S+)\s([-\w]+)\.', re.MULTILINE),
                                    (r'^(.+)$', re.MULTILINE)
                                ],
                    "services": ['service hostname restart']
                    },
     
                "domain"    : {
                    "add_func": [
                                    None,
                                    None,
                                    self._add_search_domain,
                                ],
                    "del_func": [
                                    None,
                                    None,
                                    None,
                                ],
                    "files"   : [
                                    '/etc/hosts',
                                    '/etc/samba/smb.conf',
                                    '/etc/resolvconf/resolv.conf.d/base'
                                ],
                    "regex"   : [
                                    (r'^127\.0\.1\.1\s*\S+\s[-\w]+\.(.+)$', re.MULTILINE),
                                    (r'^\s+workgroup\s=\s(.+)$', re.MULTILINE),
                                    (r'^search\s(.+)$', re.MULTILINE)
                                ],
                    "services": [
                                    'service hostname restart',
                                    'service smbd restart'
                                ]
                    },
     
                "ip"        : {
                    "add_func": [
                                    None,
                                    None,
                                    None,
                                ],
                    "del_func": [
                                    None,
                                    None,
                                    None,
                                ],
                    "files"   : [
                                    '/etc/network/interfaces',
                                    '/etc/shinken/modules/ui-pnp.cfg',
                                    '/etc/shinken/nagvis/etc/nagvis.ini.php'
                                 ],
                    "regex"   : [
                                    (r'^address\s(.+)$', re.MULTILINE),
                                    ('^\s+uri\s+http://(\S+)/pnp4nagios/', re.MULTILINE),
                                    ('^htmlcgi\s=\s\"http://(\S+):7767\"', re.MULTILINE)
                                 ],
                    "services": [
                                    'ifdown eth0',
                                    'ifup eth0',
                                    'service nginx reload',
                                    'service shinken restart'
                                ]
                    },
     
                "subnetmask": {
                    "add_func": [None],
                    "del_func": [None],
                    "files"   : ['/etc/network/interfaces'],
                    "regex"   : [(r'^netmask\s(.+)$', re.MULTILINE)],
                    "services": [
                                    'ifdown eth0',
                                    'ifup eth0'
                                 ]
                    },
     
                "gateway"   : {
                    "add_func": [None],
                    "del_func": [None],
                    "files"   : ['/etc/network/interfaces'],
                    "regex"   : [(r'^gateway\s(.+)$', re.MULTILINE)],
                    "services": [
                                    'ifdown eth0',
                                    'ifup eth0'
                                ]
                    },
     
                "dns1"      : {
                    "add_func": [None],
                    "del_func": [None],
                    "files"   : ['/etc/resolvconf/resolv.conf.d/base'],
                    "regex"   : [(r'^nameserver\s(.+)', None)],
                    "services": ['service resolvconf restart']
                    },
     
                "dns2"      : {
                    "add_func": [self._add_dns2],
                    "del_func": [self._del_dns2],
                    "files"   : ['/etc/resolvconf/resolv.conf.d/base'],
                    "regex"   : [(r'\nnameserver\s(.+)', None)],
                    "services": ['service resolvconf restart']
                    },
                }
     
        def _create_dynamic_properties(self, data):
            """
                Merge the static properties with value passed for each property.
                data must be like this :
                {
                    'property_name': value
                }
     
                :param data: Dictionary like above
                :type data: dict
                :return: Merged dictionary
                :rtype: dict
            """
            properties = {}
            for k, v in data.iteritems():
                if k in self.static_properties.keys():
                    properties[k] = {'value' : v}
            for k in properties:
                properties[k].update(self.static_properties[k])
            LOGGER.debug('Dynamic properties : %s' % properties)
            return properties
     
        def call(self):
            """
                Call function, all process is launched here.
     
                For each key in the dynamic_properties, we launched a regex catch.
                If the regex is existing in the file, we launch the modification,
                else we launch the add method. All property does not have add
                function, because for some properties, it is impossible to not
                exist.
                Finally, if the property is existing and it's value is None,
                we launch the delete function for this property.
                Some properties don't have delete method for the same reason that
                add function does not exist for them, they must be here all time.
     
                (See self._create_static_properties for more details about the regex
                order, the add function and the delete function)
            """
            LOGGER.info('Launch system configuration')
            services = []
            for key, prop in self.dynamic_properties.iteritems():
                if prop['value']:
                    for file_path, regex, add_func, in zip(prop['files'], prop['regex'], prop['add_func']):
                        content = self._get_file_content(file_path)
                        grep = self._find_properties(regex[0], content, regex[1])
                        if grep:
                            for group in grep.groups():
                                content = self._property_replacement(group, prop['value'], content)
                            self._write_file_content(file_path, content)
                        else:
                            if add_func:
                                add_func(file_path, prop['value'])
                else:
                    for file_path, del_func in zip(prop['files'], prop['del_func']):
                        if del_func:
                            del_func(file_path)
                for service in prop['services']:
                    if service not in services:
                        services.append(service)
            static_call(services)
            LOGGER.info('System configuration finished')
     
        @staticmethod
        def _get_file_content(file_path):
            """
                Return the full content of a file
     
                :param file_path: Path to file
                :type file_path: unicode
                :return: File content
                :rtype: str
            """
            with open(file_path, b'r') as fh:
                return fh.read()
     
        @staticmethod
        def _write_file_content(file_path, content):
            """
                Write the content passed to the method in the file define in the
                file_path argument.
     
                :param file_path:
                :type file_path: unicode
                :param content:
                :type content: unicode
            """
            with open(file_path, b'w') as fh:
                # noinspection PyTypeChecker
                fh.write(content)
     
        @staticmethod
        def _find_properties(pattern, string, options=None):
            """
                Find the pattern in the string, if options is given use it with the
                re.search function.
     
                :param pattern: Pattern to match
                :type pattern: unicode
                :param string: The string we look for the pattern
                :type string: unicode
                :param options: Options to pass to re.search
                :type options: re constant like re.MULTILINE
                :return: Return None if the pattern is not find in the string or
                a MatchObject instead
                :rtype: MatchObject or None
            """
            if options:
                return re.search(pattern, string, options)
            else:
                return re.search(pattern, string)
     
        @staticmethod
        def _property_replacement(pattern, repl, string):
            """
                Replace the first matched pattern in string with repl.
     
                :param pattern: Pattern to replace
                :type pattern: unicode
                :param repl: String replacement
                :type repl: unicode
                :param string: The string in which we replace pattern by repl
                :type string
                :return: Return the replaced string
                :rtype: unicode
            """
            return re.sub(pattern, repl, string, 1)
     
        def _add_dns2(self, file_path, value):
            """
                Special function to add the second dns in the resolvconf file.
     
                :param file_path: File path to write
                :type file_path: unicode
                :param value: DNS2 value
                :type value: unicode
            """
            content = self._get_file_content(file_path)
            content = content.strip().split('\n')
            content.insert(1, 'nameserver %s' % value)
            content = '\n'.join(content)
            content = content.strip()
            self._write_file_content(file_path, content)
     
        def _del_dns2(self, file_path):
            """
                Special function to delete the second dns in the resolvconf file.
     
                :param file_path: File path to write
                :type file_path: unicode
            """
            content = self._get_file_content(file_path)
            content = content.strip().split('\n')
            del content[1]
            content = '\n'.join(content)
            content = content.strip()
            self._write_file_content(file_path, content)
     
        def _add_search_domain(self, file_path, value):
            """
                Special function to add the search option in the resolvconf file.
     
                :param file_path: File path to write
                :type file_path: unicode
                :param value: Search value
                :type value: unicode
            """
            content = self._get_file_content(file_path)
            content = content.strip().split('\n')
            content.append('search %s' % value)
            content = '\n'.join(content)
            content = content.strip()
            self._write_file_content(file_path, content)

    Désolé pour la classe configuration, j'ai pas eu le temps de faire la docstring.

    Si vous avez besoin de plus de précision n'hésitez pas à demander, je conçois très bien que l'explication ne soit pas très claire.

    Et merci d'avance à ceux qui se pencheront dessus.

  2. #2
    Membre confirmé
    Homme Profil pro
    Développeur banc de test
    Inscrit en
    mai 2014
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur banc de test
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : mai 2014
    Messages : 193
    Points : 462
    Points
    462
    Par défaut
    Bonjour,

    pour faciliter la compréhension est-ce qu'il vous serait possible de réduire/résumer en quelques lignes votre code de façon à pouvoir le reproduire facilement ?

    Peut-être ai-je mal compris mais votre question semble finalement d'ordre générale pour incorporer un wrapper/décorateur sur une instance ?

  3. #3
    Membre habitué
    Homme Profil pro
    Développeur backend (python)
    Inscrit en
    mai 2014
    Messages
    74
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Vendée (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur backend (python)

    Informations forums :
    Inscription : mai 2014
    Messages : 74
    Points : 135
    Points
    135
    Par défaut
    Bonjour,

    Alors, pour ce qui est du wrapper/décorateur, non. J'ai déjà un wrapper via sur ma classe principale qui est un context manager, les fonctions __enter__ et __exit__ font office de wrapper.

    Ce que j'ai besoin c'est d'ajouter un observer depuis une classe sur l'enfant de l'enfant (En très gros) afin que cet enfant d'enfant puisse déclencher un événement.

    Et voilà le code remanier et plus simple pour l'exemple, j'ai mis toutes les classe dans un seul fichier :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    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()

    Ce que je voudrait du coup, c'est pouvoir déclencher un event 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().

    En espérant avoir été plus clair.

Discussions similaires

  1. Design pattern Observer
    Par aicfr dans le forum Logging
    Réponses: 1
    Dernier message: 06/11/2007, 23h14
  2. [Memento] Design Pattern Memento ou clonage d'objets ?
    Par SPQR dans le forum Design Patterns
    Réponses: 1
    Dernier message: 23/07/2007, 09h08
  3. [Conception] Design Pattern Observer Java
    Par jeb001 dans le forum Général Java
    Réponses: 4
    Dernier message: 02/05/2007, 23h43
  4. Design pattern : Elaborez le diagramme d'objet ?
    Par ivanoe25 dans le forum Design Patterns
    Réponses: 12
    Dernier message: 29/03/2006, 10h29
  5. [Observateur] Précisions sur le design pattern Observer [UML]
    Par joquetino dans le forum Design Patterns
    Réponses: 2
    Dernier message: 07/10/2004, 22h35

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo