-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
533 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from snowflake.cli.api.console.console import cli_console | ||
|
||
__all__ = ("cli_console",) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
from abc import ABC, abstractmethod | ||
from contextlib import contextmanager | ||
from typing import Callable, Iterator, Optional | ||
|
||
from rich import print as rich_print | ||
from rich.text import Text | ||
from snowflake.cli.api.cli_global_context import _CliGlobalContextAccess, cli_context | ||
|
||
|
||
class AbstractConsole(ABC): | ||
"""Interface for cli console implementation. | ||
Each console should have three methods implemented: | ||
- `step` - for more detailed informations on steps | ||
- `warning` - for displaying messages in a style that makes it | ||
visually stand out from other output | ||
- `phase` a context manager for organising steps into logical group | ||
""" | ||
|
||
_print_fn: Callable[[str], None] | ||
_cli_context: _CliGlobalContextAccess | ||
_in_phase: bool | ||
|
||
def __init__(self): | ||
super().__init__() | ||
self._cli_context = cli_context | ||
self._in_phase = False | ||
|
||
@property | ||
def is_silent(self) -> bool: | ||
"""Returns information whether intermediate output is muted.""" | ||
return self._cli_context.silent | ||
|
||
@property | ||
def in_phase(self) -> bool: | ||
"""Indicated whether output should be grouped.""" | ||
return self._in_phase | ||
|
||
def _print(self, text: Text): | ||
if self.is_silent: | ||
return | ||
rich_print(text) | ||
|
||
@contextmanager | ||
@abstractmethod | ||
def phase( | ||
self, | ||
enter_message: str, | ||
exit_message: Optional[str] = None, | ||
) -> Iterator[Callable[[str], None]]: | ||
"""A context manager for organising steps into logical group.""" | ||
|
||
@abstractmethod | ||
def step(self, message: str): | ||
"""Displays message to output.""" | ||
|
||
@abstractmethod | ||
def warning(self, message: str): | ||
"""Displays message in a style that makes it visually stand out from other output. | ||
Intended for diplaying messeges related to important messages.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from __future__ import annotations | ||
|
||
from contextlib import contextmanager | ||
from typing import Optional | ||
|
||
from rich.style import Style | ||
from rich.text import Text | ||
from snowflake.cli.api.console.abc import AbstractConsole | ||
from snowflake.cli.api.console.enum import Output | ||
|
||
PHASE_STYLE: Style = Style(color="grey93", bold=True) | ||
STEP_STYLE: Style = Style(color="grey89", italic=True) | ||
IMPORTANT_STYLE: Style = Style(color="red", bold=True, italic=True) | ||
INDENTATION_LEVEL: int = 2 | ||
|
||
|
||
class CliConsoleNestingProhibitedError(RuntimeError): | ||
"""CliConsole phase nesting not allowed.""" | ||
|
||
|
||
class CliConsole(AbstractConsole): | ||
"""An utility for displaying intermediate output. | ||
Provides following methods for handling displying messages: | ||
- `step` - for more detailed informations on steps | ||
- `warning` - for displaying messages in a style that makes it | ||
visually stand out from other output | ||
- `phase` a context manager for organising steps into logical group | ||
""" | ||
|
||
_indentation_level: int = INDENTATION_LEVEL | ||
_styles: dict = { | ||
"default": "", | ||
Output.PHASE: PHASE_STYLE, | ||
Output.STEP: STEP_STYLE, | ||
Output.IMPORTANT: IMPORTANT_STYLE, | ||
} | ||
|
||
def _format_message(self, message: str, output: Output) -> Text: | ||
"""Wraps message in rich Text object and applys formatting.""" | ||
style = self._styles.get(output, "default") | ||
text = Text(message, style=style) | ||
|
||
if self.in_phase and output in {Output.STEP, Output.IMPORTANT}: | ||
text.pad_left(self._indentation_level) | ||
|
||
return text | ||
|
||
@contextmanager | ||
def phase(self, enter_message: str, exit_message: Optional[str] = None): | ||
"""A context manager for organising steps into logical group.""" | ||
if self.in_phase: | ||
raise CliConsoleNestingProhibitedError("Only one phase allowed at a time.") | ||
|
||
self._print(self._format_message(enter_message, Output.PHASE)) | ||
self._in_phase = True | ||
|
||
yield self.step | ||
|
||
self._in_phase = False | ||
if exit_message: | ||
self._print(self._format_message(exit_message, Output.PHASE)) | ||
|
||
def step(self, message: str): | ||
"""Displays messge to output. | ||
If called within a phase, the output will be indented. | ||
""" | ||
text = self._format_message(message, Output.STEP) | ||
self._print(text) | ||
|
||
def warning(self, message: str): | ||
"""Displays message in a style that makes it visually stand out from other output. | ||
Intended for diplaying messeges related to important messages.""" | ||
text = self._format_message(message, Output.IMPORTANT) | ||
self._print(text) | ||
|
||
|
||
def get_cli_console() -> AbstractConsole: | ||
console = CliConsole() | ||
return console | ||
|
||
|
||
cli_console = get_cli_console() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from enum import Enum | ||
|
||
Output = Enum("Output", ("PHASE", "STEP", "IMPORTANT")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
import logging | ||
import os | ||
|
||
# Suppress logging from Snowflake connector | ||
logging.getLogger("snowflake").setLevel(logging.ERROR) | ||
|
||
# Restrict permissions of all created files | ||
os.umask(0o077) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Generator | ||
|
||
import pytest | ||
from snowflake.cli.api.console.console import ( | ||
CliConsole, | ||
CliConsoleNestingProhibitedError, | ||
) | ||
|
||
|
||
@pytest.fixture(name="cli_console") | ||
def make_cli_console() -> Generator[CliConsole, None, None]: | ||
console = CliConsole() | ||
yield console | ||
|
||
|
||
def assert_output_matches(expected: str, capsys): | ||
out, _ = capsys.readouterr() | ||
assert out == expected | ||
|
||
|
||
def test_phase_alone_produces_no_output(cli_console, capsys): | ||
cli_console.phase("42") | ||
assert_output_matches("", capsys) | ||
|
||
|
||
def test_only_step_no_indent(cli_console, capsys): | ||
cli_console.step("73") | ||
assert_output_matches("73\n", capsys) | ||
|
||
|
||
def test_step_indented_in_phase(cli_console, capsys): | ||
with cli_console.phase("42"): | ||
cli_console.step("73") | ||
assert_output_matches("42\n 73\n", capsys) | ||
|
||
|
||
def test_multi_step_indented(cli_console, capsys): | ||
with cli_console.phase("42"): | ||
cli_console.step("73.1") | ||
cli_console.step("73.2") | ||
assert_output_matches("42\n 73.1\n 73.2\n", capsys) | ||
|
||
|
||
def test_phase_after_step_not_indented(cli_console, capsys): | ||
with cli_console.phase("42"): | ||
cli_console.step("73") | ||
cli_console.step("42") | ||
assert_output_matches("42\n 73\n42\n", capsys) | ||
|
||
|
||
def test_error_messages(cli_console, capsys): | ||
with cli_console.phase("42"): | ||
cli_console.step("73") | ||
cli_console.warning("ops") | ||
cli_console.warning("OPS") | ||
|
||
assert_output_matches("42\n 73\n ops\nOPS\n", capsys) | ||
|
||
|
||
def test_phase_nesting_not_allowed(cli_console): | ||
with cli_console.phase("Enter 1"): | ||
with pytest.raises(CliConsoleNestingProhibitedError): | ||
with cli_console.phase("Enter 2"): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from contextlib import contextmanager | ||
|
||
from snowflake.cli.api.console.abc import AbstractConsole | ||
|
||
|
||
def test_console_base_class(capsys): | ||
class TConsole(AbstractConsole): | ||
@contextmanager | ||
def phase(self, enter_message: str, exit_message: str): | ||
print(enter_message) | ||
yield self.step | ||
print(exit_message) | ||
|
||
def step(self, message: str): | ||
print(message) | ||
|
||
def warning(self, message: str): | ||
print(message) | ||
|
||
console = TConsole() | ||
assert not console.is_silent | ||
|
||
with console.phase("Enter", "Exit"): | ||
console.step("b") | ||
console.warning("c") | ||
|
||
out, _ = capsys.readouterr() | ||
assert out == "Enter\nb\nc\nExit\n" |
Oops, something went wrong.