Skip to content

Commit df52607

Browse files
Feat: Ajout de l'exportation des données (#30)
- Création du dossier export - Création de la classe Exporteur dans le module exporteur.py - Ajout de l'exception ExportationException - Utilisation de l'exportation dans le module main.py - Ajout des tests unitaires pour la classe Exporteur - Ajout de la documentation pour la classe Exporteur
1 parent 9a74e6c commit df52607

8 files changed

Lines changed: 241 additions & 5 deletions

File tree

app/export/exporteur.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Module pour l'exportation des données.
3+
"""
4+
5+
from os.path import abspath, dirname, isdir
6+
from json import dump
7+
8+
9+
class Exporteur:
10+
"""
11+
Représente un exporteur de données pour exporter des données
12+
vers un fichier de sortie.
13+
14+
Attributes:
15+
_chemin_sortie (str): Le chemin du fichier vers lequel
16+
les données seront exportées.
17+
"""
18+
19+
def __init__(self, chemin_sortie: str):
20+
"""
21+
Initialise un exporteur de données.
22+
23+
Args:
24+
chemin_sortie (str): Le chemin du fichier vers lequel
25+
les données seront exportées.
26+
27+
Raises:
28+
TypeError: Le chemin de sortie n'est pas une chaîne de caractère.
29+
ExportationException: Exportation impossible à cause de
30+
l'emplacement invalide du fichier de sortie.
31+
"""
32+
if not isinstance(chemin_sortie, str):
33+
raise TypeError("Le chemin de sortie doit être une chaîne de caractère.")
34+
chemin_sortie_absolue = abspath(chemin_sortie)
35+
dossier_parent = dirname(chemin_sortie_absolue)
36+
if not isdir(dossier_parent):
37+
raise ExportationException(f"Impossible d'exporter vers le "
38+
f"fichier {chemin_sortie}, son dossier parent "
39+
f"{dossier_parent} n'existe pas.")
40+
self._chemin_sortie = chemin_sortie
41+
42+
def export_vers_json(self, donnees: dict):
43+
"""
44+
Export le dictionnaire fourni vers le :attr:`chemin de sortie`.
45+
46+
Args:
47+
donnees (dict): Le dictionnaire qui contient les données.
48+
49+
Returns:
50+
None
51+
52+
Raises:
53+
TypeError: Le paramètre ``donnees`` n'est pas un dictionnaire.
54+
ExportationException: Une erreur lors de l'écriture dans le fichier JSON.
55+
"""
56+
if not isinstance(donnees, dict):
57+
raise TypeError("Les données à exporter doivent être sous une forme "
58+
"de dictionnaire.")
59+
60+
try:
61+
with open(self._chemin_sortie, 'w') as fichier:
62+
dump(donnees, fichier, indent=4)
63+
except Exception as ex:
64+
raise ExportationException(str(ex)) from ex
65+
66+
class ExportationException(Exception):
67+
"""
68+
Représente une erreur lors de l'exportation de données.
69+
"""
70+
71+
def __init__(self, *args):
72+
super().__init__(*args)

app/main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from cli.parseur_arguments_cli import ParseurArgumentsCLI, ArgumentCLIException
77
from parse.parseur_log_apache import ParseurLogApache, FormatLogApacheInvalideException
88
from analyse.analyseur_log_apache import AnalyseurLogApache
9+
from export.exporteur import Exporteur, ExportationException
910

1011

1112
def main():
@@ -36,12 +37,16 @@ def main():
3637
analyseur_log = AnalyseurLogApache(fichier_log)
3738
analyse = analyseur_log.get_analyse_complete()
3839
# Exportation de l'analyse
40+
exporteur = Exporteur(arguments_cli.sortie)
41+
exporteur.export_vers_json(analyse)
3942
except ArgumentCLIException as ex:
4043
print(f"Erreur dans les arguments fournis !\n {ex}")
4144
except FileNotFoundError as ex:
4245
print(f"Erreur dans la recherche du log Apache !\n{ex}")
4346
except FormatLogApacheInvalideException as ex:
4447
print(f"Erreur dans l'analyse du log Apache !\n{ex}")
48+
except ExportationException as ex:
49+
print(f"Erreur dans l'exportation de l'analyse !\n{ex}")
4550
except Exception as ex:
4651
print(f"Erreur interne !\n{ex}")
4752

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Exporteur
2+
==========
3+
4+
.. automodule:: export.exporteur
5+
:members:
6+
:show-inheritance:
7+
:undoc-members:
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Export
2+
===========
3+
4+
.. toctree::
5+
:maxdepth: 4
6+
7+
exporteur.rst

docs/source/modules/index_modules.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ Modules
99
cli/index_cli.rst
1010
parse/index_parse.rst
1111
donnees/index_donnees.rst
12-
analyse/index_analyse.rst
12+
analyse/index_analyse.rst
13+
export/index_export.rst

tests/conftest.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
import pytest
66
from cli.parseur_arguments_cli import ParseurArgumentsCLI
7-
from parse.fichier_log_apache import FichierLogApache
87
from parse.parseur_log_apache import ParseurLogApache
98
from analyse.analyseur_log_apache import AnalyseurLogApache
9+
from export.exporteur import Exporteur
1010

1111

1212
# -----------------
@@ -98,8 +98,8 @@ def parseur_log_apache(log_apache, request):
9898
Fixture pour initialiser un parseur de fichier de log Apache.
9999
100100
Args:
101-
log_apache: La fixture pour initialiser un fichier temporaire.
102-
request: Paramètre de la fonction. Si il est égale à ``False``, cette fixture
101+
log_apache (Path): La fixture pour initialiser un fichier temporaire.
102+
request (object): Paramètre de la fonction. Si il est égale à ``False``, cette fixture
103103
retourne un parseur de log Apache qui analyse un fichier avec un format
104104
invalide. Sinon, retourne un parseur de log Apache qui analyse un fichier
105105
avec un format valide.
@@ -157,4 +157,32 @@ def analyseur_log_apache(fichier_log_apache):
157157
Returns:
158158
AnalyseurLogApache: Une instance de la classe :class:`AnalyseurLogApache`.
159159
"""
160-
return AnalyseurLogApache(fichier_log_apache)
160+
return AnalyseurLogApache(fichier_log_apache)
161+
162+
@pytest.fixture
163+
def fichier_json(tmp_path):
164+
"""
165+
Fixture pour retourner un chemin de fichier JSON temporaire.
166+
167+
Args:
168+
tmp_path (Path): Chemin temporaire fourni par pytest.
169+
170+
Returns:
171+
Path: Un chemin de fichier JSON temporaire.
172+
"""
173+
fichier_temp = tmp_path / "sortie.json"
174+
return fichier_temp
175+
176+
@pytest.fixture
177+
def exporteur(fichier_json):
178+
"""
179+
Fixture pour initialiser un exportateur de données.
180+
181+
Args:
182+
fichier_json (Path): Fixture pour initialiser
183+
un chemin de fichier json temporaire.
184+
185+
Returns:
186+
Exporteur: Une instance de la classe :class:`Exportateur`.
187+
"""
188+
return Exporteur(str(fichier_json))

tests/test_exporteur.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Module des tests unitaires pour l'exporteur de données.
3+
"""
4+
5+
import pytest
6+
from json import load
7+
from export.exporteur import Exporteur, ExportationException
8+
9+
@pytest.mark.parametrize("chemin_sortie", [
10+
(0), (None), ([])
11+
])
12+
def test_exporteur_type_chemin_invalide(chemin_sortie):
13+
"""
14+
Vérifie que la classe renvoie une erreur lorsque un argument de type invalide
15+
est passé dans le constructeur.
16+
17+
Scénarios testés:
18+
- Type incorrect pour le paramètre ``chemin_sortie``.
19+
20+
Asserts:
21+
- Une exception :class:`TypeError` est levée.
22+
23+
Args:
24+
chemin_sortie (any): Le chemin de sortie utilisé dans le constructeur.
25+
"""
26+
with pytest.raises(TypeError):
27+
exporteur = Exporteur(chemin_sortie)
28+
29+
def test_exporteur_emplacement_inexistant():
30+
"""
31+
Vérifie que la classe renvoie une erreur lorsque un chemin de fichier invalide
32+
est passé dans le constructeur.
33+
34+
Scénarios testés:
35+
- Chemin invalide pour le paramètre ``chemin_sortie``.
36+
37+
Asserts:
38+
- Une exception :class:`ExportationException` est levée.
39+
"""
40+
with pytest.raises(ExportationException):
41+
exporteur = Exporteur("dossier/inexistant/sortie.json")
42+
43+
@pytest.mark.parametrize("donnees", [
44+
(0), (None), ([])
45+
])
46+
def test_exportateur_export_json_type_donnees_invalide(exporteur, donnees):
47+
"""
48+
Vérifie que la classe renvoie une erreur lorsque un argument de type invalide
49+
est passé dans la méthode ``export_vers_json``.
50+
51+
Scénarios testés:
52+
- Type incorrect pour le paramètre ``données``.
53+
54+
Asserts:
55+
- Une exception :class:`TypeError` est levée.
56+
57+
Args:
58+
exporteur (Exporteur) : Fixture pour l'instance de la classe :class:`Exporteur`.
59+
donnees (any): Les données à exporter.
60+
"""
61+
with pytest.raises(TypeError):
62+
exporteur.export_vers_json("type invalide")
63+
64+
@pytest.mark.parametrize("exception", [
65+
(PermissionError("Pas les droits")),
66+
(FileNotFoundError("Fichier non trouvé.")),
67+
(Exception("Toutes exceptions"))
68+
])
69+
def test_exportateur_export_json_exception_exportation(exporteur, mocker, exception):
70+
"""
71+
Vérifie que la classe renvoie l'exception :class:`ExportationException` lorsque
72+
une erreur apparait durant l'exportation des données.
73+
74+
Scénarios testés:
75+
- Une exception :class:`PermissionError` survient.
76+
- Une exception :class:`FileNotFoundError` survient.
77+
- Une exception :class:`Exception` survient.
78+
79+
Asserts:
80+
- Une exception :class:`ExportationException` est levée.
81+
82+
Args:
83+
exporteur (Exporteur) : Fixture pour l'instance de la classe :class:`Exporteur`.
84+
mocker (any): Une fixture pour simuler des exceptions.
85+
donnees (any): Les données à exporter.
86+
"""
87+
mocker.patch("builtins.open", side_effect=exception)
88+
with pytest.raises(ExportationException):
89+
exporteur.export_vers_json({})
90+
91+
def test_exportateur_exportation_json_valide(exporteur, fichier_json):
92+
"""
93+
Vérifie que la méthode ``export_vers_json`` exporte correctement les données
94+
vers une fichier.
95+
96+
Scénarios testés:
97+
- Exportation d'un dictionnaire lambda.
98+
99+
Asserts:
100+
- Le fichier est bien crée.
101+
- Les données dans le fichier sont conformes à celles fournies.
102+
103+
Args:
104+
exporteur (Exporteur) : Fixture pour l'instance de la classe :class:`Exporteur`.
105+
fichier_json (Path): Le chemin du fichier.
106+
"""
107+
donnees = {"cle": {"valeur": [1, 2, 3]}}
108+
exporteur.export_vers_json(donnees)
109+
assert fichier_json.exists()
110+
with open(fichier_json, "r") as exportation:
111+
contenu_exportation = load(exportation)
112+
assert contenu_exportation == donnees

tests/test_main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
from main import main
88
from cli.parseur_arguments_cli import ArgumentCLIException
99
from parse.parseur_log_apache import FormatLogApacheInvalideException
10+
from export.exporteur import ExportationException
1011

1112
@pytest.mark.parametrize("exception", [
1213
(ArgumentCLIException),
1314
(FileNotFoundError),
1415
(FormatLogApacheInvalideException),
16+
(ExportationException),
1517
(Exception)
1618
])
1719
def test_main_gestion_exception(mocker, exception):
@@ -50,6 +52,8 @@ def test_main_succes(mocker):
5052

5153
mock_analyseur_log = mocker.patch("main.AnalyseurLogApache")
5254
mock_analyseur_log.return_value.get_analyse_complete.return_value = {"chemin": "test.log"}
55+
56+
mocker.patch("main.Exporteur")
5357

5458
# Vérifie qu'aucune exception n'est levée
5559
try:

0 commit comments

Comments
 (0)