maintenance.espace

Module d'interface avec un espace.

Modifié le 12/03/23 @author: nicolair

Un "espace" est un service de stockage analogue au s3 de Amazon mais proposé par DigitalOcean. Ce module définit la classe Espace. Une instanciation de cette classe réalise la mise à jour de l'espace pour refléter l'état local du dépôt.

  1# -*- coding: utf-8
  2"""
  3Module d'interface avec un espace.
  4
  5Modifié le 12/03/23  @author: nicolair
  6
  7Un "espace" est un service de stockage analogue au s3 de Amazon mais proposé
  8par DigitalOcean. Ce module définit la classe `Espace`. Une instanciation de
  9cette classe réalise la mise à jour de l'espace pour refléter l'état local du
 10dépôt.
 11"""
 12import boto3
 13import os.path
 14# import copy
 15import mimetypes
 16
 17
 18class Espace:
 19    """
 20    Classe Espace.
 21
 22    """
 23
 24    def __init__(self, data, apublier_data):
 25        """
 26        Initialise la classe et met à jour l'espace.
 27
 28        Pour les fichiers publiables cad ceux de "apublier_data":
 29
 30        - récupère les clés associées et leur timestamp dans l'espace
 31        - si le fichier local est plus récent:
 32                      upload sur l'espace
 33        #### Paramètres:
 34        - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple :
 35        
 36                {
 37                    "region_name" : "fra1",
 38                    "endpoint_url" : "https://fra1.digitaloceanspaces.com",
 39                    "bucket" : "maquisdoc-math",
 40                    "prefix" : "math-exos/"
 41                }
 42                
 43            Les données secrètes de connexion sont dans le fichier local
 44            `~/.aws/credentials`.
 45
 46        - apublier_data : liste de dictionnaires donnant les timestamps
 47        des fichiers susceptibles d'être publiés.
 48            
 49                [{chemin de fichier local : timestamp du fichier},]
 50
 51        #### Renvoie
 52
 53        None.
 54
 55        """
 56        connect_data = data['credentials']
 57        self.log = "\t Initialisation de la classe Espace \n"
 58        r_n = connect_data['region_name']
 59        e_u = connect_data['endpoint_url']
 60        self.bck_nom = connect_data['bucket']
 61        self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u)
 62        s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u)
 63        self.bucket = s3.Bucket(connect_data['bucket'])
 64        self.prefix = connect_data['prefix']
 65        self.apublier = apublier_data  # dictio path: date
 66
 67        self.bck_times = self.get_times()  # dictio key: date
 68
 69        self.MAJ()
 70
 71    def get_times(self):
 72        """
 73        Renvoie un dictionnaire donnant les dates de modifications des
 74        fichiers de l'espace.
 75
 76               clé = key d'un fichier du dépôt dans l'espace
 77               valeur = date modification
 78        """
 79        key_times = {}
 80        for obj in self.bucket.objects.all():
 81            if (obj.key.startswith(self.prefix)
 82                    and len(obj.key) > len(self.prefix)):
 83                key_times[obj.key] = obj.last_modified
 84        self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n"
 85        return key_times
 86
 87    def del_objs(self, keys):
 88        """
 89        Supprime des objets et rend compte dans le journal.
 90        
 91        #### Paramètres:
 92        
 93        - `keys`: liste des clés à supprimer dans l'espace'
 94
 95        #### Renvoie
 96        
 97        None
 98        """
 99        objs = []
100        for key in keys:
101            objs.append({'Key': key})
102        response = self.bucket.delete_objects(Delete={'Objects': objs})
103        if 'Deleted' in response:
104            self.log += "\n Journal des suppressions de l'espace: \n"
105            self.log += 'Deleted : '
106            for deleted in response['Deleted']:
107                self.log += deleted['Key'] + ', '
108
109    def upload_file(self, path, key):
110        """
111        Upload un fichier dans l'espace.
112
113        Avec un ACL public et un Content-Type déduit de l'extension.
114
115        #### Parametres
116        
117        - path : chemin du fichier à uploader dans l'espace.
118        - key : clé du fichier uploadé dans l'espace.
119
120        Renvoie
121        -------
122        None.
123
124        """
125        file_name = os.path.basename(path)
126        file_mime_type, encoding = mimetypes.guess_type(file_name)
127        self.bucket.upload_file(path, key,
128                                ExtraArgs={'ACL': 'public-read',
129                                           'ContentType': file_mime_type})
130
131    def MAJ(self):
132        """
133        Mise à jour de l'espace.
134
135        Avec self.bck_times et self.apublier:
136              - supprime dans l'espace les clés
137                  qui ne correspondent pas à un fichier
138              - si fichier plus récent que clé
139                  upload du fichier dans la clé
140        """
141        key_times = self.bck_times
142        fic_times = self.apublier
143
144        # supprimer les keys qui ne sont pas des fics
145        fics = [os.path.basename(t) for t in fic_times]
146        a_supprimer = []
147        for key in key_times:
148            if os.path.basename(key) not in fics:
149                a_supprimer.append(key)
150        self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n"
151        self.del_objs(a_supprimer)
152
153        # mettre à jour les keys correspondant à des fics
154        self.log += "\t \t  clés à créer ou mettre à jour \n"
155        for path in fic_times:
156            file_name = os.path.basename(path)
157            key_a_updater = self.prefix + file_name
158            fic_a_updater_time = fic_times[path]
159            #self.log += '\t \t '  ajoute des espaces inutiles si rien à updater
160            if key_a_updater in key_times:
161                key_a_updater_time = key_times[key_a_updater].timestamp()
162                if fic_a_updater_time > key_a_updater_time:
163                    self.log += key_a_updater + " : upload (maj)\n "
164                    self.upload_file(path, key_a_updater)
165            else:
166                self.log += key_a_updater + " : upload (créer) \n "
167                self.upload_file(path, key_a_updater)
168        self.log += '\n'
169
170    """
171    Méthodes qui ne sont pas utilisées
172
173    def put_objs(self, keys):
174        #Place (put) les objets de la liste keys dans l'espace.
175        #Cette méthode n'est pas utilisé actuellement, remplacée par upload'
176
177        jrnl = ''
178        bck = self.bck_nom
179        acl = 'public-read'
180        ct = 'application/pdf'
181        for key in keys:
182            bdy = open(key, mode='rb')
183            response = self.client.put_object(ACL=acl, Body=bdy, Bucket=bck,
184                                              Key=key, ContentType=ct)
185            print(str(response))
186            jrnl += '\n ' + key + ' : ' + str(response) + '\n'
187        return jrnl
188
189    def get_private_objs(self):
190        #renvoie la liste des objets avec un acl privé
191
192        bck = self.bck_nom
193        response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000)
194        objs = [content['Key']for content in response['Contents']]
195        objs_p = copy.copy(objs)
196        for key in objs:
197            response = self.client.get_object_acl(Bucket=bck, Key=key)
198            for grant in response['Grants']:
199                permission = grant['Permission']
200                grantee = grant['Grantee']
201                # print(key, grantee)
202                if 'URI' in grantee:
203                    grp = os.path.basename(grantee['URI'])
204                    # print(key, grp, permission)
205                    if (grp == 'AllUsers') and (permission == 'READ'):
206                        objs_p.remove(key)
207        return objs_p
208
209    def list_buckets(self):
210        response = self.client.list_buckets()
211        spaces = [space['Name'] for space in response['Buckets']]
212        print("Spaces List: %s" % spaces)
213
214    def get_objs(self):
215        bck = self.bck_nom
216        response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000)
217        objs_t = [[content['Key'], content['LastModified'].timestamp()]
218                  for content in response['Contents']]
219        return objs_t
220
221    """
class Espace:
 19class Espace:
 20    """
 21    Classe Espace.
 22
 23    """
 24
 25    def __init__(self, data, apublier_data):
 26        """
 27        Initialise la classe et met à jour l'espace.
 28
 29        Pour les fichiers publiables cad ceux de "apublier_data":
 30
 31        - récupère les clés associées et leur timestamp dans l'espace
 32        - si le fichier local est plus récent:
 33                      upload sur l'espace
 34        #### Paramètres:
 35        - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple :
 36        
 37                {
 38                    "region_name" : "fra1",
 39                    "endpoint_url" : "https://fra1.digitaloceanspaces.com",
 40                    "bucket" : "maquisdoc-math",
 41                    "prefix" : "math-exos/"
 42                }
 43                
 44            Les données secrètes de connexion sont dans le fichier local
 45            `~/.aws/credentials`.
 46
 47        - apublier_data : liste de dictionnaires donnant les timestamps
 48        des fichiers susceptibles d'être publiés.
 49            
 50                [{chemin de fichier local : timestamp du fichier},]
 51
 52        #### Renvoie
 53
 54        None.
 55
 56        """
 57        connect_data = data['credentials']
 58        self.log = "\t Initialisation de la classe Espace \n"
 59        r_n = connect_data['region_name']
 60        e_u = connect_data['endpoint_url']
 61        self.bck_nom = connect_data['bucket']
 62        self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u)
 63        s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u)
 64        self.bucket = s3.Bucket(connect_data['bucket'])
 65        self.prefix = connect_data['prefix']
 66        self.apublier = apublier_data  # dictio path: date
 67
 68        self.bck_times = self.get_times()  # dictio key: date
 69
 70        self.MAJ()
 71
 72    def get_times(self):
 73        """
 74        Renvoie un dictionnaire donnant les dates de modifications des
 75        fichiers de l'espace.
 76
 77               clé = key d'un fichier du dépôt dans l'espace
 78               valeur = date modification
 79        """
 80        key_times = {}
 81        for obj in self.bucket.objects.all():
 82            if (obj.key.startswith(self.prefix)
 83                    and len(obj.key) > len(self.prefix)):
 84                key_times[obj.key] = obj.last_modified
 85        self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n"
 86        return key_times
 87
 88    def del_objs(self, keys):
 89        """
 90        Supprime des objets et rend compte dans le journal.
 91        
 92        #### Paramètres:
 93        
 94        - `keys`: liste des clés à supprimer dans l'espace'
 95
 96        #### Renvoie
 97        
 98        None
 99        """
100        objs = []
101        for key in keys:
102            objs.append({'Key': key})
103        response = self.bucket.delete_objects(Delete={'Objects': objs})
104        if 'Deleted' in response:
105            self.log += "\n Journal des suppressions de l'espace: \n"
106            self.log += 'Deleted : '
107            for deleted in response['Deleted']:
108                self.log += deleted['Key'] + ', '
109
110    def upload_file(self, path, key):
111        """
112        Upload un fichier dans l'espace.
113
114        Avec un ACL public et un Content-Type déduit de l'extension.
115
116        #### Parametres
117        
118        - path : chemin du fichier à uploader dans l'espace.
119        - key : clé du fichier uploadé dans l'espace.
120
121        Renvoie
122        -------
123        None.
124
125        """
126        file_name = os.path.basename(path)
127        file_mime_type, encoding = mimetypes.guess_type(file_name)
128        self.bucket.upload_file(path, key,
129                                ExtraArgs={'ACL': 'public-read',
130                                           'ContentType': file_mime_type})
131
132    def MAJ(self):
133        """
134        Mise à jour de l'espace.
135
136        Avec self.bck_times et self.apublier:
137              - supprime dans l'espace les clés
138                  qui ne correspondent pas à un fichier
139              - si fichier plus récent que clé
140                  upload du fichier dans la clé
141        """
142        key_times = self.bck_times
143        fic_times = self.apublier
144
145        # supprimer les keys qui ne sont pas des fics
146        fics = [os.path.basename(t) for t in fic_times]
147        a_supprimer = []
148        for key in key_times:
149            if os.path.basename(key) not in fics:
150                a_supprimer.append(key)
151        self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n"
152        self.del_objs(a_supprimer)
153
154        # mettre à jour les keys correspondant à des fics
155        self.log += "\t \t  clés à créer ou mettre à jour \n"
156        for path in fic_times:
157            file_name = os.path.basename(path)
158            key_a_updater = self.prefix + file_name
159            fic_a_updater_time = fic_times[path]
160            #self.log += '\t \t '  ajoute des espaces inutiles si rien à updater
161            if key_a_updater in key_times:
162                key_a_updater_time = key_times[key_a_updater].timestamp()
163                if fic_a_updater_time > key_a_updater_time:
164                    self.log += key_a_updater + " : upload (maj)\n "
165                    self.upload_file(path, key_a_updater)
166            else:
167                self.log += key_a_updater + " : upload (créer) \n "
168                self.upload_file(path, key_a_updater)
169        self.log += '\n'
170
171    """
172    Méthodes qui ne sont pas utilisées
173
174    def put_objs(self, keys):
175        #Place (put) les objets de la liste keys dans l'espace.
176        #Cette méthode n'est pas utilisé actuellement, remplacée par upload'
177
178        jrnl = ''
179        bck = self.bck_nom
180        acl = 'public-read'
181        ct = 'application/pdf'
182        for key in keys:
183            bdy = open(key, mode='rb')
184            response = self.client.put_object(ACL=acl, Body=bdy, Bucket=bck,
185                                              Key=key, ContentType=ct)
186            print(str(response))
187            jrnl += '\n ' + key + ' : ' + str(response) + '\n'
188        return jrnl
189
190    def get_private_objs(self):
191        #renvoie la liste des objets avec un acl privé
192
193        bck = self.bck_nom
194        response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000)
195        objs = [content['Key']for content in response['Contents']]
196        objs_p = copy.copy(objs)
197        for key in objs:
198            response = self.client.get_object_acl(Bucket=bck, Key=key)
199            for grant in response['Grants']:
200                permission = grant['Permission']
201                grantee = grant['Grantee']
202                # print(key, grantee)
203                if 'URI' in grantee:
204                    grp = os.path.basename(grantee['URI'])
205                    # print(key, grp, permission)
206                    if (grp == 'AllUsers') and (permission == 'READ'):
207                        objs_p.remove(key)
208        return objs_p
209
210    def list_buckets(self):
211        response = self.client.list_buckets()
212        spaces = [space['Name'] for space in response['Buckets']]
213        print("Spaces List: %s" % spaces)
214
215    def get_objs(self):
216        bck = self.bck_nom
217        response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000)
218        objs_t = [[content['Key'], content['LastModified'].timestamp()]
219                  for content in response['Contents']]
220        return objs_t
221
222    """

Classe Espace.

Espace(data, apublier_data)
25    def __init__(self, data, apublier_data):
26        """
27        Initialise la classe et met à jour l'espace.
28
29        Pour les fichiers publiables cad ceux de "apublier_data":
30
31        - récupère les clés associées et leur timestamp dans l'espace
32        - si le fichier local est plus récent:
33                      upload sur l'espace
34        #### Paramètres:
35        - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple :
36        
37                {
38                    "region_name" : "fra1",
39                    "endpoint_url" : "https://fra1.digitaloceanspaces.com",
40                    "bucket" : "maquisdoc-math",
41                    "prefix" : "math-exos/"
42                }
43                
44            Les données secrètes de connexion sont dans le fichier local
45            `~/.aws/credentials`.
46
47        - apublier_data : liste de dictionnaires donnant les timestamps
48        des fichiers susceptibles d'être publiés.
49            
50                [{chemin de fichier local : timestamp du fichier},]
51
52        #### Renvoie
53
54        None.
55
56        """
57        connect_data = data['credentials']
58        self.log = "\t Initialisation de la classe Espace \n"
59        r_n = connect_data['region_name']
60        e_u = connect_data['endpoint_url']
61        self.bck_nom = connect_data['bucket']
62        self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u)
63        s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u)
64        self.bucket = s3.Bucket(connect_data['bucket'])
65        self.prefix = connect_data['prefix']
66        self.apublier = apublier_data  # dictio path: date
67
68        self.bck_times = self.get_times()  # dictio key: date
69
70        self.MAJ()

Initialise la classe et met à jour l'espace.

Pour les fichiers publiables cad ceux de "apublier_data":

  • récupère les clés associées et leur timestamp dans l'espace
  • si le fichier local est plus récent: upload sur l'espace

    Paramètres:

  • data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple :

    {
        "region_name" : "fra1",
        "endpoint_url" : "https://fra1.digitaloceanspaces.com",
        "bucket" : "maquisdoc-math",
        "prefix" : "math-exos/"
    }
    

    Les données secrètes de connexion sont dans le fichier local ~/.aws/credentials.

  • apublier_data : liste de dictionnaires donnant les timestamps des fichiers susceptibles d'être publiés.

    [{chemin de fichier local : timestamp du fichier},]
    

Renvoie

None.

def get_times(self):
72    def get_times(self):
73        """
74        Renvoie un dictionnaire donnant les dates de modifications des
75        fichiers de l'espace.
76
77               clé = key d'un fichier du dépôt dans l'espace
78               valeur = date modification
79        """
80        key_times = {}
81        for obj in self.bucket.objects.all():
82            if (obj.key.startswith(self.prefix)
83                    and len(obj.key) > len(self.prefix)):
84                key_times[obj.key] = obj.last_modified
85        self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n"
86        return key_times

Renvoie un dictionnaire donnant les dates de modifications des fichiers de l'espace.

   clé = key d'un fichier du dépôt dans l'espace
   valeur = date modification
def del_objs(self, keys):
 88    def del_objs(self, keys):
 89        """
 90        Supprime des objets et rend compte dans le journal.
 91        
 92        #### Paramètres:
 93        
 94        - `keys`: liste des clés à supprimer dans l'espace'
 95
 96        #### Renvoie
 97        
 98        None
 99        """
100        objs = []
101        for key in keys:
102            objs.append({'Key': key})
103        response = self.bucket.delete_objects(Delete={'Objects': objs})
104        if 'Deleted' in response:
105            self.log += "\n Journal des suppressions de l'espace: \n"
106            self.log += 'Deleted : '
107            for deleted in response['Deleted']:
108                self.log += deleted['Key'] + ', '

Supprime des objets et rend compte dans le journal.

Paramètres:

  • keys: liste des clés à supprimer dans l'espace'

Renvoie

None

def upload_file(self, path, key):
110    def upload_file(self, path, key):
111        """
112        Upload un fichier dans l'espace.
113
114        Avec un ACL public et un Content-Type déduit de l'extension.
115
116        #### Parametres
117        
118        - path : chemin du fichier à uploader dans l'espace.
119        - key : clé du fichier uploadé dans l'espace.
120
121        Renvoie
122        -------
123        None.
124
125        """
126        file_name = os.path.basename(path)
127        file_mime_type, encoding = mimetypes.guess_type(file_name)
128        self.bucket.upload_file(path, key,
129                                ExtraArgs={'ACL': 'public-read',
130                                           'ContentType': file_mime_type})

Upload un fichier dans l'espace.

Avec un ACL public et un Content-Type déduit de l'extension.

Parametres

  • path : chemin du fichier à uploader dans l'espace.
  • key : clé du fichier uploadé dans l'espace.

Renvoie

None.

def MAJ(self):
132    def MAJ(self):
133        """
134        Mise à jour de l'espace.
135
136        Avec self.bck_times et self.apublier:
137              - supprime dans l'espace les clés
138                  qui ne correspondent pas à un fichier
139              - si fichier plus récent que clé
140                  upload du fichier dans la clé
141        """
142        key_times = self.bck_times
143        fic_times = self.apublier
144
145        # supprimer les keys qui ne sont pas des fics
146        fics = [os.path.basename(t) for t in fic_times]
147        a_supprimer = []
148        for key in key_times:
149            if os.path.basename(key) not in fics:
150                a_supprimer.append(key)
151        self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n"
152        self.del_objs(a_supprimer)
153
154        # mettre à jour les keys correspondant à des fics
155        self.log += "\t \t  clés à créer ou mettre à jour \n"
156        for path in fic_times:
157            file_name = os.path.basename(path)
158            key_a_updater = self.prefix + file_name
159            fic_a_updater_time = fic_times[path]
160            #self.log += '\t \t '  ajoute des espaces inutiles si rien à updater
161            if key_a_updater in key_times:
162                key_a_updater_time = key_times[key_a_updater].timestamp()
163                if fic_a_updater_time > key_a_updater_time:
164                    self.log += key_a_updater + " : upload (maj)\n "
165                    self.upload_file(path, key_a_updater)
166            else:
167                self.log += key_a_updater + " : upload (créer) \n "
168                self.upload_file(path, key_a_updater)
169        self.log += '\n'

Mise à jour de l'espace.

Avec self.bck_times et self.apublier: - supprime dans l'espace les clés qui ne correspondent pas à un fichier - si fichier plus récent que clé upload du fichier dans la clé