Attempt to parse via PythonMonkey/mermaid-parser-py. Returns the extracted parse-error message, "" on success, or None when PythonMonkey itself is unusable (broken JS event loop binding on Python 3.13+) so the caller can fall back to mermaid-py.
(diagram_content: str)
| 159 | |
| 160 | |
| 161 | async def _try_pythonmonkey_parse(diagram_content: str) -> str | None: |
| 162 | """Attempt to parse via PythonMonkey/mermaid-parser-py. |
| 163 | |
| 164 | Returns the extracted parse-error message, "" on success, or None when |
| 165 | PythonMonkey itself is unusable (broken JS event loop binding on |
| 166 | Python 3.13+) so the caller can fall back to mermaid-py. |
| 167 | """ |
| 168 | global _PYTHONMONKEY_BROKEN |
| 169 | if _PYTHONMONKEY_BROKEN: |
| 170 | return None |
| 171 | |
| 172 | import os |
| 173 | |
| 174 | try: |
| 175 | from mermaid_parser.parser import parse_mermaid_py |
| 176 | except Exception: |
| 177 | _PYTHONMONKEY_BROKEN = True |
| 178 | return None |
| 179 | |
| 180 | old_stderr = sys.stderr |
| 181 | sys.stderr = open(os.devnull, 'w') |
| 182 | try: |
| 183 | if ( |
| 184 | _main_loop is not None |
| 185 | and _main_loop.is_running() |
| 186 | and threading.get_ident() != _main_loop_thread_ident |
| 187 | ): |
| 188 | fut = asyncio.run_coroutine_threadsafe( |
| 189 | parse_mermaid_py(diagram_content), _main_loop |
| 190 | ) |
| 191 | await asyncio.wrap_future(fut) |
| 192 | else: |
| 193 | await parse_mermaid_py(diagram_content) |
| 194 | return "" |
| 195 | except Exception as e: |
| 196 | error_str = str(e) |
| 197 | # PythonMonkey 1.3.1 only supports Python 3.8-3.11; on newer Pythons |
| 198 | # every JS call raises this. Latch the failure once so subsequent |
| 199 | # diagrams skip the broken path and go straight to mermaid-py. |
| 200 | if "cannot find a running Python event-loop" in error_str: |
| 201 | _PYTHONMONKEY_BROKEN = True |
| 202 | return None |
| 203 | match = re.search(r"Error:(.*?)(?=Stack Trace:|$)", error_str, re.DOTALL) |
| 204 | if match: |
| 205 | return match.group(0).strip() |
| 206 | # Unknown error from the JS parser — fall back rather than surface it. |
| 207 | return None |
| 208 | finally: |
| 209 | sys.stderr.close() |
| 210 | sys.stderr = old_stderr |
| 211 | |
| 212 | |
| 213 | def _parse_via_mermaid_py(diagram_content: str) -> str: |
no outgoing calls
no test coverage detected