Skip to content

Feature/color on win #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d14e83
refactored debug.Debug._env_bool to prettier.env_bool
Cielquan May 28, 2020
292d30b
added tests for prettier.env_bool
Cielquan May 28, 2020
34e6b69
added highlight module
Cielquan May 28, 2020
2e6764c
implement use_highlight function in Debug cls
Cielquan May 28, 2020
62ba8a6
added tests for highlight module to test_main
Cielquan May 28, 2020
1d1f056
fixed imports
Cielquan May 28, 2020
356f2a9
added type hints to `env_true` and `env_bool`
Cielquan May 30, 2020
598bc12
updated `test_env_bool` because of type hints
Cielquan May 30, 2020
f2eb27e
change double quotes to single quotes in PR code
Cielquan May 30, 2020
a6b9150
added 'windows' to ci
Cielquan May 30, 2020
6997084
added xfail to test failing on win
Cielquan May 30, 2020
9fa9b3f
changed " to ' in test_expr_render
Cielquan May 31, 2020
4fb13e4
refactored highlight.py into utils.py; refactored sys test for win in…
Cielquan May 31, 2020
bcfd996
changed " to ' in test_main
Cielquan May 31, 2020
3025340
added covdefaults pkg
Cielquan May 31, 2020
0199463
fixed lint
Cielquan May 31, 2020
0a793fc
fixed lint (2)
Cielquan May 31, 2020
37555cd
refactored env_bool & env_true from prettier into utils; movedd tests…
Cielquan May 31, 2020
7a7b73a
fixed lint
Cielquan May 31, 2020
7073538
fixed type hints in utils
Cielquan May 31, 2020
c312ee7
fixed Debug.__call__ rebase error
Cielquan May 31, 2020
940dd3f
added cov req = 0 to pytest cmd
Cielquan May 31, 2020
83df9c2
removed covdefaults; added nocover to win parts
Cielquan May 31, 2020
a5ea9e0
fixed make install
Cielquan May 31, 2020
3c2b126
fixed double quotes to single
Cielquan Jun 11, 2020
2718027
changed docstring format
Cielquan Jun 11, 2020
9ec2d1b
refactored _enable_vt_mode subfunction into activate_win_color function
Cielquan Jun 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos]
os: [ubuntu, macos, windows]
python-version: ['3.6', '3.7', '3.8']

env:
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ black = black -S -l 120 --target-version py37 devtools

.PHONY: install
install:
pip install -U setuptools pip
python -m pip install -U setuptools pip
pip install -U -r requirements.txt
pip install -e .

Expand All @@ -27,11 +27,11 @@ check-dist:

.PHONY: test
test:
pytest --cov=devtools
pytest --cov=devtools --cov-fail-under 0

.PHONY: testcov
testcov:
pytest --cov=devtools
pytest --cov=devtools --cov-fail-under 0
@echo "building coverage html"
@coverage html

Expand Down
18 changes: 5 additions & 13 deletions devtools/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import sys

from .ansi import sformat
from .prettier import PrettyFormat, env_true
from .prettier import PrettyFormat
from .timer import Timer
from .utils import isatty
from .utils import env_bool, env_true, use_highlight

__all__ = 'Debug', 'debug'
MYPY = False
Expand Down Expand Up @@ -111,22 +111,14 @@ class Debug:
def __init__(
self, *, warnings: 'Optional[bool]' = None, highlight: 'Optional[bool]' = None, frame_context_length: int = 50
):
self._show_warnings = self._env_bool(warnings, 'PY_DEVTOOLS_WARNINGS', True)
self._highlight = self._env_bool(highlight, 'PY_DEVTOOLS_HIGHLIGHT', None)
self._show_warnings = env_bool(warnings, 'PY_DEVTOOLS_WARNINGS', True)
self._highlight = highlight
# 50 lines should be enough to make sure we always get the entire function definition
self._frame_context_length = frame_context_length

@classmethod
def _env_bool(cls, value, env_name, env_default):
if value is None:
return env_true(env_name, env_default)
else:
return value

def __call__(self, *args, file_=None, flush_=True, **kwargs) -> None:
d_out = self._process(args, kwargs, 'debug')
highlight = isatty(file_) if self._highlight is None else self._highlight
s = d_out.str(highlight)
s = d_out.str(use_highlight(self._highlight, file_))
print(s, file=file_, flush=flush_)

def format(self, *args, **kwargs) -> DebugOutput:
Expand Down
10 changes: 1 addition & 9 deletions devtools/prettier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import OrderedDict
from collections.abc import Generator

from .utils import isatty
from .utils import env_true, isatty

__all__ = 'PrettyFormat', 'pformat', 'pprint'
MYPY = False
Expand All @@ -20,14 +20,6 @@
PRETTY_KEY = '__prettier_formatted_value__'


def env_true(var_name, alt=None):
env = os.getenv(var_name, None)
if env:
return env.upper() in {'1', 'TRUE'}
else:
return alt


def fmt(v):
return {PRETTY_KEY: v}

Expand Down
87 changes: 87 additions & 0 deletions devtools/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,98 @@
import os
import sys

__all__ = ('isatty',)

MYPY = False
if MYPY:
from typing import Optional


def isatty(stream=None):
stream = stream or sys.stdout
try:
return stream.isatty()
except Exception:
return False


def env_true(var_name: str, alt: 'Optional[bool]' = None) -> 'Optional[bool]':
env = os.getenv(var_name, None)
if env:
return env.upper() in {'1', 'TRUE'}
else:
return alt


def env_bool(value: 'Optional[bool]', env_name: str, env_default: 'Optional[bool]') -> 'Optional[bool]':
if value is None:
return env_true(env_name, env_default)
else:
return value


def activate_win_color() -> bool: # pragma: no cover
"""
Activate ANSI support on windows consoles.

As of Windows 10, the windows conolse got some support for ANSI escape
sequences. Unfortunately it has to be enabled first using `SetConsoleMode`.
See: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

Code snippet source: https://bugs.python.org/msg291732
"""
import os
import msvcrt
import ctypes

from ctypes import wintypes

def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args

ERROR_INVALID_PARAMETER = 0x0057
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004

LPDWORD = ctypes.POINTER(wintypes.DWORD)

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
kernel32.GetConsoleMode.errcheck = _check_bool
kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD)
kernel32.SetConsoleMode.errcheck = _check_bool
kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)

def _set_conout_mode(new_mode, mask=0xFFFFFFFF):
# don't assume StandardOutput is a console.
# open CONOUT$ instead
fdout = os.open('CONOUT$', os.O_RDWR)
try:
hout = msvcrt.get_osfhandle(fdout)
old_mode = wintypes.DWORD()
kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
mode = (new_mode & mask) | (old_mode.value & ~mask)
kernel32.SetConsoleMode(hout, mode)
return old_mode.value
finally:
os.close(fdout)

mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING
try:
_set_conout_mode(mode, mask)
except WindowsError as e:
if e.winerror == ERROR_INVALID_PARAMETER:
return False
raise
return True


def use_highlight(highlight: 'Optional[bool]' = None, file_=None) -> bool:
highlight = env_bool(highlight, 'PY_DEVTOOLS_HIGHLIGHT', None)

if highlight is not None:
return highlight

if sys.platform == 'win32': # pragma: no cover
return isatty(file_) and activate_win_color()
return isatty(file_)
13 changes: 13 additions & 0 deletions tests/test_expr_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def foobar(a, b, c):
return a + b + c


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_simple():
a = [1, 2, 3]
v = debug.format(len(a))
Expand All @@ -23,6 +24,7 @@ def test_simple():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_subscription():
a = {1: 2}
v = debug.format(a[1])
Expand All @@ -33,6 +35,7 @@ def test_subscription():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_exotic_types():
aa = [1, 2, 3]
v = debug.format(
Expand Down Expand Up @@ -74,6 +77,7 @@ def test_exotic_types():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_newline():
v = debug.format(
foobar(1, 2, 3))
Expand All @@ -85,6 +89,7 @@ def test_newline():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_trailing_bracket():
v = debug.format(
foobar(1, 2, 3)
Expand All @@ -97,6 +102,7 @@ def test_trailing_bracket():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_multiline():
v = debug.format(
foobar(1,
Expand All @@ -111,6 +117,7 @@ def test_multiline():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_multiline_trailing_bracket():
v = debug.format(
foobar(1, 2, 3
Expand All @@ -123,6 +130,7 @@ def test_multiline_trailing_bracket():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
@pytest.mark.skipif(sys.version_info < (3, 6), reason='kwarg order is not guaranteed for 3.5')
def test_kwargs():
v = debug.format(
Expand All @@ -139,6 +147,7 @@ def test_kwargs():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
@pytest.mark.skipif(sys.version_info < (3, 6), reason='kwarg order is not guaranteed for 3.5')
def test_kwargs_multiline():
v = debug.format(
Expand All @@ -156,6 +165,7 @@ def test_kwargs_multiline():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_multiple_trailing_lines():
v = debug.format(
foobar(
Expand All @@ -168,6 +178,7 @@ def test_multiple_trailing_lines():
) == s


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_very_nested_last_statement():
def func():
return debug.format(
Expand All @@ -191,6 +202,7 @@ def func():
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_syntax_warning():
def func():
return debug.format(
Expand Down Expand Up @@ -241,6 +253,7 @@ def func():
assert 'func' in str(v)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_await():
async def foo():
return 1
Expand Down
12 changes: 12 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from devtools.ansi import strip_ansi


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_print(capsys):
a = 1
b = 2
Expand All @@ -23,6 +24,7 @@ def test_print(capsys):
assert stderr == ''


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_format():
a = b'i might bite'
b = "hello this is a test"
Expand All @@ -36,6 +38,7 @@ def test_format():
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_print_subprocess(tmpdir):
f = tmpdir.join('test.py')
f.write("""\
Expand Down Expand Up @@ -65,6 +68,7 @@ def test_func(v):
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_odd_path(mocker):
# all valid calls
mocked_relative_to = mocker.patch('pathlib.Path.relative_to')
Expand All @@ -73,6 +77,7 @@ def test_odd_path(mocker):
assert re.search(r"/.*?/test_main.py:\d{2,} test_odd_path\n 'test' \(str\) len=4", str(v)), v


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_small_call_frame():
debug_ = Debug(warnings=False, frame_context_length=2)
v = debug_.format(
Expand All @@ -88,6 +93,7 @@ def test_small_call_frame():
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_small_call_frame_warning():
debug_ = Debug(frame_context_length=2)
v = debug_.format(
Expand All @@ -105,6 +111,7 @@ def test_small_call_frame_warning():
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
@pytest.mark.skipif(sys.version_info < (3, 6), reason='kwarg order is not guaranteed for 3.5')
def test_kwargs():
a = 'variable'
Expand All @@ -118,6 +125,7 @@ def test_kwargs():
)


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_kwargs_orderless():
# for python3.5
a = 'variable'
Expand All @@ -130,6 +138,7 @@ def test_kwargs_orderless():
}


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_simple_vars():
v = debug.format('test', 1, 2)
s = re.sub(r':\d{2,}', ':<line no>', str(v))
Expand Down Expand Up @@ -199,6 +208,7 @@ def test_exec(capsys):
assert stderr == ''


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_colours():
v = debug.format(range(6))
s = re.sub(r':\d{2,}', ':<line no>', v.str(True))
Expand Down Expand Up @@ -232,6 +242,7 @@ def test_breakpoint(mocker):
assert mocked_set_trace.called


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
def test_starred_kwargs():
v = {'foo': 1, 'bar': 2}
v = debug.format(**v)
Expand All @@ -243,6 +254,7 @@ def test_starred_kwargs():
}


@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem')
@pytest.mark.skipif(sys.version_info < (3, 7), reason='error repr different before 3.7')
def test_pretty_error():
class BadPretty:
Expand Down
Loading