1
0
mirror of https://github.com/httpie/cli.git synced 2026-04-26 20:02:11 +02:00
Files
httpie-cli/httpie/context.py
T

219 lines
6.6 KiB
Python
Raw Normal View History

2022-04-14 17:43:10 +03:00
import argparse
2014-04-27 00:07:13 +02:00
import sys
2020-06-26 11:28:03 -05:00
import os
import warnings
from contextlib import contextmanager
from pathlib import Path
2022-04-14 17:43:10 +03:00
from typing import Iterator, IO, Optional, TYPE_CHECKING
from enum import Enum
2019-08-29 13:39:42 +02:00
2016-09-06 11:07:52 +01:00
try:
import curses
except ImportError:
curses = None # Compiled w/o curses
2014-04-27 00:07:13 +02:00
2022-04-14 17:43:10 +03:00
from .compat import is_windows, cached_property
2021-05-05 14:13:39 +02:00
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
from .encoding import UTF8
2014-04-27 00:07:13 +02:00
2021-05-05 14:13:39 +02:00
from .utils import repr_dict
2022-04-14 17:43:10 +03:00
from httpie.output.ui import rich_palette as palette
if TYPE_CHECKING:
from rich.console import Console
2016-03-05 01:42:13 +08:00
2014-04-27 00:07:13 +02:00
class Levels(str, Enum):
WARNING = 'warning'
ERROR = 'error'
DISPLAY_THRESHOLDS = {
Levels.WARNING: 2,
Levels.ERROR: float('inf'), # Never hide errors.
}
class Environment:
2014-04-27 00:07:13 +02:00
"""
Information about the execution context
(standard streams, config directory, etc).
By default, it represents the actual environment.
All of the attributes can be overwritten though, which
is used by the test suite to simulate various scenarios.
"""
2022-04-14 17:43:10 +03:00
args = argparse.Namespace()
is_windows: bool = is_windows
config_dir: Path = DEFAULT_CONFIG_DIR
2019-08-29 13:39:42 +02:00
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
stdin_isatty: bool = stdin.isatty() if stdin else False
stdin_encoding: str = None
stdout: IO = sys.stdout
stdout_isatty: bool = stdout.isatty()
stdout_encoding: str = None
stderr: IO = sys.stderr
stderr_isatty: bool = stderr.isatty()
colors = 256
program_name: str = 'http'
2022-04-14 17:43:10 +03:00
# Whether to show progress bars / status spinners etc.
show_displays: bool = True
if not is_windows:
2016-09-06 11:07:52 +01:00
if curses:
2015-01-23 22:19:02 +01:00
try:
2016-09-06 11:07:52 +01:00
curses.setupterm()
2015-01-23 22:19:02 +01:00
colors = curses.tigetnum('colors')
2016-09-06 11:07:52 +01:00
except curses.error:
pass
else:
2014-04-27 00:07:13 +02:00
# noinspection PyUnresolvedReferences
2020-06-27 11:08:44 -05:00
import colorama.initialise
stdout = colorama.initialise.wrap_stream(
stdout, convert=None, strip=None,
autoreset=True, wrap=True
)
stderr = colorama.initialise.wrap_stream(
stderr, convert=None, strip=None,
autoreset=True, wrap=True
)
del colorama
2014-04-27 00:07:13 +02:00
2020-08-15 15:25:05 +02:00
def __init__(self, devnull=None, **kwargs):
2014-04-27 00:07:13 +02:00
"""
Use keyword arguments to overwrite
any of the class attributes for this instance.
"""
assert all(hasattr(type(self), attr) for attr in kwargs.keys())
self.__dict__.update(**kwargs)
2020-08-15 15:25:05 +02:00
# The original STDERR unaffected by --quiet’ing.
self._orig_stderr = self.stderr
self._devnull = devnull
2021-08-05 20:58:43 +02:00
# Keyword arguments > stream.encoding > default UTF-8
2019-07-17 22:58:37 -06:00
if self.stdin and self.stdin_encoding is None:
2014-04-27 00:07:13 +02:00
self.stdin_encoding = getattr(
2021-08-05 20:58:43 +02:00
self.stdin, 'encoding', None) or UTF8
2014-04-27 00:07:13 +02:00
if self.stdout_encoding is None:
actual_stdout = self.stdout
if is_windows:
# noinspection PyUnresolvedReferences
2014-04-27 00:07:13 +02:00
from colorama import AnsiToWin32
if isinstance(self.stdout, AnsiToWin32):
# noinspection PyUnresolvedReferences
2014-04-27 00:07:13 +02:00
actual_stdout = self.stdout.wrapped
self.stdout_encoding = getattr(
2021-08-05 20:58:43 +02:00
actual_stdout, 'encoding', None) or UTF8
2014-04-27 00:07:13 +02:00
self.quiet = kwargs.pop('quiet', 0)
2016-03-05 01:42:13 +08:00
def __str__(self):
defaults = dict(type(self).__dict__)
actual = dict(defaults)
actual.update(self.__dict__)
actual['config'] = self.config
2019-08-31 18:00:03 +02:00
return repr_dict({
key: value
2016-03-05 01:42:13 +08:00
for key, value in actual.items()
2019-08-31 18:00:03 +02:00
if not key.startswith('_')
})
2016-03-05 01:42:13 +08:00
def __repr__(self):
2019-08-31 15:17:10 +02:00
return f'<{type(self).__name__} {self}>'
_config: Config = None
@property
def config(self) -> Config:
config = self._config
if not config:
self._config = config = Config(directory=self.config_dir)
if not config.is_new():
try:
config.load()
except ConfigFileError as e:
self.log_error(e, level='warning')
return config
2020-07-14 17:21:57 -05:00
@property
def devnull(self) -> IO:
if self._devnull is None:
self._devnull = open(os.devnull, 'w+')
return self._devnull
@contextmanager
def as_silent(self) -> Iterator[None]:
original_stdout = self.stdout
original_stderr = self.stderr
try:
self.stdout = self.devnull
self.stderr = self.devnull
yield
finally:
self.stdout = original_stdout
self.stderr = original_stderr
def log_error(self, msg: str, level: Levels = Levels.ERROR) -> None:
if self.stdout_isatty and self.quiet >= DISPLAY_THRESHOLDS[level]:
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
else:
stderr = self._orig_stderr
stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
def apply_warnings_filter(self) -> None:
if self.quiet >= DISPLAY_THRESHOLDS[Levels.WARNING]:
warnings.simplefilter("ignore")
2022-04-14 17:43:10 +03:00
def _make_rich_console(
self,
file: IO[str],
force_terminal: bool
) -> 'Console':
from rich.console import Console
from rich.theme import Theme
from rich.style import Style
style = getattr(self.args, 'style', palette.AUTO_STYLE)
theme = {}
if style in palette.STYLE_SHADES:
shade = palette.STYLE_SHADES[style]
theme.update({
color: Style(
color=palette.get_color(
color,
shade,
palette=palette.RICH_THEME_PALETTE
),
bold=True
)
for color in palette.RICH_THEME_PALETTE
})
# Rich infers the rest of the knowledge (e.g encoding)
# dynamically by looking at the file/stderr.
return Console(
file=file,
force_terminal=force_terminal,
no_color=(self.colors == 0),
theme=Theme(theme)
)
# Rich recommends separating the actual console (stdout) from
2022-04-14 17:43:10 +03:00
# the error (stderr) console for better isolation between parts.
# https://rich.readthedocs.io/en/stable/console.html#error-console
@cached_property
def rich_console(self):
return self._make_rich_console(self.stdout, self.stdout_isatty)
@cached_property
def rich_error_console(self):
return self._make_rich_console(self.stderr, self.stderr_isatty)