From python-toolkit
Audit et améliore la qualité du code Python qui appelle Microsoft Graph API dans un contexte Azure. Utiliser cette skill quand l'utilisateur soumet du code Python faisant appel à Graph API (Microsoft 365, Azure AD, Teams, SharePoint, OneDrive, Bookings, etc.) et veut l'analyser, le corriger, ou l'améliorer. Déclencher aussi sur : "regarde mon code Graph API", "améliore mon script MSAL", "mon code Graph fait des erreurs 429", "comment paginer correctement avec Graph", "est-ce que mon auth est sécurisée", "mon token expire", "optimise mes appels Graph", "audit de mon code Microsoft Graph", "regarde mon automatisation Azure".
How this skill is triggered — by the user, by Claude, or both
Slash command
/python-toolkit:graph-api-quality-auditorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Skill d'audit et d'amélioration du code Python appelant Microsoft Graph API, couvrant quatre axes :
Skill d'audit et d'amélioration du code Python appelant Microsoft Graph API, couvrant quatre axes : sécurité/auth, robustesse (erreurs & throttling), performance, et qualité du code.
Phase 1 — Audit : produire un rapport structuré AVANT toute correction.
Phase 2 — Amélioration : proposer du code corrigé, section par section.
Ne pas mélanger les deux phases. Ne jamais réécrire en bloc sans rapport préalable.
Analyser le code soumis selon les quatre axes ci-dessous. Pour chaque problème trouvé, assigner :
## Rapport d'audit Graph API
### Résumé
- X problèmes critiques, Y majeurs, Z mineurs, W suggestions
- Contexte détecté : [script ponctuel | backend web | déclenché par Power Automate]
### Axe 1 : Authentification & Sécurité
[liste des problèmes trouvés avec sévérité et ligne si possible]
### Axe 2 : Gestion des erreurs & Throttling
[...]
### Axe 3 : Performance & Utilisation de l'API
[...]
### Axe 4 : Qualité du code
[...]
### Verdict global
[Phrase de synthèse + priorités d'action]
Flux d'authentification
msal (recommandé) ou requests brut avec un token hardcodé ?PublicClientApplication vs ConfidentialClientApplication — bon choix pour le contexte ?acquire_token_for_client (client credentials)Secrets & configuration
.env commité → 🔴 CRITIQUEos.environ ou Azure Key Vault → 🟠 MAJEURtenant_id, client_id hardcodés (pas secrets, mais non configurables) → 🟡 MINEURScopes
Mail.ReadWrite au lieu de Mail.Read) → 🔴 CRITIQUE.default sans commentaire expliquant pourquoi → 🟡 MINEURCache de tokens
SerializableTokenCache non persisté entre les runs (scripts longs) → 🟡 MINEUR# ✅ Auth avec cache — script ponctuel ou Azure Function
import msal, os, json
def get_token(cache_file: str | None = None) -> str:
cache = msal.SerializableTokenCache()
if cache_file and os.path.exists(cache_file):
cache.deserialize(open(cache_file).read())
app = msal.ConfidentialClientApplication(
client_id=os.environ["GRAPH_CLIENT_ID"],
client_credential=os.environ["GRAPH_CLIENT_SECRET"],
authority=f"https://login.microsoftonline.com/{os.environ['GRAPH_TENANT_ID']}",
token_cache=cache,
)
result = app.acquire_token_silent(
scopes=["https://graph.microsoft.com/.default"], account=None
)
if not result:
result = app.acquire_token_for_client(
scopes=["https://graph.microsoft.com/.default"]
)
if "access_token" not in result:
raise RuntimeError(f"Échec auth MSAL : {result.get('error_description')}")
if cache_file and cache.has_state_changed:
open(cache_file, "w").write(cache.serialize())
return result["access_token"]
Throttling (erreurs 429 / 503)
Retry-After → aggrave le throttling → 🔴 CRITIQUEGestion des codes d'erreur Graph
raise_for_status() seul — perd le corps JSON de l'erreur Graph → 🟠 MAJEURLogging
print() au lieu de logging → 🟡 MINEURimport time, logging, requests
from typing import Any
logger = logging.getLogger(__name__)
GRAPH_BASE = "https://graph.microsoft.com/v1.0"
RETRYABLE_STATUS = {429, 500, 502, 503, 504}
def graph_request(
method: str,
endpoint: str,
token: str,
max_retries: int = 5,
**kwargs,
) -> Any:
"""Appel Graph API avec retry automatique sur throttling."""
url = f"{GRAPH_BASE}{endpoint}" if not endpoint.startswith("http") else endpoint
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
**kwargs.pop("headers", {}),
}
for attempt in range(max_retries):
resp = requests.request(method, url, headers=headers, **kwargs)
if resp.status_code in RETRYABLE_STATUS:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
logger.warning(
"Graph API %s %s → %s, retry dans %ss (tentative %s/%s)",
method, endpoint, resp.status_code, wait, attempt + 1, max_retries,
)
time.sleep(wait)
continue
if not resp.ok:
err = resp.json().get("error", {})
logger.error(
"Graph API erreur %s : %s — %s",
resp.status_code, err.get("code"), err.get("message"),
)
resp.raise_for_status()
return resp.json() if resp.content else None
raise RuntimeError(f"Graph API : max retries atteint pour {method} {endpoint}")
Sélection des champs ($select)
$select → récupère 40+ champs inutiles → 🟡 MINEUR à 🟠 MAJEUR (volume)$select manquant sur les listes d'utilisateurs ou messages → 🟠 MAJEURFiltrage côté serveur ($filter)
$filter sur des propriétés non indexées sans ConsistencyLevel: eventual → erreur 400Pagination
@odata.nextLink ignoré → 🔴 CRITIQUE (données manquantes)Batching
/$batch → 🟠 MAJEUR# ✅ Pagination complète
def get_all_pages(token: str, endpoint: str, params: dict | None = None) -> list:
results = []
url = endpoint
while url:
data = graph_request("GET", url, token, params=params)
results.extend(data.get("value", []))
url = data.get("@odata.nextLink") # None stoppe la boucle
params = None # params déjà encodés dans nextLink
return results
# ✅ Exemple d'appel optimisé
users = get_all_pages(
token,
"/users",
params={"$select": "id,displayName,mail,userPrincipalName", "$top": 999},
)
# ✅ Filtre avancé (requiert ConsistencyLevel)
members = graph_request(
"GET",
"/users",
token,
headers={"ConsistencyLevel": "eventual"},
params={
"$filter": "department eq 'Engineering'",
"$select": "id,displayName",
"$count": "true",
},
)
Batching ($batch) — pour 2 à 20 requêtes indépendantes
def graph_batch(token: str, requests_list: list[dict]) -> list:
"""
requests_list : liste de dicts {"id": "1", "method": "GET", "url": "/users/xxx"}
Limite : 20 requêtes par batch.
"""
payload = {"requests": requests_list}
result = graph_request("POST", "/$batch", token, json=payload)
return sorted(result["responses"], key=lambda r: r["id"])
Structure
Type hints & contrats
dict non typé quand la structure est connue (TypedDict ou dataclass) → 🔵 SUGGESTIONConstantes & configuration
"https://graph.microsoft.com/v1.0/...") répétées → 🟡 MINEURTestabilité
graph_client/
├── __init__.py
├── auth.py # get_token(), cache management
├── client.py # graph_request(), graph_batch(), pagination
├── config.py # GraphConfig dataclass, chargement depuis env
└── services/
├── users.py # get_user(), list_users(), etc.
└── calendar.py # list_events(), create_event(), etc.
threading.Lock), session requests partagée, async si FastAPIhttpx.AsyncClient à la place de requestsAprès le rapport, proposer les corrections dans cet ordre de priorité :
Format de chaque correction :
### Correction : [titre]
**Problème** : [description courte]
**Avant** :
```python
# code problématique
Après :
# code corrigé
Pourquoi : [explication concise]
Terminer avec une synthèse : « Ce qui fonctionne bien » + « Prochaine étape recommandée ».
npx claudepluginhub camauger/dev-skills --plugin python-toolkitCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.