Skip to content

Commit

Permalink
thread safety
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Mar 2, 2020
1 parent 97938a4 commit a8736e2
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 59 deletions.
1 change: 1 addition & 0 deletions docs/source/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ Reference
reference/syntax.rst
reference/table.rst
reference/text.rst
reference/theme.rst
reference/traceback.rst
144 changes: 86 additions & 58 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,19 +287,19 @@ def __init__(
else:
self._color_system = COLOR_SYSTEMS[color_system]

self._thread_locals = threading.local()
self._buffer_index = 0
self._record_buffer: List[Segment] = []

default_style = Style()
self.style_stack: List[Style] = [default_style]
self.current_style = default_style

self._log_render = LogRender(
show_time=log_time, show_path=log_path, time_format=log_time_format
)
self.highlighter: HighlighterType = highlighter or _null_highlighter

self._record_buffer_lock = threading.RLock()
self._thread_locals = threading.local()
self._record_buffer: List[Segment] = []

default_style = Style()
self._style_stack.append(default_style)
self._current_style = default_style

def __repr__(self) -> str:
return f"<console width={self.width} {str(self._color_system)}>"

Expand All @@ -311,7 +311,33 @@ def _buffer(self) -> List[Segment]:
buffer = self._thread_locals.buffer = []
return buffer

def _detect_color_system(self,) -> Optional[ColorSystem]:
@property
def _buffer_index(self) -> int:
"""Get a thread local buffer."""
return getattr(self._thread_locals, "buffer_index", 0)

@_buffer_index.setter
def _buffer_index(self, value: int) -> None:
self._thread_locals.buffer_index = value

@property
def _style_stack(self) -> List[Style]:
"""Get a thread local style stack."""
style_stack = getattr(self._thread_locals, "style_stack", None)
if style_stack is None:
style_stack = self._thread_locals.style_stack = []
return style_stack

@property
def _current_style(self) -> Style:
"""Get a thread local buffer."""
return getattr(self._thread_locals, "current_style", 0)

@_current_style.setter
def _current_style(self, style: Style) -> None:
self._thread_locals.current_style = style

def _detect_color_system(self) -> Optional[ColorSystem]:
"""Detect color system from env vars."""
if not self.is_terminal:
return None
Expand Down Expand Up @@ -499,7 +525,7 @@ def render(
Iterable[Segment]: An iterable of segments that may be rendered.
"""
yield from Segment.apply_style(
self._render(renderable, options), self.current_style
self._render(renderable, options), self._current_style
)

def render_all(
Expand Down Expand Up @@ -629,8 +655,8 @@ def push_style(self, style: Union[str, Style]) -> None:
"""
if isinstance(style, str):
style = self.get_style(style)
self.current_style = self.current_style + style
self.style_stack.append(self.current_style)
self._current_style = self._current_style + style
self._style_stack.append(self._current_style)

def pop_style(self) -> Style:
"""Pop a style from the stack.
Expand All @@ -640,12 +666,12 @@ def pop_style(self) -> Style:
Returns:
Style: The previously applied style.
"""
if len(self.style_stack) == 1:
if len(self._style_stack) == 1:
raise errors.StyleStackError(
"Can't pop the default style (check there is `push_style` for every `pop_style`)"
)
style = self.style_stack.pop()
self.current_style = self.style_stack[-1]
style = self._style_stack.pop()
self._current_style = self._style_stack[-1]
return style

def style(self, style: Optional[Union[str, Style]]) -> StyleContext:
Expand Down Expand Up @@ -894,15 +920,16 @@ def export_text(self, clear: bool = True, styles: bool = False) -> str:
self.record
), "To export console contents set record=True in the constructor or instance"

if styles:
text = "".join(
(style.render(text, reset=True) if style else text)
for text, style in self._record_buffer
)
else:
text = "".join(text for text, _ in self._record_buffer)
if clear:
del self._record_buffer[:]
with self._record_buffer_lock:
if styles:
text = "".join(
(style.render(text, reset=True) if style else text)
for text, style in self._record_buffer
)
else:
text = "".join(text for text, _ in self._record_buffer)
if clear:
del self._record_buffer[:]
return text

def save_text(self, path: str, clear: bool = True, styles: bool = False) -> None:
Expand Down Expand Up @@ -954,42 +981,43 @@ def escape(text: str) -> str:

render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format

if inline_styles:
for text, style in Segment.simplify(self._record_buffer):
text = escape(text)
if style:
rule = style.get_html_style(_theme)
append(f'<span style="{rule}">{text}</span>' if rule else text)
else:
append(text)
else:
styles: Dict[str, int] = {}
for text, style in Segment.simplify(self._record_buffer):
text = escape(text)
if style:
rule = style.get_html_style(_theme)
if rule:
style_number = styles.setdefault(rule, len(styles) + 1)
append(f'<span class="r{style_number}">{text}</span>')
with self._record_buffer_lock:
if inline_styles:
for text, style in Segment.simplify(self._record_buffer):
text = escape(text)
if style:
rule = style.get_html_style(_theme)
append(f'<span style="{rule}">{text}</span>' if rule else text)
else:
append(text)
else:
append(text)
stylesheet_rules: List[str] = []
stylesheet_append = stylesheet_rules.append
for style_rule, style_number in styles.items():
if style_rule:
stylesheet_append(f".r{style_number} {{{style_rule}}}")
stylesheet = "\n".join(stylesheet_rules)

rendered_code = render_code_format.format(
code="".join(fragments),
stylesheet=stylesheet,
foreground=_theme.foreground_color.hex,
background=_theme.background_color.hex,
)
if clear:
del self._record_buffer[:]
else:
styles: Dict[str, int] = {}
for text, style in Segment.simplify(self._record_buffer):
text = escape(text)
if style:
rule = style.get_html_style(_theme)
if rule:
style_number = styles.setdefault(rule, len(styles) + 1)
append(f'<span class="r{style_number}">{text}</span>')
else:
append(text)
else:
append(text)
stylesheet_rules: List[str] = []
stylesheet_append = stylesheet_rules.append
for style_rule, style_number in styles.items():
if style_rule:
stylesheet_append(f".r{style_number} {{{style_rule}}}")
stylesheet = "\n".join(stylesheet_rules)

rendered_code = render_code_format.format(
code="".join(fragments),
stylesheet=stylesheet,
foreground=_theme.foreground_color.hex,
background=_theme.background_color.hex,
)
if clear:
del self._record_buffer[:]
return rendered_code

def save_html(
Expand Down
2 changes: 1 addition & 1 deletion rich/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Panel:
>>> console.print(Panel("Hello, World!))
Args:
renderable (ConsoleRenderable): A console renderable objects.
renderable (RenderableType): A console renderable object.
box (Box, optional): A Box instance that defines the look of the border.
Defaults to box.SQUARE.
expand (bool, optional): If True the panel will stretch to fill the console
Expand Down
1 change: 1 addition & 0 deletions rich/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def flexible(self) -> bool:


class _Cell(NamedTuple):
"""A single cell in a table."""

style: Union[str, Style]
renderable: "RenderableType"
Expand Down

0 comments on commit a8736e2

Please sign in to comment.