refactor: standardize string quotes and improve code formatting

Updated both main.py and generate_math_exercises.py to use consistent double quotes for strings and improved code formatting with better line breaks for readability. Also added JSON import to main.py for bulk operations.
This commit is contained in:
2025-09-04 00:08:13 +02:00
parent 1d47b5490e
commit cdaf3d9500
2 changed files with 237 additions and 165 deletions
+98 -59
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import json
import random import random
import os import os
import io import io
@@ -7,8 +8,8 @@ import zipfile
import tempfile import tempfile
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from typing import List from typing import List
from fastapi import FastAPI, Request, Response, Form from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from pydantic import BaseModel from pydantic import BaseModel
from fpdf import FPDF from fpdf import FPDF
@@ -16,29 +17,30 @@ from fpdf import FPDF
app = FastAPI() app = FastAPI()
# S3 Configuration # S3 Configuration
S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME', 'math-exercises') S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", "math-exercises")
templates = Jinja2Templates(directory="app/templates") templates = Jinja2Templates(directory="app/templates")
def get_s3_client(): def get_s3_client():
"""Create and return an S3 client using environment variables""" """Create and return an S3 client using environment variables"""
s3_access_key = os.environ.get('S3_ACCESS_KEY') s3_access_key = os.environ.get("S3_ACCESS_KEY")
s3_secret_key = os.environ.get('S3_SECRET_KEY') s3_secret_key = os.environ.get("S3_SECRET_KEY")
s3_host_base = os.environ.get('S3_HOST_BASE') s3_host_base = os.environ.get("S3_HOST_BASE")
s3_host_bucket = os.environ.get('S3_HOST_BUCKET')
if not all([s3_access_key, s3_secret_key, s3_host_base]): if not all([s3_access_key, s3_secret_key, s3_host_base]):
raise ValueError("S3 environment variables not properly set") raise ValueError("S3 environment variables not properly set")
s3 = boto3.client( s3 = boto3.client(
's3', "s3",
aws_access_key_id=s3_access_key, aws_access_key_id=s3_access_key,
aws_secret_access_key=s3_secret_key, aws_secret_access_key=s3_secret_key,
endpoint_url=s3_host_base, endpoint_url=s3_host_base,
region_name='us-east-1' # Required but unused for Infomaniak region_name="us-east-1", # Required but unused for Infomaniak
) )
return s3 return s3
def create_bucket_if_not_exists(bucket_name): def create_bucket_if_not_exists(bucket_name):
"""Create S3 bucket if it doesn't exist""" """Create S3 bucket if it doesn't exist"""
s3_client = get_s3_client() s3_client = get_s3_client()
@@ -46,7 +48,7 @@ def create_bucket_if_not_exists(bucket_name):
try: try:
s3_client.head_bucket(Bucket=bucket_name) s3_client.head_bucket(Bucket=bucket_name)
except ClientError as e: except ClientError as e:
error_code = int(e.response['Error']['Code']) error_code = int(e.response["Error"]["Code"])
if error_code == 404: if error_code == 404:
# Bucket doesn't exist, create it # Bucket doesn't exist, create it
try: try:
@@ -60,13 +62,15 @@ def create_bucket_if_not_exists(bucket_name):
print(f"Error checking bucket: {e}") print(f"Error checking bucket: {e}")
raise raise
# Create bucket on startup # Create bucket on startup
try: try:
create_bucket_if_not_exists(S3_BUCKET_NAME) create_bucket_if_not_exists(S3_BUCKET_NAME)
except Exception as e: except Exception as e:
print(f"Warning: Could not create/check S3 bucket: {e}") print(f"Warning: Could not create/check S3 bucket: {e}")
def upload_to_s3(file_data, bucket_name, object_name, content_type='application/pdf'):
def upload_to_s3(file_data, bucket_name, object_name, content_type="application/pdf"):
"""Upload file data to S3 bucket""" """Upload file data to S3 bucket"""
s3_client = get_s3_client() s3_client = get_s3_client()
@@ -75,34 +79,38 @@ def upload_to_s3(file_data, bucket_name, object_name, content_type='application/
Bucket=bucket_name, Bucket=bucket_name,
Key=object_name, Key=object_name,
Body=file_data, Body=file_data,
ContentType=content_type ContentType=content_type,
) )
return True return True
except ClientError as e: except ClientError as e:
print(f"Error uploading to S3: {e}") print(f"Error uploading to S3: {e}")
return False return False
def download_from_s3(bucket_name, object_name): def download_from_s3(bucket_name, object_name):
"""Download file data from S3 bucket""" """Download file data from S3 bucket"""
s3_client = get_s3_client() s3_client = get_s3_client()
try: try:
response = s3_client.get_object(Bucket=bucket_name, Key=object_name) response = s3_client.get_object(Bucket=bucket_name, Key=object_name)
return response['Body'].read() return response["Body"].read()
except ClientError as e: except ClientError as e:
print(f"Error downloading from S3: {e}") print(f"Error downloading from S3: {e}")
return None return None
def list_objects_in_s3(bucket_name): def list_objects_in_s3(bucket_name):
"""List all objects in S3 bucket (sorted from newest to oldest)""" """List all objects in S3 bucket (sorted from newest to oldest)"""
s3_client = get_s3_client() s3_client = get_s3_client()
try: try:
response = s3_client.list_objects_v2(Bucket=bucket_name) response = s3_client.list_objects_v2(Bucket=bucket_name)
if 'Contents' in response: if "Contents" in response:
# Filter for PDF files only and sort by last modified (newest first) # Filter for PDF files only and sort by last modified (newest first)
pdf_files = [obj for obj in response['Contents'] if obj['Key'].endswith('.pdf')] pdf_files = [
pdf_files.sort(key=lambda x: x['LastModified'], reverse=True) obj for obj in response["Contents"] if obj["Key"].endswith(".pdf")
]
pdf_files.sort(key=lambda x: x["LastModified"], reverse=True)
return pdf_files return pdf_files
else: else:
return [] return []
@@ -110,6 +118,7 @@ def list_objects_in_s3(bucket_name):
print(f"Error listing objects in S3: {e}") print(f"Error listing objects in S3: {e}")
return [] return []
def delete_from_s3(bucket_name, object_name): def delete_from_s3(bucket_name, object_name):
"""Delete file from S3 bucket""" """Delete file from S3 bucket"""
s3_client = get_s3_client() s3_client = get_s3_client()
@@ -121,26 +130,42 @@ def delete_from_s3(bucket_name, object_name):
print(f"Error deleting from S3: {e}") print(f"Error deleting from S3: {e}")
return False return False
class MathExercisesPDF(FPDF): class MathExercisesPDF(FPDF):
def header(self): def header(self):
self.set_font('Helvetica', 'B', 16) self.set_font("Helvetica", "B", 16)
self.cell(0, 10, 'Exercices de Multiplication et Division', 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") self.cell(
0,
10,
"Exercices de Multiplication et Division",
0,
1,
"C",
new_x="LMARGIN",
new_y="NEXT",
)
self.ln(10) self.ln(10)
def footer(self): def footer(self):
self.set_y(-15) self.set_y(-15)
self.set_font('Helvetica', 'I', 8) self.set_font("Helvetica", "I", 8)
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP") self.cell(
0, 10, f"Page {self.page_no()}", 0, 0, "C", new_x="RIGHT", new_y="TOP"
)
class ExerciseRequest(BaseModel): class ExerciseRequest(BaseModel):
min_table: int min_table: int
max_table: int max_table: int
num_exercises: int = 15 num_exercises: int = 15
def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15) -> List[str]:
def generate_exercises(
min_table: int, max_table: int, num_exercises: int = 15
) -> List[str]:
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons""" """Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
exercises = [] exercises: List[str] = []
used_operations = set() # Pour éviter les doublons used_operations: set = set() # Pour éviter les doublons
# Générer le nombre exact d'exercices demandé # Générer le nombre exact d'exercices demandé
attempts = 0 attempts = 0
@@ -155,9 +180,9 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
result = a * b result = a * b
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division) # Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
exercise_type = random.choice(['multiplication', 'division']) exercise_type = random.choice(["multiplication", "division"])
if exercise_type == 'multiplication': if exercise_type == "multiplication":
# Exercice de multiplication # Exercice de multiplication
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
if operation_key not in used_operations: if operation_key not in used_operations:
@@ -184,9 +209,9 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
result = a * b result = a * b
# Choisir aléatoirement le type d'exercice # Choisir aléatoirement le type d'exercice
exercise_type = random.choice(['multiplication', 'division']) exercise_type = random.choice(["multiplication", "division"])
if exercise_type == 'multiplication': if exercise_type == "multiplication":
exercise = f"{a} · {b} = ____" exercise = f"{a} · {b} = ____"
else: # division else: # division
divisor = random.choice([a, b]) divisor = random.choice([a, b])
@@ -196,7 +221,10 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
return exercises return exercises
def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int = 15) -> str:
def create_math_exercises_pdf(
min_table: int, max_table: int, num_exercises: int = 15
) -> str:
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes et l'upload sur S3""" """Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes et l'upload sur S3"""
import datetime import datetime
@@ -205,17 +233,19 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
# Ajouter des informations sur la plage de tables # Ajouter des informations sur la plage de tables
if min_table == max_table: if min_table == max_table:
table_info = f'Tables de multiplication et division pour {min_table}' table_info = f"Tables de multiplication et division pour {min_table}"
pdf_filename = f'exercices_mathematiques_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf' pdf_filename = f"exercices_mathematiques_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf"
else: else:
table_info = f'Tables de multiplication et division de {min_table} à {max_table}' table_info = (
pdf_filename = f'exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf' f"Tables de multiplication et division de {min_table} à {max_table}"
)
pdf_filename = f"exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf"
pdf = MathExercisesPDF() pdf = MathExercisesPDF()
pdf.add_page() pdf.add_page()
pdf.set_font('Helvetica', '', 12) pdf.set_font("Helvetica", "", 12)
pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") pdf.cell(0, 10, table_info, 0, 1, "C", new_x="LMARGIN", new_y="NEXT")
pdf.ln(5) pdf.ln(5)
# Générer les exercices # Générer les exercices
@@ -255,9 +285,9 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
col3_text = "" col3_text = ""
# Ajouter la ligne # Ajouter la ligne
pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col1_text, 0, 0, "L", new_x="RIGHT", new_y="TOP")
pdf.cell(60, 10, col2_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col2_text, 0, 0, "L", new_x="RIGHT", new_y="TOP")
pdf.cell(60, 10, col3_text, 0, 1, 'L', new_x="LMARGIN", new_y="NEXT") pdf.cell(60, 10, col3_text, 0, 1, "L", new_x="LMARGIN", new_y="NEXT")
# Sauvegarder le PDF en mémoire # Sauvegarder le PDF en mémoire
pdf_data = pdf.output() pdf_data = pdf.output()
@@ -270,11 +300,11 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
return pdf_filename return pdf_filename
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def read_root(request: Request): async def read_root(request: Request):
return templates.TemplateResponse("index.html", {"request": request}) return templates.TemplateResponse("index.html", {"request": request})
from fastapi.responses import RedirectResponse
@app.post("/generate") @app.post("/generate")
async def generate_exercises_endpoint(request: ExerciseRequest): async def generate_exercises_endpoint(request: ExerciseRequest):
@@ -282,14 +312,14 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
if request.min_table < 1 or request.max_table < 1: if request.min_table < 1 or request.max_table < 1:
return {"error": "Les tables doivent être supérieures à 0"} return {"error": "Les tables doivent être supérieures à 0"}
if request.min_table > request.max_table: if request.min_table > request.max_table:
return {"error": "La table minimale doit être inférieure ou égale à la table maximale"} return {
"error": "La table minimale doit être inférieure ou égale à la table maximale"
}
if request.num_exercises < 1: if request.num_exercises < 1:
return {"error": "Le nombre d'exercices doit être supérieur à 0"} return {"error": "Le nombre d'exercices doit être supérieur à 0"}
pdf_filename = create_math_exercises_pdf( pdf_filename = create_math_exercises_pdf(
request.min_table, request.min_table, request.max_table, request.num_exercises
request.max_table,
request.num_exercises
) )
# Return redirect to automatically download the file # Return redirect to automatically download the file
@@ -298,6 +328,7 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
except Exception as e: except Exception as e:
return {"error": f"Erreur lors de la création du PDF: {str(e)}"} return {"error": f"Erreur lors de la création du PDF: {str(e)}"}
@app.get("/download/{filename}") @app.get("/download/{filename}")
async def download_pdf(filename: str): async def download_pdf(filename: str):
# Download file from S3 # Download file from S3
@@ -309,10 +340,11 @@ async def download_pdf(filename: str):
# Return streaming response with PDF data # Return streaming response with PDF data
return StreamingResponse( return StreamingResponse(
io.BytesIO(file_data), io.BytesIO(file_data),
media_type='application/pdf', media_type="application/pdf",
headers={'Content-Disposition': f'attachment; filename="{filename}"'} headers={"Content-Disposition": f'attachment; filename="{filename}"'},
) )
@app.get("/list") @app.get("/list")
async def list_pdfs(page: int = 1, page_size: int = 10): async def list_pdfs(page: int = 1, page_size: int = 10):
"""List PDF files in the S3 bucket with pagination (sorted from newest to oldest)""" """List PDF files in the S3 bucket with pagination (sorted from newest to oldest)"""
@@ -342,33 +374,37 @@ async def list_pdfs(page: int = 1, page_size: int = 10):
"total_files": total_files, "total_files": total_files,
"total_pages": total_pages, "total_pages": total_pages,
"has_next": page < total_pages, "has_next": page < total_pages,
"has_prev": page > 1 "has_prev": page > 1,
} },
} }
except Exception as e: except Exception as e:
return {"error": f"Error listing files: {str(e)}"} return {"error": f"Error listing files: {str(e)}"}
@app.delete("/delete/{filename}") @app.delete("/delete/{filename}")
async def delete_pdf(filename: str): async def delete_pdf(filename: str):
"""Delete a PDF file from S3 bucket""" """Delete a PDF file from S3 bucket"""
try: try:
# Decode URL encoded filename # Decode URL encoded filename
import urllib.parse import urllib.parse
decoded_filename = urllib.parse.unquote(filename) decoded_filename = urllib.parse.unquote(filename)
success = delete_from_s3(S3_BUCKET_NAME, decoded_filename) success = delete_from_s3(S3_BUCKET_NAME, decoded_filename)
if success: if success:
return {"message": f"Fichier {decoded_filename} supprimé avec succès"} return {"message": f"Fichier {decoded_filename} supprimé avec succès"}
else: else:
return {"error": f"Erreur lors de la suppression du fichier {decoded_filename}"} return {
"error": f"Erreur lors de la suppression du fichier {decoded_filename}"
}
except Exception as e: except Exception as e:
return {"error": f"Error deleting file: {str(e)}"} return {"error": f"Error deleting file: {str(e)}"}
@app.post("/bulk-delete") @app.post("/bulk-delete")
async def bulk_delete(filenames: str = Form(...)): async def bulk_delete(filenames: str = Form(...)):
"""Delete multiple PDF files from S3 bucket""" """Delete multiple PDF files from S3 bucket"""
try: try:
import json
filename_list = json.loads(filenames) filename_list = json.loads(filenames)
deleted_files = [] deleted_files = []
errors = [] errors = []
@@ -381,51 +417,54 @@ async def bulk_delete(filenames: str = Form(...)):
errors.append(filename) errors.append(filename)
if errors: if errors:
return {"message": f"Fichiers supprimés: {len(deleted_files)}", "errors": errors} return {
"message": f"Fichiers supprimés: {len(deleted_files)}",
"errors": errors,
}
else: else:
return {"message": f"{len(deleted_files)} fichiers supprimés avec succès"} return {"message": f"{len(deleted_files)} fichiers supprimés avec succès"}
except Exception as e: except Exception as e:
return {"error": f"Error deleting files: {str(e)}"} return {"error": f"Error deleting files: {str(e)}"}
from fastapi import Form
@app.post("/bulk-download") @app.post("/bulk-download")
async def bulk_download(filenames: str = Form(...)): async def bulk_download(filenames: str = Form(...)):
"""Download multiple PDF files as a zip archive""" """Download multiple PDF files as a zip archive"""
try: try:
import zipfile
import tempfile
import json
# Parse the JSON string to get the list of filenames # Parse the JSON string to get the list of filenames
filename_list = json.loads(filenames) filename_list = json.loads(filenames)
# Create a temporary zip file # Create a temporary zip file
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_zip: with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip:
with zipfile.ZipFile(tmp_zip.name, 'w') as zipf: with zipfile.ZipFile(tmp_zip.name, "w") as zipf:
for filename in filename_list: for filename in filename_list:
file_data = download_from_s3(S3_BUCKET_NAME, filename) file_data = download_from_s3(S3_BUCKET_NAME, filename)
if file_data: if file_data:
zipf.writestr(filename, file_data) zipf.writestr(filename, file_data)
# Read the zip file # Read the zip file
with open(tmp_zip.name, 'rb') as f: with open(tmp_zip.name, "rb") as f:
zip_data = f.read() zip_data = f.read()
# Clean up temporary file # Clean up temporary file
import os import os
os.unlink(tmp_zip.name) os.unlink(tmp_zip.name)
# Return streaming response with zip data # Return streaming response with zip data
zip_filename = f"math_exercises_{len(filename_list)}_files.zip" zip_filename = f"math_exercises_{len(filename_list)}_files.zip"
return StreamingResponse( return StreamingResponse(
io.BytesIO(zip_data), io.BytesIO(zip_data),
media_type='application/zip', media_type="application/zip",
headers={'Content-Disposition': f'attachment; filename="{zip_filename}"'} headers={
"Content-Disposition": f'attachment; filename="{zip_filename}"'
},
) )
except Exception as e: except Exception as e:
return {"error": f"Error downloading files: {str(e)}"} return {"error": f"Error downloading files: {str(e)}"}
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000) uvicorn.run(app, host="0.0.0.0", port=8000)
+57 -24
View File
@@ -5,16 +5,29 @@ import subprocess
import os import os
from fpdf import FPDF from fpdf import FPDF
class MathExercisesPDF(FPDF): class MathExercisesPDF(FPDF):
def header(self): def header(self):
self.set_font('Helvetica', 'B', 16) self.set_font("Helvetica", "B", 16)
self.cell(0, 10, 'Exercices de Multiplication et Division', 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") self.cell(
0,
10,
"Exercices de Multiplication et Division",
0,
1,
"C",
new_x="LMARGIN",
new_y="NEXT",
)
self.ln(10) self.ln(10)
def footer(self): def footer(self):
self.set_y(-15) self.set_y(-15)
self.set_font('Helvetica', 'I', 8) self.set_font("Helvetica", "I", 8)
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP") self.cell(
0, 10, f"Page {self.page_no()}", 0, 0, "C", new_x="RIGHT", new_y="TOP"
)
def generate_exercises(min_table, max_table, num_exercises=15): def generate_exercises(min_table, max_table, num_exercises=15):
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons""" """Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
@@ -34,9 +47,9 @@ def generate_exercises(min_table, max_table, num_exercises=15):
result = a * b result = a * b
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division) # Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
exercise_type = random.choice(['multiplication', 'division']) exercise_type = random.choice(["multiplication", "division"])
if exercise_type == 'multiplication': if exercise_type == "multiplication":
# Exercice de multiplication # Exercice de multiplication
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
if operation_key not in used_operations: if operation_key not in used_operations:
@@ -63,9 +76,9 @@ def generate_exercises(min_table, max_table, num_exercises=15):
result = a * b result = a * b
# Choisir aléatoirement le type d'exercice # Choisir aléatoirement le type d'exercice
exercise_type = random.choice(['multiplication', 'division']) exercise_type = random.choice(["multiplication", "division"])
if exercise_type == 'multiplication': if exercise_type == "multiplication":
exercise = f"{a} · {b} = ____" exercise = f"{a} · {b} = ____"
else: # division else: # division
divisor = random.choice([a, b]) divisor = random.choice([a, b])
@@ -75,19 +88,22 @@ def generate_exercises(min_table, max_table, num_exercises=15):
return exercises return exercises
def create_math_exercises_pdf(min_table, max_table, num_exercises=15): def create_math_exercises_pdf(min_table, max_table, num_exercises=15):
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes""" """Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes"""
pdf = MathExercisesPDF() pdf = MathExercisesPDF()
pdf.add_page() pdf.add_page()
pdf.set_font('Helvetica', '', 12) pdf.set_font("Helvetica", "", 12)
# Ajouter des informations sur la plage de tables # Ajouter des informations sur la plage de tables
if min_table == max_table: if min_table == max_table:
table_info = f'Tables de multiplication et division pour {min_table}' table_info = f"Tables de multiplication et division pour {min_table}"
else: else:
table_info = f'Tables de multiplication et division de {min_table} à {max_table}' table_info = (
f"Tables de multiplication et division de {min_table} à {max_table}"
)
pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") pdf.cell(0, 10, table_info, 0, 1, "C", new_x="LMARGIN", new_y="NEXT")
pdf.ln(5) pdf.ln(5)
# Générer les exercices # Générer les exercices
@@ -127,18 +143,21 @@ def create_math_exercises_pdf(min_table, max_table, num_exercises=15):
col3_text = "" col3_text = ""
# Ajouter la ligne # Ajouter la ligne
pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col1_text, 0, 0, "L", new_x="RIGHT", new_y="TOP")
pdf.cell(60, 10, col2_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col2_text, 0, 0, "L", new_x="RIGHT", new_y="TOP")
pdf.cell(60, 10, col3_text, 0, 1, 'L', new_x="LMARGIN", new_y="NEXT") pdf.cell(60, 10, col3_text, 0, 1, "L", new_x="LMARGIN", new_y="NEXT")
# Sauvegarder le PDF # Sauvegarder le PDF
if min_table == max_table: if min_table == max_table:
filename = f'exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf' filename = (
f"exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf"
)
else: else:
filename = f'exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf' filename = f"exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf"
pdf.output(filename) pdf.output(filename)
return filename return filename
def open_pdf(filename): def open_pdf(filename):
"""Ouvre le fichier PDF avec la commande système appropriée""" """Ouvre le fichier PDF avec la commande système appropriée"""
try: try:
@@ -152,14 +171,21 @@ def open_pdf(filename):
except (subprocess.CalledProcessError, FileNotFoundError, OSError): except (subprocess.CalledProcessError, FileNotFoundError, OSError):
return False return False
def main(): def main():
"""Fonction principale""" """Fonction principale"""
# Vérifier les arguments de ligne de commande # Vérifier les arguments de ligne de commande
if len(sys.argv) < 3: if len(sys.argv) < 3:
print("Usage: python generate_math_exercises.py <min_table> <max_table> [num_exercises] [--open]") print(
"Usage: python generate_math_exercises.py <min_table> <max_table> [num_exercises] [--open]"
)
print("Exemple: python generate_math_exercises.py 4 7 15") print("Exemple: python generate_math_exercises.py 4 7 15")
print(" python generate_math_exercises.py 4 7 15 --open (pour ouvrir automatiquement le PDF)") print(
print(" python generate_math_exercises.py 5 5 10 (pour seulement la table de 5)") " python generate_math_exercises.py 4 7 15 --open (pour ouvrir automatiquement le PDF)"
)
print(
" python generate_math_exercises.py 5 5 10 (pour seulement la table de 5)"
)
sys.exit(1) sys.exit(1)
# Vérifier si l'option --open est présente # Vérifier si l'option --open est présente
@@ -173,7 +199,9 @@ def main():
if min_table < 1 or max_table < 1: if min_table < 1 or max_table < 1:
raise ValueError("Les tables doivent être supérieures à 0") raise ValueError("Les tables doivent être supérieures à 0")
if min_table > max_table: if min_table > max_table:
raise ValueError("La table minimale doit être inférieure ou égale à la table maximale") raise ValueError(
"La table minimale doit être inférieure ou égale à la table maximale"
)
except ValueError as e: except ValueError as e:
print(f"Erreur: {e}") print(f"Erreur: {e}")
sys.exit(1) sys.exit(1)
@@ -197,7 +225,9 @@ def main():
print(f"- Exercices pour la table de {min_table}") print(f"- Exercices pour la table de {min_table}")
else: else:
print(f"- Exercices pour les tables de {min_table} à {max_table}") print(f"- Exercices pour les tables de {min_table} à {max_table}")
print(f"- {num_exercises} exercices MÉLANGÉS générés aléatoirement et numérotés de 1 à {num_exercises}") print(
f"- {num_exercises} exercices MÉLANGÉS générés aléatoirement et numérotés de 1 à {num_exercises}"
)
print("- Types d'exercices : multiplications (·) et divisions (:) uniquement") print("- Types d'exercices : multiplications (·) et divisions (:) uniquement")
print("- Présentés en 3 colonnes pour économiser l'espace") print("- Présentés en 3 colonnes pour économiser l'espace")
print("- Aucune opération en double (quand c'est possible)") print("- Aucune opération en double (quand c'est possible)")
@@ -205,13 +235,16 @@ def main():
# Ouvrir le PDF si demandé # Ouvrir le PDF si demandé
if open_after_creation: if open_after_creation:
if open_pdf(filename): if open_pdf(filename):
print(f"\nPDF ouvert automatiquement avec succès !") print("\nPDF ouvert automatiquement avec succès !")
else: else:
print(f"\nImpossible d'ouvrir automatiquement le PDF. Vous pouvez l'ouvrir manuellement.") print(
"\nImpossible d'ouvrir automatiquement le PDF. Vous pouvez l'ouvrir manuellement."
)
except Exception as e: except Exception as e:
print(f"Erreur lors de la création du PDF: {e}") print(f"Erreur lors de la création du PDF: {e}")
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
main() main()