[{"data":1,"prerenderedAt":532},["ShallowReactive",2],{"\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fdecorateur-rate-limit":3,"section:\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fdecorateur-rate-limit":531},{"id":4,"title":5,"body":6,"description":519,"extension":520,"meta":521,"navigation":315,"path":527,"seo":528,"stem":529,"__hash__":530},"docs\u002F2.next\u002F3.dodock\u002F10.developpeurs\u002F1.api\u002Fdecorateur-rate-limit.md","Décorateur @rate_limit",{"type":7,"value":8,"toc":506},"minimark",[9,18,34,37,42,49,53,57,73,209,213,276,281,287,291,295,382,385,389,414,417,421,426,469,473,502],[10,11,13,14],"h1",{"id":12},"décorateur-rate_limit","Décorateur ",[15,16,17],"code",{},"@rate_limit",[19,20,21,22,25,26,29,30,33],"p",{},"Le décorateur ",[15,23,24],{},"rate_limit"," limite le nombre d'appels à une méthode pendant une période. Au-delà de la limite, Dodock lève ",[15,27,28],{},"frappe.RateLimitExceededError"," et la réponse HTTP a le code ",[15,31,32],{},"429",".",[19,35,36],{},"Le décorateur agit seulement pendant une requête HTTP. Un appel depuis une tâche de fond ou la console n'est pas compté.",[38,39,41],"h2",{"id":40},"principe","Principe",[19,43,44,45,48],{},"Dodock garde un compteur dans Redis pour chaque méthode et chaque identité. Le compteur expire à la fin de la période (",[15,46,47],{},"seconds",").",[50,51,52],"mermaid",{},"\nflowchart LR\n    A[Appel de la méthode] --> B{Méthode HTTP concernée ?}\n    B -- Non --> E[Appel exécuté]\n    B -- Oui --> C[Compteur de l'identité + 1]\n    C --> D{Compteur > limit ?}\n    D -- Non --> E\n    D -- Oui --> F[Erreur 429]\n",[38,54,56],{"id":55},"paramètres","Paramètres",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from frappe.rate_limiter import rate_limit\n","python","",[15,65,66],{"__ignoreMap":63},[67,68,71],"span",{"class":69,"line":70},"line",1,[67,72,61],{},[74,75,76,92],"table",{},[77,78,79],"thead",{},[80,81,82,86,89],"tr",{},[83,84,85],"th",{},"Paramètre",[83,87,88],{},"Défaut",[83,90,91],{},"Rôle",[93,94,95,111,126,148,163,178,193],"tbody",{},[80,96,97,103,108],{},[98,99,100],"td",{},[15,101,102],{},"limit",[98,104,105],{},[15,106,107],{},"5",[98,109,110],{},"Nombre maximal d'appels dans la période. Accepte une fonction qui renvoie un entier",[80,112,113,117,123],{},[98,114,115],{},[15,116,47],{},[98,118,119,122],{},[15,120,121],{},"86400"," (24 h)",[98,124,125],{},"Durée de la période, en secondes",[80,127,128,133,138],{},[98,129,130],{},[15,131,132],{},"methods",[98,134,135],{},[15,136,137],{},"\"ALL\"",[98,139,140,141,144,145,147],{},"Méthodes HTTP comptées, par exemple ",[15,142,143],{},"[\"POST\"]",". ",[15,146,137],{}," compte toutes les méthodes",[80,149,150,155,160],{},[98,151,152],{},[15,153,154],{},"ip_based",[98,156,157],{},[15,158,159],{},"True",[98,161,162],{},"Identifie l'appelant par son adresse IP",[80,164,165,170,175],{},[98,166,167],{},[15,168,169],{},"key",[98,171,172],{},[15,173,174],{},"None",[98,176,177],{},"Nom d'un paramètre de la requête. Sa valeur est ajoutée à l'identité, par exemple l'adresse e-mail saisie",[80,179,180,185,190],{},[98,181,182],{},[15,183,184],{},"user_based",[98,186,187],{},[15,188,189],{},"False",[98,191,192],{},"Identifie l'appelant par son utilisateur connecté",[80,194,195,200,203],{},[98,196,197],{},[15,198,199],{},"endpoint",[98,201,202],{},"Chemin de la fonction",[98,204,205,206],{},"Nom du compteur. Nécessaire si la fonction n'a pas de chemin pointé stable, par exemple un ",[15,207,208],{},"functools.partial",[38,210,212],{"id":211},"identité-de-lappelant","Identité de l'appelant",[74,214,215,225],{},[77,216,217],{},[80,218,219,222],{},[83,220,221],{},"Réglage",[83,223,224],{},"Identité",[93,226,227,245,262],{},[80,228,229,236],{},[98,230,231,232,235],{},"Par défaut (",[15,233,234],{},"ip_based=True",")",[98,237,238,239,241,242,244],{},"Adresse IP, plus la valeur du paramètre ",[15,240,169],{}," si ",[15,243,169],{}," est défini",[80,246,247,256],{},[98,248,249,252,253,255],{},[15,250,251],{},"ip_based=False"," et ",[15,254,169],{}," défini",[98,257,258,259,261],{},"Valeur du paramètre ",[15,260,169],{}," seulement",[80,263,264,269],{},[98,265,266],{},[15,267,268],{},"user_based=True",[98,270,271,272,241,274,244],{},"Utilisateur connecté. Pour un invité, adresse IP. Plus la valeur du paramètre ",[15,273,169],{},[15,275,169],{},[277,278,280],"h3",{"id":279},"pourquoi-suivre-par-utilisateur","Pourquoi suivre par utilisateur",[19,282,283,284,286],{},"Dans une entreprise, plusieurs personnes partagent souvent la même adresse IP publique. Avec un suivi par IP, une personne qui atteint la limite bloque aussi ses collègues. Avec ",[15,285,268],{},", chaque utilisateur connecté a son propre compteur.",[38,288,290],{"id":289},"exemples","Exemples",[277,292,294],{"id":293},"limite-par-utilisateur","Limite par utilisateur",[58,296,298],{"className":60,"code":297,"language":62,"meta":63,"style":63},"import frappe\nfrom frappe.rate_limiter import rate_limit\n\n\n@frappe.whitelist()\n@rate_limit(limit=20, seconds=60, user_based=True)\ndef rechercher_clients(search_term: str):\n    \"\"\"Chaque utilisateur peut faire 20 recherches par minute.\"\"\"\n    return frappe.get_list(\n        \"Customer\",\n        filters={\"customer_name\": [\"like\", f\"%{search_term}%\"]},\n        fields=[\"name\", \"customer_name\"],\n        limit=50,\n    )\n",[15,299,300,305,310,317,322,328,334,340,346,352,358,364,370,376],{"__ignoreMap":63},[67,301,302],{"class":69,"line":70},[67,303,304],{},"import frappe\n",[67,306,308],{"class":69,"line":307},2,[67,309,61],{},[67,311,313],{"class":69,"line":312},3,[67,314,316],{"emptyLinePlaceholder":315},true,"\n",[67,318,320],{"class":69,"line":319},4,[67,321,316],{"emptyLinePlaceholder":315},[67,323,325],{"class":69,"line":324},5,[67,326,327],{},"@frappe.whitelist()\n",[67,329,331],{"class":69,"line":330},6,[67,332,333],{},"@rate_limit(limit=20, seconds=60, user_based=True)\n",[67,335,337],{"class":69,"line":336},7,[67,338,339],{},"def rechercher_clients(search_term: str):\n",[67,341,343],{"class":69,"line":342},8,[67,344,345],{},"    \"\"\"Chaque utilisateur peut faire 20 recherches par minute.\"\"\"\n",[67,347,349],{"class":69,"line":348},9,[67,350,351],{},"    return frappe.get_list(\n",[67,353,355],{"class":69,"line":354},10,[67,356,357],{},"        \"Customer\",\n",[67,359,361],{"class":69,"line":360},11,[67,362,363],{},"        filters={\"customer_name\": [\"like\", f\"%{search_term}%\"]},\n",[67,365,367],{"class":69,"line":366},12,[67,368,369],{},"        fields=[\"name\", \"customer_name\"],\n",[67,371,373],{"class":69,"line":372},13,[67,374,375],{},"        limit=50,\n",[67,377,379],{"class":69,"line":378},14,[67,380,381],{},"    )\n",[19,383,384],{},"Deux utilisateurs derrière la même adresse IP ont chacun 20 recherches par minute.",[277,386,388],{"id":387},"limite-par-ip-et-par-paramètre-sur-les-seuls-appels-post","Limite par IP et par paramètre, sur les seuls appels POST",[58,390,392],{"className":60,"code":391,"language":62,"meta":63,"style":63},"@frappe.whitelist(allow_guest=True)\n@rate_limit(key=\"email\", limit=5, seconds=60 * 60, methods=[\"POST\"])\ndef demander_rappel(email: str):\n    ...\n",[15,393,394,399,404,409],{"__ignoreMap":63},[67,395,396],{"class":69,"line":70},[67,397,398],{},"@frappe.whitelist(allow_guest=True)\n",[67,400,401],{"class":69,"line":307},[67,402,403],{},"@rate_limit(key=\"email\", limit=5, seconds=60 * 60, methods=[\"POST\"])\n",[67,405,406],{"class":69,"line":312},[67,407,408],{},"def demander_rappel(email: str):\n",[67,410,411],{"class":69,"line":319},[67,412,413],{},"    ...\n",[19,415,416],{},"Chaque couple adresse IP et adresse e-mail peut envoyer 5 demandes par heure.",[277,418,420],{"id":419},"limite-dynamique","Limite dynamique",[19,422,423,425],{},[15,424,102],{}," accepte une fonction. Dodock l'appelle à chaque requête.",[58,427,429],{"className":60,"code":428,"language":62,"meta":63,"style":63},"@frappe.whitelist()\n@rate_limit(\n    limit=lambda: frappe.db.get_single_value(\"Mes Parametres\", \"limite_appels\"),  # type de document fictif\n    seconds=300,\n    user_based=True,\n)\ndef mon_point_acces():\n    ...\n",[15,430,431,435,440,445,450,455,460,465],{"__ignoreMap":63},[67,432,433],{"class":69,"line":70},[67,434,327],{},[67,436,437],{"class":69,"line":307},[67,438,439],{},"@rate_limit(\n",[67,441,442],{"class":69,"line":312},[67,443,444],{},"    limit=lambda: frappe.db.get_single_value(\"Mes Parametres\", \"limite_appels\"),  # type de document fictif\n",[67,446,447],{"class":69,"line":319},[67,448,449],{},"    seconds=300,\n",[67,451,452],{"class":69,"line":324},[67,453,454],{},"    user_based=True,\n",[67,456,457],{"class":69,"line":330},[67,458,459],{},")\n",[67,461,462],{"class":69,"line":336},[67,463,464],{},"def mon_point_acces():\n",[67,466,467],{"class":69,"line":342},[67,468,413],{},[38,470,472],{"id":471},"bonnes-pratiques","Bonnes pratiques",[474,475,476,487,493,499],"ul",{},[477,478,479,480,483,484,33],"li",{},"Placez ",[15,481,482],{},"@frappe.whitelist()"," au-dessus de ",[15,485,486],{},"@rate_limit(...)",[477,488,489,490,492],{},"Utilisez ",[15,491,268],{}," pour les méthodes réservées aux utilisateurs connectés.",[477,494,495,496,498],{},"Pour une méthode ouverte aux invités, gardez le suivi par IP et ajoutez ",[15,497,169],{}," si un identifiant est saisi.",[477,500,501],{},"Choisissez un seuil adapté à l'usage réel. Trop bas, il bloque le travail normal. Trop haut, il ne protège plus.",[503,504,505],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":63,"searchDepth":70,"depth":307,"links":507},[508,509,510,513,518],{"id":40,"depth":307,"text":41},{"id":55,"depth":307,"text":56},{"id":211,"depth":307,"text":212,"children":511},[512],{"id":279,"depth":312,"text":280},{"id":289,"depth":307,"text":290,"children":514},[515,516,517],{"id":293,"depth":312,"text":294},{"id":387,"depth":312,"text":388},{"id":419,"depth":312,"text":420},{"id":471,"depth":307,"text":472},"Limiter le nombre d'appels à une fonction ou une route API avec le décorateur @rate_limit de Dodock","md",{"reviewed":315,"last_verified":522,"sources":523},"2026-11-03",[524,525,526],"dodock@develop:frappe\u002Frate_limiter.py","dodock@develop:frappe\u002Fexceptions.py","dodock@develop:frappe\u002Fcore\u002Fdoctype\u002Fuser\u002Fuser.py","\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fdecorateur-rate-limit",{"title":5,"description":519},"2.next\u002F3.dodock\u002F10.developpeurs\u002F1.api\u002Fdecorateur-rate-limit","mAcjgONQIx5Oq_BGiIkciADT7wBDmuuNda3OyJgLj3M",{},1793871729025]