2
2
3
3
import asyncio
4
4
import base64
5
+ import random
5
6
import re
7
+ import time
6
8
from dataclasses import asdict
7
9
from pathlib import Path
8
10
from typing import Any , Literal , Optional
@@ -254,6 +256,8 @@ def draw_mermaid_png(
254
256
draw_method : MermaidDrawMethod = MermaidDrawMethod .API ,
255
257
background_color : Optional [str ] = "white" ,
256
258
padding : int = 10 ,
259
+ max_retries : int = 1 ,
260
+ retry_delay : float = 1.0 ,
257
261
) -> bytes :
258
262
"""Draws a Mermaid graph as PNG using provided syntax.
259
263
@@ -266,6 +270,10 @@ def draw_mermaid_png(
266
270
background_color (str, optional): Background color of the image.
267
271
Defaults to "white".
268
272
padding (int, optional): Padding around the image. Defaults to 10.
273
+ max_retries (int, optional): Maximum number of retries (MermaidDrawMethod.API).
274
+ Defaults to 1.
275
+ retry_delay (float, optional): Delay between retries (MermaidDrawMethod.API).
276
+ Defaults to 1.0.
269
277
270
278
Returns:
271
279
bytes: PNG image bytes.
@@ -283,7 +291,11 @@ def draw_mermaid_png(
283
291
)
284
292
elif draw_method == MermaidDrawMethod .API :
285
293
img_bytes = _render_mermaid_using_api (
286
- mermaid_syntax , output_file_path , background_color
294
+ mermaid_syntax ,
295
+ output_file_path = output_file_path ,
296
+ background_color = background_color ,
297
+ max_retries = max_retries ,
298
+ retry_delay = retry_delay ,
287
299
)
288
300
else :
289
301
supported_methods = ", " .join ([m .value for m in MermaidDrawMethod ])
@@ -371,9 +383,12 @@ async def _render_mermaid_using_pyppeteer(
371
383
372
384
def _render_mermaid_using_api (
373
385
mermaid_syntax : str ,
386
+ * ,
374
387
output_file_path : Optional [str ] = None ,
375
388
background_color : Optional [str ] = "white" ,
376
389
file_type : Optional [Literal ["jpeg" , "png" , "webp" ]] = "png" ,
390
+ max_retries : int = 1 ,
391
+ retry_delay : float = 1.0 ,
377
392
) -> bytes :
378
393
"""Renders Mermaid graph using the Mermaid.INK API."""
379
394
try :
@@ -400,15 +415,55 @@ def _render_mermaid_using_api(
400
415
f"https://mermaid.ink/img/{ mermaid_syntax_encoded } "
401
416
f"?type={ file_type } &bgColor={ background_color } "
402
417
)
403
- response = requests .get (image_url , timeout = 10 )
404
- if response .status_code == requests .codes .ok :
405
- img_bytes = response .content
406
- if output_file_path is not None :
407
- Path (output_file_path ).write_bytes (response .content )
408
418
409
- return img_bytes
410
- msg = (
411
- f"Failed to render the graph using the Mermaid.INK API. "
412
- f"Status code: { response .status_code } ."
419
+ error_msg_suffix = (
420
+ "To resolve this issue:\n "
421
+ "1. Check your internet connection and try again\n "
422
+ "2. Try with higher retry settings: "
423
+ "`draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`\n "
424
+ "3. Use the Pyppeteer rendering method which will render your graph locally "
425
+ "in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`"
413
426
)
427
+
428
+ for attempt in range (max_retries + 1 ):
429
+ try :
430
+ response = requests .get (image_url , timeout = 10 )
431
+ if response .status_code == requests .codes .ok :
432
+ img_bytes = response .content
433
+ if output_file_path is not None :
434
+ Path (output_file_path ).write_bytes (response .content )
435
+
436
+ return img_bytes
437
+
438
+ # If we get a server error (5xx), retry
439
+ if 500 <= response .status_code < 600 and attempt < max_retries :
440
+ # Exponential backoff with jitter
441
+ sleep_time = retry_delay * (2 ** attempt ) * (0.5 + 0.5 * random .random ()) # noqa: S311 not used for crypto
442
+ time .sleep (sleep_time )
443
+ continue
444
+
445
+ # For other status codes, fail immediately
446
+ msg = (
447
+ "Failed to reach https://mermaid.ink/ API while trying to render "
448
+ f"your graph. Status code: { response .status_code } .\n \n "
449
+ ) + error_msg_suffix
450
+ raise ValueError (msg )
451
+
452
+ except (requests .RequestException , requests .Timeout ) as e :
453
+ if attempt < max_retries :
454
+ # Exponential backoff with jitter
455
+ sleep_time = retry_delay * (2 ** attempt ) * (0.5 + 0.5 * random .random ()) # noqa: S311 not used for crypto
456
+ time .sleep (sleep_time )
457
+ else :
458
+ msg = (
459
+ "Failed to reach https://mermaid.ink/ API while trying to render "
460
+ f"your graph after { max_retries } retries. "
461
+ ) + error_msg_suffix
462
+ raise ValueError (msg ) from e
463
+
464
+ # This should not be reached, but just in case
465
+ msg = (
466
+ "Failed to reach https://mermaid.ink/ API while trying to render "
467
+ f"your graph after { max_retries } retries. "
468
+ ) + error_msg_suffix
414
469
raise ValueError (msg )
0 commit comments