Skip to content

Commit

Permalink
fix: Fix race condition issue
Browse files Browse the repository at this point in the history
The `markdown` singleton was causing
race conditions while testing.
The code is simplified,
and the Markdown converter
is instantiated by each formatter.
  • Loading branch information
pawamoy committed Sep 1, 2022
1 parent b3c844d commit 37d7f86
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 35 deletions.
4 changes: 2 additions & 2 deletions src/markdown_exec/formatters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from markupsafe import Markup

from markdown_exec.logger import get_logger
from markdown_exec.rendering import add_source, code_block, markdown
from markdown_exec.rendering import MarkdownConverter, add_source, code_block

logger = get_logger(__name__)

Expand Down Expand Up @@ -41,7 +41,7 @@ def base_format( # noqa: WPS231
Returns:
HTML contents.
"""
markdown.setup(md)
markdown = MarkdownConverter(md)
extra = options.get("extra", {})
try:
output = run(code, **extra)
Expand Down
4 changes: 2 additions & 2 deletions src/markdown_exec/formatters/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from markdown_exec.formatters.sh import _run_sh # noqa: WPS450
from markdown_exec.logger import get_logger
from markdown_exec.rendering import add_source, code_block, markdown
from markdown_exec.rendering import MarkdownConverter, add_source, code_block

logger = get_logger(__name__)

Expand All @@ -24,7 +24,7 @@ def _format_console( # noqa: WPS231
tabs: tuple[str, str],
**options: Any,
) -> str:
markdown.setup(md)
markdown = MarkdownConverter(md)

sh_lines = []
for line in code.split("\n"):
Expand Down
4 changes: 2 additions & 2 deletions src/markdown_exec/formatters/pycon.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from markdown_exec.formatters.python import _run_python # noqa: WPS450
from markdown_exec.logger import get_logger
from markdown_exec.rendering import add_source, code_block, markdown
from markdown_exec.rendering import MarkdownConverter, add_source, code_block

logger = get_logger(__name__)

Expand All @@ -24,7 +24,7 @@ def _format_pycon( # noqa: WPS231
tabs: tuple[str, str],
**options: Any,
) -> str:
markdown.setup(md)
markdown = MarkdownConverter(md)

python_lines = []
for line in code.split("\n"):
Expand Down
4 changes: 2 additions & 2 deletions src/markdown_exec/formatters/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from markdown import Markdown

from markdown_exec.rendering import code_block, markdown
from markdown_exec.rendering import MarkdownConverter, code_block


def _rec_build_tree(lines: list[str], parent: list, offset: int, base_indent: int):
Expand Down Expand Up @@ -62,6 +62,6 @@ def _format_tree( # noqa: WPS231
tabs: tuple[str, str],
**options: Any,
) -> str:
markdown.setup(md)
markdown = MarkdownConverter(md)
output = "\n".join(_rec_format_tree(_build_tree(code)))
return markdown.convert(code_block(result or "bash", output))
36 changes: 9 additions & 27 deletions src/markdown_exec/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,28 +128,25 @@ def run(self, root: Element): # noqa: D102,WPS231


def _mimic(md: Markdown) -> Markdown:
md = getattr(md, "_original_md", md)
new_md = Markdown() # noqa: WPS442
new_md.registerExtensions(md.registeredExtensions + ["tables", "md_in_html"], {})
new_md.treeprocessors.register(
_IdPrependingTreeprocessor(md, ""),
_IdPrependingTreeprocessor.name,
priority=4, # right after 'toc' (needed because that extension adds ids to headers)
)
new_md._original_md = md # type: ignore[attr-defined] # noqa: WPS437
return new_md


class _MarkdownConverter:
class MarkdownConverter:
"""Helper class to avoid breaking the original Markdown instance state."""

def __init__(self) -> None: # noqa: D107
self._md_ref: Markdown = None # type: ignore[assignment]
self._md_stack: list[Markdown] = []
self._counter: int = 0
self._level: int = 0
counter: int = 0

def setup(self, md: Markdown) -> None:
if not self._md_ref:
self._md_ref = md
def __init__(self, md: Markdown) -> None: # noqa: D107
self._md_ref: Markdown = md

def convert(self, text: str, stash: dict[str, str] | None = None) -> Markup:
"""Convert Markdown text to safe HTML.
Expand All @@ -161,34 +158,19 @@ def convert(self, text: str, stash: dict[str, str] | None = None) -> Markup:
Returns:
Safe HTML.
"""
# store current md instance
md = self._md
self._level += 1
md = _mimic(self._md_ref)

# prepare for conversion
md.treeprocessors[_IdPrependingTreeprocessor.name].id_prefix = f"exec-{self._counter}--"
self._counter += 1
md.treeprocessors[_IdPrependingTreeprocessor.name].id_prefix = f"exec-{MarkdownConverter.counter}--"
MarkdownConverter.counter += 1

try: # noqa: WPS501
converted = md.convert(text)
finally:
self._level -= 1
md.treeprocessors[_IdPrependingTreeprocessor.name].id_prefix = ""

# restore html from stash
for placeholder, stashed in (stash or {}).items():
converted = converted.replace(placeholder, stashed)

return Markup(converted)

@property
def _md(self) -> Markdown:
try:
return self._md_stack[self._level]
except IndexError:
self._md_stack.append(_mimic(self._md_ref))
return self._md


# provide a singleton
markdown = _MarkdownConverter()

0 comments on commit 37d7f86

Please sign in to comment.