Eric2mangel commited on
Commit
d42bc16
·
verified ·
1 Parent(s): a30fe66

Upload 4 files

Browse files

IMport des fichiers

Files changed (4) hide show
  1. Dockerfile +34 -20
  2. README.md +57 -19
  3. app.py +220 -0
  4. requirements.txt +4 -3
Dockerfile CHANGED
@@ -1,20 +1,34 @@
1
- FROM python:3.13.5-slim
2
-
3
- WORKDIR /app
4
-
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
- && rm -rf /var/lib/apt/lists/*
10
-
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
13
-
14
- RUN pip3 install -r requirements.txt
15
-
16
- EXPOSE 8501
17
-
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
-
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax = docker/dockerfile:1.4
2
+ FROM python:3.12-slim
3
+
4
+ # Installe les dépendances système nécessaires
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ gcc \
7
+ g++ \
8
+ libjpeg62-turbo-dev \
9
+ zlib1g-dev \
10
+ libpng-dev \
11
+ libfreetype6-dev \
12
+ libopenjp2-7-dev \
13
+ libtiff5-dev \
14
+ curl \
15
+ git \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ WORKDIR /app
19
+
20
+ # Copie requirements en premier (meilleur cache)
21
+ COPY requirements.txt .
22
+
23
+ # Installe tout (sans cache pour réduire la taille finale)
24
+ RUN pip install --no-cache-dir -r requirements.txt
25
+
26
+ # Copie le code
27
+ COPY app.py .
28
+
29
+ # Port + commande obligatoire pour HF Spaces
30
+ EXPOSE 8501
31
+
32
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
33
+
34
+ CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.enableCORS=false", "--server.enableXsrfProtection=false"]
README.md CHANGED
@@ -1,19 +1,57 @@
1
- ---
2
- title: Coach Code Python
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Assess your Python programming skills
12
- ---
13
-
14
- # Welcome to Streamlit!
15
-
16
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
17
-
18
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
19
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Coach Code Python
3
+ emoji: 🐍
4
+ colorFrom: green
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 8501
8
+ pinned: false
9
+ ---
10
+
11
+ # 🐍 Coach Code Python
12
+
13
+ Application Streamlit pour analyser et corriger automatiquement votre code Python avec Ruff.
14
+
15
+ ## Fonctionnalités
16
+
17
+ - **Analyse de code** : Détection automatique de tous les problèmes de qualité
18
+ - **Correction automatique** : Applique les corrections recommandées par Ruff
19
+ - **Support multi-fichiers** : Analysez un fichier unique ou plusieurs fichiers simultanément
20
+ - **Visualisations interactives** : Graphiques des erreurs par catégorie
21
+ - **Comparaison avant/après** : Visualisez les modifications apportées au code
22
+ - **Options configurables** :
23
+ - Désactiver l'obligation des commentaires (docstrings)
24
+ - Garder le code compact (sans espaces excessifs)
25
+ - Activer les corrections forcées (modernisation du code)
26
+
27
+ ## Catégories d'analyse
28
+
29
+ - **F** : Erreurs Logiques
30
+ - **E** : Style PEP8
31
+ - **W** : Avertissements
32
+ - **I** : Tri des Imports
33
+ - **B** : Bugs Potentiels
34
+ - **UP** : Modernisation
35
+ - **N** : Nommage
36
+ - **D** : Documentation
37
+ - **ANN** : Annotations de type
38
+ - **T** : Tests & Debug
39
+ - **A** : Built-ins
40
+
41
+ ## Utilisation
42
+
43
+ 1. Sélectionnez votre mode (fichier unique ou plusieurs fichiers)
44
+ 2. Uploadez votre/vos fichier(s) Python (.py)
45
+ 3. Configurez les options de correction selon vos besoins
46
+ 4. Cliquez sur "🚀 Analyser & Corriger"
47
+ 5. Consultez les résultats dans les différents onglets :
48
+ - **📊 Statistiques** : Graphiques des erreurs détectées
49
+ - **📜 Rapport** : Liste détaillée de tous les problèmes
50
+ - **🔍 Comparatif** : Code avant/après (mode fichier unique)
51
+
52
+ ## Métriques affichées
53
+
54
+ - **Qualité du Code** : Score sur 100 basé sur le nombre d'erreurs
55
+ - **Points corrigés** : Nombre total de problèmes détectés
56
+ - **Lignes modifiées** : Différence de lignes de code
57
+ - **Gain de poids** : Réduction de la taille du fichier en octets
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import sys
4
+ import subprocess
5
+ import json
6
+ import pandas as pd
7
+ import plotly.express as px
8
+ import shutil
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ # --- CONFIGURATION ---
13
+ st.set_page_config(page_title="Coach Code Python", layout="wide")
14
+
15
+ # Dictionnaire pédagogique mis à jour avec les lettres
16
+ RUFF_CAT_MAP = {
17
+ "F": "Erreurs Logiques (F)",
18
+ "E": "Style PEP8 (E)",
19
+ "W": "Avertissements (W)",
20
+ "I": "Tri des Imports (I)",
21
+ "B": "Bugs Potentiels (B)",
22
+ "UP": "Modernisation (UP)",
23
+ "N": "Nommage (N)",
24
+ "D": "Documentation (D)",
25
+ "ANN": "Annotations de type (ANN)",
26
+ "T": "Tests & Debug (T)",
27
+ "A": "Built-ins (A)"
28
+ }
29
+
30
+ def remove_excessive_blank_lines(code):
31
+ """Supprime les lignes vides excessives (plus de 1 ligne vide consécutive)"""
32
+ lines = code.split('\n')
33
+ result = []
34
+ blank_count = 0
35
+
36
+ for line in lines:
37
+ if line.strip() == '':
38
+ blank_count += 1
39
+ if blank_count <= 1: # Garde max 1 ligne vide
40
+ result.append(line)
41
+ else:
42
+ blank_count = 0
43
+ result.append(line)
44
+
45
+ return '\n'.join(result)
46
+
47
+
48
+ def run_ruff(paths, fix=False, disable_docs=False, compact=False, unsafe=False):
49
+ """Exécute l'analyse et le formatage Ruff"""
50
+ ignore_list = []
51
+
52
+ if compact:
53
+ ignore_list.extend(["E302", "E303", "E305", "E301", "E401", "W391"])
54
+
55
+ if disable_docs:
56
+ ignore_list.extend(["D100", "D101", "D102", "D103", "D104", "D107"])
57
+
58
+ cmd_check = [
59
+ sys.executable, "-m", "ruff", "check", *paths,
60
+ "--output-format", "json",
61
+ "--select", "ALL",
62
+ "--isolated", "--no-cache",
63
+ ]
64
+
65
+ if ignore_list:
66
+ cmd_check.extend(["--ignore", ",".join(ignore_list)])
67
+
68
+ if fix:
69
+ cmd_check.append("--fix")
70
+ if unsafe:
71
+ cmd_check.append("--unsafe-fixes")
72
+
73
+ use_shell = os.name == 'nt'
74
+ result = subprocess.run(cmd_check, capture_output=True, text=True, encoding="utf-8", shell=use_shell)
75
+
76
+ if fix:
77
+ cmd_format = [sys.executable, "-m", "ruff", "format", *paths, "--isolated"]
78
+ subprocess.run(cmd_format, capture_output=True, shell=use_shell)
79
+
80
+ try:
81
+ return json.loads(result.stdout) if result.stdout.strip() else []
82
+ except:
83
+ return []
84
+
85
+ def get_stats(paths):
86
+ total_size, total_lines = 0, 0
87
+ for p in paths:
88
+ path_obj = Path(p)
89
+ files = [path_obj] if path_obj.is_file() else path_obj.rglob("*.py")
90
+ for f in files:
91
+ try:
92
+ content = f.read_text(errors='ignore')
93
+ total_size += f.stat().st_size
94
+ total_lines += len(content.splitlines())
95
+ except: continue
96
+ return total_size, total_lines
97
+
98
+ # --- SIDEBAR ---
99
+ with st.sidebar:
100
+ st.title("🛡️ Configuration")
101
+ mode = st.radio("Source :", ["Fichier unique", "Plusieurs fichiers"], index=0)
102
+
103
+ uploaded_files = []
104
+ if mode == "Fichier unique":
105
+ f = st.file_uploader("Fichier .py", type="py")
106
+ if f: uploaded_files = [f]
107
+ else:
108
+ f_list = st.file_uploader("Sélectionner fichiers", type="py", accept_multiple_files=True)
109
+ if f_list: uploaded_files = f_list
110
+
111
+ st.subheader("🛠️ Options de correction")
112
+ opt_docs = st.checkbox("Désactiver l'obligation des commentaires", value=True)
113
+ opt_compact = st.checkbox("Garder le code compact (sans espaces excessifs)", value=True)
114
+ opt_unsafe = st.checkbox("Activer les corrections forcées (modernisation)", value=True)
115
+
116
+ #st.divider()
117
+ btn_analyze = st.button("🚀 Analyser & Corriger", use_container_width=True)
118
+
119
+ # --- ZONE CENTRALE ---
120
+ st.title("🐍 Coach Code Python")
121
+
122
+ if btn_analyze and uploaded_files:
123
+ with tempfile.TemporaryDirectory() as temp_dir:
124
+ temp_workspace = Path(temp_dir)
125
+ work_paths, code_before, code_after = [], "", ""
126
+
127
+ try:
128
+ for uploaded_file in uploaded_files:
129
+ file_path = temp_workspace / uploaded_file.name
130
+ file_data = uploaded_file.getvalue().decode("utf-8", errors="ignore")
131
+ if mode == "Fichier unique": code_before = file_data
132
+ file_path.write_text(file_data, encoding="utf-8")
133
+ work_paths.append(str(file_path))
134
+
135
+ s_init, l_init = get_stats(work_paths)
136
+
137
+ # Analyse initiale
138
+ errors = run_ruff(work_paths, fix=False, disable_docs=opt_docs, compact=opt_compact, unsafe=opt_unsafe)
139
+
140
+ # Correction
141
+ run_ruff(work_paths, fix=True, disable_docs=opt_docs, compact=opt_compact, unsafe=opt_unsafe)
142
+
143
+ if opt_compact:
144
+ for path in work_paths:
145
+ file_path = Path(path)
146
+ content = file_path.read_text(encoding="utf-8")
147
+ cleaned = remove_excessive_blank_lines(content)
148
+ file_path.write_text(cleaned, encoding="utf-8")
149
+
150
+ s_after, l_after = get_stats(work_paths)
151
+
152
+ if mode == "Fichier unique":
153
+ code_after = Path(work_paths[0]).read_text(encoding="utf-8")
154
+
155
+ nb_err = len(errors)
156
+ score = max(0, min(100, 100 - (nb_err / (l_init if l_init > 0 else 1)) * 100))
157
+
158
+ m1, m2, m3, m4 = st.columns(4)
159
+ m1.metric("Qualité du Code", f"{score:.1f}/100")
160
+ m2.metric("Points corrigés", nb_err)
161
+ m3.metric("Lignes modifiées", l_init - l_after)
162
+ m4.metric("Gain de poids", f"{s_init - s_after} octets")
163
+
164
+ tabs = st.tabs(["📊 Statistiques", "📜 Rapport", "🔍 Comparatif"] if mode == "Fichier unique" else ["📊 Statistiques", "📜 Rapport"])
165
+
166
+ with tabs[0]:
167
+ if nb_err > 0:
168
+ df = pd.DataFrame(errors)
169
+ df['Cat_Code'] = df['code'].str[0]
170
+ df['Catégorie'] = df['Cat_Code'].map(lambda x: RUFF_CAT_MAP.get(x, f"Autre ({x})"))
171
+
172
+ c1, c2 = st.columns(2)
173
+ with c1:
174
+ counts = df['code'].value_counts().reset_index().sort_values('count', ascending=True)
175
+ fig = px.bar(counts, x='count', y='code', orientation='h',
176
+ title="Fréquence par code d'erreur",
177
+ color='count', color_continuous_scale='Blues')
178
+ st.plotly_chart(fig, use_container_width=True)
179
+ with c2:
180
+ cat_counts = df['Catégorie'].value_counts().reset_index().sort_values('count', ascending=False)
181
+ fig2 = px.bar(cat_counts, x='count', y='Catégorie', orientation='h',
182
+ title="Problèmes par famille",
183
+ color='Catégorie', color_discrete_sequence=px.colors.qualitative.G10)
184
+ fig2.update_layout(showlegend=False, yaxis={'categoryorder':'total ascending'})
185
+ st.plotly_chart(fig2, use_container_width=True)
186
+ else:
187
+ st.success("✨ Félicitations ! Ruff n'a trouvé aucune erreur.")
188
+
189
+ with tabs[1]:
190
+ if nb_err > 0:
191
+ # --- TRANSFORMATION DES DONNÉES POUR LE TABLEAU ---
192
+ report_data = []
193
+ for err in errors:
194
+ filename = Path(err['filename']).name
195
+ line = err['location']['row']
196
+ col = err['location']['column']
197
+
198
+ report_data.append({
199
+ "Code": err['code'],
200
+ "Message": err['message'],
201
+ "Localisation": f"{filename} (L:{line}, C:{col})"
202
+ })
203
+
204
+ st.dataframe(pd.DataFrame(report_data), use_container_width=True)
205
+
206
+ if mode == "Fichier unique":
207
+ with tabs[2]:
208
+ col1, col2 = st.columns(2)
209
+ col1.subheader("Version Originale")
210
+ col1.code(code_before, language="python")
211
+ col2.subheader("Version Corrigée")
212
+ col2.code(code_after, language="python")
213
+
214
+ except Exception as e:
215
+ st.error(f"Erreur : {e}")
216
+
217
+ if Path(".ruff_cache").exists():
218
+ shutil.rmtree(".ruff_cache")
219
+ else:
220
+ st.info("👈 Veuillez sélectionner un ou plusieurs fichiers Python dans la barre latérale pour commencer l'analyse.")
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
- altair
2
- pandas
3
- streamlit
 
 
1
+ streamlit==1.40.2
2
+ pandas==2.2.3
3
+ plotly==5.24.1
4
+ ruff==0.8.4