Skip to content

Commit 1c5576b

Browse files
Feat: Amélioration des classes de données et du parseur (#19)
- Utilisation de la bibliothèque typing et dataclasses - Simplification de la classe en dataclass - Ajout de vérification des types des attributs - Modifiation du test unitaire test_parsage_entree_valide suite à une erreur due aux précédents commits qui ont modifié le nom de l'attribut code_statut_http - Ajout de commentaires et de sauts de ligne aux méthodes de ParseurLogApache afin de rendre le fichier plus lisible - Suppression de la méthode __fichier_existe dans ParseurLogApache qui était utilisée qu'une seule fois en déplacant son contenu directement dans le __init__ - Ajout d'un except pour les Exceptions qui affiche un message en indiquant qu'une erreur interne s'est produite
1 parent 4870023 commit 1c5576b

6 files changed

Lines changed: 147 additions & 51 deletions

File tree

app/donnees/client_informations.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,45 @@
22
Module relatif aux informations d'un client dans un fichier de log Apache.
33
"""
44

5+
from dataclasses import dataclass
6+
from typing import Optional
57

8+
9+
@dataclass
610
class ClientInformations:
11+
"""
12+
Représente les informations d'un client à partir d'une entrée d'un log Apache.
13+
14+
Cette classe regroupe les données extraites d'une entrée de log,
15+
qui concernent le client ayant effectué la requête au serveur Apache.
16+
17+
Attributes:
18+
adresse_ip (str): L'adresse IP du client.
19+
identifiant_rfc (Optional[str]): L'identifiant RFC du client.
20+
Peut être None si non fournie.
21+
nom_utilisateur (Optional[str]): Le nom de l'utilisateur authentifié.
22+
Peut être None si non fournie.
23+
agent_utilisateur (Optional[str]): L'agent utilisateur (User-Agent).
24+
Peut être None si non fournie.
25+
26+
Raises:
27+
TypeError: Si les attributs ne sont pas de type `str` ou `None`.
28+
"""
29+
adresse_ip: str
30+
identifiant_rfc: Optional[str]
31+
nom_utilisateur: Optional[str]
32+
agent_utilisateur: Optional[str]
733

8-
def __init__(self,
9-
adresse_ip,
10-
identifiant_rfc,
11-
nom_utilisateur,
12-
agent_utilisateur
13-
):
14-
self.adresse_ip = adresse_ip
15-
self.identifiant_rfc = identifiant_rfc
16-
self.nom_utilisateur = nom_utilisateur
17-
self.agent_utilisateur = agent_utilisateur
34+
def __post_init__(self):
35+
# Validation de l'adresse IP
36+
if not isinstance(self.adresse_ip, str):
37+
raise TypeError("L'adresse IP est obligatoire et doit être une chaîne de caractères.")
38+
# Validation de l'identifiant RFC
39+
if self.identifiant_rfc != None and not isinstance(self.identifiant_rfc, str):
40+
raise TypeError("L'identifiant RFC doit être une chaîne de caractères ou None.")
41+
# Validation du nom d'utilisateur
42+
if self.nom_utilisateur != None and not isinstance(self.nom_utilisateur, str):
43+
raise TypeError("Le nom d'utilisateur doit être une chaîne de caractères ou None.")
44+
# Validation de l'agent utilisateur
45+
if self.agent_utilisateur != None and not isinstance(self.agent_utilisateur, str):
46+
raise TypeError("L'agent utilisateur doit être une chaîne de caractères ou None.")

app/donnees/reponse_informations.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22
Module relatif aux informations de la réponse dans un fichier de log Apache.
33
"""
44

5+
from dataclasses import dataclass
6+
from typing import Optional
57

8+
9+
@dataclass
610
class ReponseInformations:
11+
"""
12+
Représente les informations de la réponse HTTP à partir d'une entrée d'un log Apache.
13+
14+
Cette classe regroupe les données extraites d'une entrée de log,
15+
qui concernent les informations techniques sur la réponse émise par
16+
le serveur Apache au client.
17+
18+
Attributes:
19+
code_statut_http (int): Le code de statut HTTP.
20+
taille_octets (Optional[int]): La taille de la réponse en octets.
21+
Peut être None si non fournie.
22+
23+
Raises:
24+
TypeError: Si les attributs ne sont pas du type int.
25+
"""
26+
27+
code_statut_http: int
28+
taille_octets: Optional[int]
729

8-
def __init__(self,
9-
code_status_http,
10-
taille_octets
11-
):
12-
self.code_status_http = code_status_http
13-
self.taille_octets = taille_octets
30+
def __post_init__(self):
31+
# Vérification du code de statut HTTP
32+
if not isinstance(self.code_statut_http, int):
33+
raise TypeError("Le code de statut HTTP doit être un entier.")
34+
# Vérification de la taille de la réponse (en octets)
35+
if self.taille_octets != None and not isinstance(self.taille_octets, int):
36+
raise TypeError("La taille en octets doit être un entier ou None.")

app/donnees/requete_informations.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,52 @@
22
Module relatif aux informations de la requete dans un fichier de log Apache.
33
"""
44

5+
from dataclasses import dataclass
6+
from typing import Optional
7+
from datetime import datetime
58

9+
@dataclass
610
class RequeteInformations:
11+
"""
12+
Représente les informations de la requête HTTP à partir d'une entrée d'un log Apache.
713
8-
def __init__(self,
9-
horodatage,
10-
methode_http,
11-
url,
12-
protocole_http,
13-
ancienne_url
14-
):
15-
self.horodatage = horodatage
16-
self.methode_http = methode_http
17-
self.url = url
18-
self.protocole_http = protocole_http
19-
self.ancienne_url = ancienne_url
14+
Cette classe regroupe les données extraites d'une entrée de log,
15+
qui concernent les informations techniques sur la requête émise au
16+
serveur Apache.
17+
18+
Attributes:
19+
horodatage (datetime): L'horodatage de la requête.
20+
methode_http (Optional[str]): La méthode HTTP utilisée.
21+
Peut être None si non fournie.
22+
url (Optional[str]): L'URL cible de la requête.
23+
Peut être None si non fournie.
24+
protocole_http (Optional[str]): Le protocole HTTP utilisé.
25+
Peut être None si non fournie.
26+
ancienne_url (Optional[str]): L'URL de provenance (referrer).
27+
Peut être None si non fournie.
28+
29+
Raises:
30+
TypeError: Si les attributs ne sont pas du type attendu ou None.
31+
"""
32+
horodatage: datetime
33+
methode_http: Optional[str]
34+
url: Optional[str]
35+
protocole_http: Optional[str]
36+
ancienne_url: Optional[str]
37+
38+
def __post_init__(self):
39+
# Vérification de l'horodatage
40+
if not isinstance(self.horodatage, datetime):
41+
raise TypeError("L'horodatage doit être de type datetime.")
42+
# Vérification de la méthode HTTP
43+
if self.methode_http != None and not isinstance(self.methode_http, str):
44+
raise TypeError("La méthode HTTP doit être une chaine de caractère ou None.")
45+
# Vérification de la ressource demandée
46+
if self.url != None and not isinstance(self.url, str):
47+
raise TypeError("L'URL doit être une chaine de caractère ou None.")
48+
# Vérification du protocole HTTP
49+
if self.protocole_http != None and not isinstance(self.protocole_http, str):
50+
raise TypeError("Le protocole HTTP doit être une chaine de caractère ou None.")
51+
# Vérification de l'ancienne URL
52+
if self.ancienne_url != None and not isinstance(self.ancienne_url, str):
53+
raise TypeError("L'ancienne URL doit être une chaine de caractère ou None.")

app/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@
3636
print(f"Erreur dans la recherche du log Apache !\n{ex}")
3737
except FormatLogApacheInvalideException as ex:
3838
print(f"Erreur dans l'analyse du log Apache !\n{ex}")
39+
except Exception as ex:
40+
print(f"Erreur interne !\n{ex}")

app/parse/parseur_log_apache.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,9 @@ def __init__(self, chemin_log):
3636
Raises:
3737
FileNotFoundError: Si le fichier à analyser est introuvable.
3838
"""
39-
if not self.__fichier_existe(chemin_log):
39+
if not os.path.isfile(chemin_log):
4040
raise FileNotFoundError(f"Le fichier {chemin_log} est introuvable.")
4141
self.chemin_log = chemin_log
42-
43-
def __fichier_existe(self, chemin_fichier):
44-
"""
45-
Vérifie que le chemin passé en paramètre correspond à une fichier existant.
46-
Returns:
47-
bool: True s'il existe, False sinon.
48-
"""
49-
if not os.path.isfile(chemin_fichier):
50-
return False
51-
return True
5242

5343
def parse_fichier(self):
5444
"""
@@ -60,17 +50,17 @@ def parse_fichier(self):
6050
FormatLogApacheInvalideException: Format du fichier log invalide.
6151
"""
6252
log_analyse = FichierLogApache(self.chemin_log)
63-
numero_ligne = 1
6453
with open(self.chemin_log, "r") as log:
65-
for ligne in log:
54+
for numero_ligne, ligne in enumerate(log, start=1):
6655
try:
67-
log_analyse.ajoute_entree(self.parse_entree(ligne))
68-
numero_ligne += 1
56+
entree = self.parse_entree(ligne)
57+
log_analyse.ajoute_entree(entree)
6958
except FormatLogApacheInvalideException as ex:
7059
raise FormatLogApacheInvalideException(
71-
f"Le format de l'entrée à la ligne {numero_ligne}"
72-
f"('{ligne}') est invalide."
60+
f"Le format de l'entrée à la ligne {numero_ligne} "
61+
f"('{ligne.strip()}') est invalide."
7362
) from ex
63+
7464
return log_analyse
7565

7666
def parse_entree(self, entree):
@@ -89,40 +79,61 @@ def parse_entree(self, entree):
8979
analyse = match(self.PATTERN_ENTREE_LOG_APACHE, entree)
9080
if not analyse:
9181
raise FormatLogApacheInvalideException()
82+
83+
# Extraction des résultats d'analyse
9284
resultat_analyse = analyse.groupdict()
85+
9386
# Récupération des informations liées au client
9487
adresse_ip = self.get_information_entree(resultat_analyse, "ip")
88+
if adresse_ip is None:
89+
raise FormatLogApacheInvalideException("L'adresse IP est obligatoire.")
9590
identifiant_rfc = self.get_information_entree(resultat_analyse, "rfc")
9691
utilisateur = self.get_information_entree(resultat_analyse, "utilisateur")
9792
agent_utilisateur = self.get_information_entree(resultat_analyse, "agent_utilisateur")
93+
9894
informations_client = ClientInformations(
9995
adresse_ip, identifiant_rfc, utilisateur, agent_utilisateur
10096
)
97+
10198
# Récupération des informations liées à la requête
10299
horodatage = self.get_information_entree(resultat_analyse, "horodatage")
103100
if horodatage:
104101
horodatage = datetime.strptime(horodatage, "%d/%b/%Y:%H:%M:%S %z")
102+
103+
if horodatage is None:
104+
raise FormatLogApacheInvalideException("L'horodatage est obligatoire.")
105+
105106
methode_http = self.get_information_entree(resultat_analyse, "methode")
106107
url = self.get_information_entree(resultat_analyse, "url")
107108
protocole_http = self.get_information_entree(resultat_analyse, "protocole")
108109
ancienne_url = self.get_information_entree(resultat_analyse, "ancienne_url")
110+
109111
informations_requete = RequeteInformations(
110112
horodatage, methode_http, url, protocole_http, ancienne_url
111113
)
114+
112115
# Récupération des informations liées à la réponse
113116
code_statut = self.get_information_entree(resultat_analyse, "code_status")
114117
if code_statut:
115118
code_statut = int(code_statut)
119+
120+
if code_statut is None:
121+
raise FormatLogApacheInvalideException("Le code de statut HTTP est obligatoire.")
122+
116123
taille_octets = self.get_information_entree(resultat_analyse, "taille_octets")
117124
if taille_octets:
118125
taille_octets = int(taille_octets)
126+
119127
informations_reponse = ReponseInformations(
120128
code_statut, taille_octets
121129
)
130+
131+
# Retour des informations regroupées dans l'objet EntreeLogApache
122132
return EntreeLogApache(
123133
informations_client, informations_requete, informations_reponse
124134
)
125135

136+
126137
def get_information_entree(self, analyse_regex, nom_information):
127138
"""
128139
Retourne la valeur de l'information dans l'analyse si elle possède une valeur
@@ -134,11 +145,8 @@ def get_information_entree(self, analyse_regex, nom_information):
134145
Union[str, None]: La valeur sous forme de chaîne de caractère ou None si
135146
aucune valeur n'a été trouvée.
136147
"""
137-
if nom_information in analyse_regex:
138-
valeur = analyse_regex[nom_information]
139-
if valeur != "-" and valeur != "":
140-
return valeur
141-
return None
148+
valeur = analyse_regex.get(nom_information)
149+
return valeur if valeur != "" and valeur != "-" else None
142150

143151
class FormatLogApacheInvalideException(Exception):
144152

tests/test_parseur_log_apache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,5 @@ def test_parsage_entree_valide(parseur_log_apache):
171171
assert entree.requete.url == "/index.html"
172172
assert entree.requete.protocole_http == "HTTP/1.1"
173173
assert entree.requete.ancienne_url == "/home"
174-
assert entree.reponse.code_status_http == 200
174+
assert entree.reponse.code_statut_http == 200
175175
assert entree.reponse.taille_octets == 532

0 commit comments

Comments
 (0)