From 46c74ea6010f0fb629f20d8b7bb8fc9e574637c4 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 3 Sep 2021 14:14:44 +0100 Subject: [PATCH 1/4] return the args of debug() --- devtools/debug.py | 9 ++++++--- tests/test_main.py | 32 ++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/devtools/debug.py b/devtools/debug.py index 7be02a5..eac3af1 100644 --- a/devtools/debug.py +++ b/devtools/debug.py @@ -10,8 +10,7 @@ MYPY = False if MYPY: from types import FrameType - from typing import Generator, List, Optional - + from typing import Generator, List, Optional, Tuple, Any pformat = PrettyFormat( indent_step=int(os.getenv('PY_DEVTOOLS_INDENT', 4)), @@ -107,10 +106,14 @@ def __init__(self, *, warnings: 'Optional[bool]' = None, highlight: 'Optional[bo self._show_warnings = env_bool(warnings, 'PY_DEVTOOLS_WARNINGS', True) self._highlight = highlight - def __call__(self, *args, file_=None, flush_=True, **kwargs) -> None: + def __call__(self, *args, file_=None, flush_=True, **kwargs) -> 'Tuple[Any, ...]': d_out = self._process(args, kwargs) s = d_out.str(use_highlight(self._highlight, file_)) print(s, file=file_, flush=flush_) + if kwargs: + return *args, kwargs + else: + return args def format(self, *args, **kwargs) -> DebugOutput: return self._process(args, kwargs) diff --git a/tests/test_main.py b/tests/test_main.py index cb11c10..cb0fc08 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -8,12 +8,13 @@ from devtools import Debug, debug from devtools.ansi import strip_ansi +pytestmark = pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') + -@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem') def test_print(capsys): a = 1 b = 2 - debug(a, b) + result = debug(a, b) stdout, stderr = capsys.readouterr() print(stdout) assert re.sub(r':\d{2,}', ':', stdout) == ( @@ -22,9 +23,25 @@ def test_print(capsys): ' b: 2 (int)\n' ) assert stderr == '' + assert result == (1, 2) + + +def test_print_kwargs(capsys): + a = 1 + b = 2 + result = debug(a, b, foo=[1, 2, 3]) + stdout, stderr = capsys.readouterr() + print(stdout) + assert re.sub(r':\d{2,}', ':', stdout) == ( + 'tests/test_main.py: test_print_kwargs\n' + ' a: 1 (int)\n' + ' b: 2 (int)\n' + ' foo: [1, 2, 3] (list) len=3\n' + ) + assert stderr == '' + assert result == (1, 2, {'foo': [1, 2, 3]}) -@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" @@ -38,7 +55,6 @@ 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("""\ @@ -68,7 +84,6 @@ 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') @@ -92,7 +107,6 @@ def test_small_call_frame(): ) -@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem') def test_small_call_frame_warning(): debug_ = Debug() v = debug_.format( @@ -109,7 +123,6 @@ 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' @@ -123,7 +136,6 @@ def test_kwargs(): ) -@pytest.mark.xfail(sys.platform == 'win32', reason='yet unknown windows problem') def test_kwargs_orderless(): # for python3.5 a = 'variable' @@ -136,7 +148,6 @@ 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,}', ':', str(v)) @@ -206,7 +217,6 @@ 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,}', ':', v.str(True)) @@ -240,7 +250,6 @@ 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) @@ -252,7 +261,6 @@ 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: From 4a6d3c9a3b215b59e9a4d864b876c1deaf18dab4 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 3 Sep 2021 14:21:33 +0100 Subject: [PATCH 2/4] test for generators --- tests/test_main.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_main.py b/tests/test_main.py index cb0fc08..62a66fd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,6 @@ import re import sys +from collections.abc import Generator from pathlib import Path from subprocess import PIPE, run @@ -42,6 +43,28 @@ def test_print_kwargs(capsys): assert result == (1, 2, {'foo': [1, 2, 3]}) +def test_print_generator(capsys): + def gen(): + yield 1 + yield 2 + + result = debug(gen()) + stdout, stderr = capsys.readouterr() + print(stdout) + assert re.sub(r':\d{2,}', ':', stdout) == ( + 'tests/test_main.py: test_print_generator\n' + ' gen(): (\n' + ' 1,\n' + ' 2,\n' + ' ) (generator)\n' + ) + assert stderr == '' + assert len(result) == 1 + assert isinstance(result[0], Generator) + # the generator got evaluated and is now empty, that's correct currently + assert list(result[0]) == [] + + def test_format(): a = b'i might bite' b = "hello this is a test" From dad65bfd240b27de2623b5703b42819c9dd118a5 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 3 Sep 2021 15:04:58 +0100 Subject: [PATCH 3/4] adding docs for returning --- devtools/debug.py | 4 +++- docs/examples/return_args.py | 6 ++++++ docs/index.md | 2 ++ docs/usage.md | 16 ++++++++++++++++ tests/test_main.py | 22 ++++++++++++++-------- 5 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 docs/examples/return_args.py diff --git a/devtools/debug.py b/devtools/debug.py index eac3af1..aa9fe2c 100644 --- a/devtools/debug.py +++ b/devtools/debug.py @@ -106,12 +106,14 @@ def __init__(self, *, warnings: 'Optional[bool]' = None, highlight: 'Optional[bo self._show_warnings = env_bool(warnings, 'PY_DEVTOOLS_WARNINGS', True) self._highlight = highlight - def __call__(self, *args, file_=None, flush_=True, **kwargs) -> 'Tuple[Any, ...]': + def __call__(self, *args, file_=None, flush_=True, **kwargs) -> 'Any': d_out = self._process(args, kwargs) s = d_out.str(use_highlight(self._highlight, file_)) print(s, file=file_, flush=flush_) if kwargs: return *args, kwargs + elif len(args) == 1: + return args[0] else: return args diff --git a/docs/examples/return_args.py b/docs/examples/return_args.py new file mode 100644 index 0000000..2098507 --- /dev/null +++ b/docs/examples/return_args.py @@ -0,0 +1,6 @@ +from devtools import debug + +assert debug('foo') == 'foo' +assert debug('foo', 'bar') == ('foo', 'bar') +assert debug('foo', 'bar', spam=123) == ('foo', 'bar', {'spam': 123}) +assert debug(spam=123) == ({'spam': 123},) diff --git a/docs/index.md b/docs/index.md index eb1778d..5098d6a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,3 +15,5 @@ ``` {!examples/example.html!} + +Python devtools can do much more, see [Usage](./usage.md) for examples. diff --git a/docs/usage.md b/docs/usage.md index 90c1a74..ad92c16 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -26,6 +26,22 @@ A more complex example of `debug` shows more of what it can do. {!examples/complex.html!} +### Returning the arguments + +`debug` will return the arguments passed to it meaning you can insert `debug(...)` into code. + +The returned arguments work as follows: + +* if one non-keyword argument is passed to `debug()`, it is returned as-is +* if multiple arguments are passed to `debug()`, they are returned as a tuple +* if keyword arguments are passed to `debug()`, the `kwargs` dictionary is added to the returned tuple + +```py +{!examples/return_args.py!} +``` + +{!examples/return_args.html!} + ## Other debug tools The debug namespace includes a number of other useful functions: diff --git a/tests/test_main.py b/tests/test_main.py index 62a66fd..9b51b72 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -44,25 +44,22 @@ def test_print_kwargs(capsys): def test_print_generator(capsys): - def gen(): - yield 1 - yield 2 + gen = (i for i in [1, 2]) - result = debug(gen()) + result = debug(gen) stdout, stderr = capsys.readouterr() print(stdout) assert re.sub(r':\d{2,}', ':', stdout) == ( 'tests/test_main.py: test_print_generator\n' - ' gen(): (\n' + ' gen: (\n' ' 1,\n' ' 2,\n' ' ) (generator)\n' ) assert stderr == '' - assert len(result) == 1 - assert isinstance(result[0], Generator) + assert isinstance(result, Generator) # the generator got evaluated and is now empty, that's correct currently - assert list(result[0]) == [] + assert list(result) == [] def test_format(): @@ -310,3 +307,12 @@ def test_multiple_debugs(): 'tests/test_main.py: test_multiple_debugs\n' ' [i * 2 for i in range(2)]: [0, 2] (list) len=2' ) + + +def test_return_args(capsys): + assert debug('foo') == 'foo' + assert debug('foo', 'bar') == ('foo', 'bar') + assert debug('foo', 'bar', spam=123) == ('foo', 'bar', {'spam': 123}) + assert debug(spam=123) == ({'spam': 123},) + stdout, stderr = capsys.readouterr() + print(stdout) From ad633a344fff4046e4adb5db8bf2afc84a7e611c Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 3 Sep 2021 15:10:40 +0100 Subject: [PATCH 4/4] linting --- devtools/debug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devtools/debug.py b/devtools/debug.py index aa9fe2c..8de6c69 100644 --- a/devtools/debug.py +++ b/devtools/debug.py @@ -10,7 +10,7 @@ MYPY = False if MYPY: from types import FrameType - from typing import Generator, List, Optional, Tuple, Any + from typing import Any, Generator, List, Optional pformat = PrettyFormat( indent_step=int(os.getenv('PY_DEVTOOLS_INDENT', 4)), @@ -111,7 +111,7 @@ def __call__(self, *args, file_=None, flush_=True, **kwargs) -> 'Any': s = d_out.str(use_highlight(self._highlight, file_)) print(s, file=file_, flush=flush_) if kwargs: - return *args, kwargs + return (*args, kwargs) elif len(args) == 1: return args[0] else: