Giter Club home page Giter Club logo

python-devtools's Introduction

python devtools

CI Coverage pypi versions license

Python's missing debug print command and other development tools.

For more information, see documentation.

Install

Just

pip install devtools

If you've got python 3.7+ and pip installed, you're good to go.

Usage

from devtools import debug

whatever = [1, 2, 3]
debug(whatever)

Outputs:

test.py:4 <module>:
    whatever: [1, 2, 3] (list)

That's only the tip of the iceberg, for example:

import numpy as np

data = {
    'foo': np.array(range(20)),
    'bar': {'apple', 'banana', 'carrot', 'grapefruit'},
    'spam': [{'a': i, 'b': (i for i in range(3))} for i in range(3)],
    'sentence': 'this is just a boring sentence.\n' * 4
}

debug(data)

outputs:

python-devtools demo

Usage without Import

devtools can be used without from devtools import debug if you add debug into __builtins__ in sitecustomize.py.

For instructions on adding debug to __builtins__, see the installation docs.

python-devtools's People

Contributors

0xsirsaif avatar alexmojaki avatar aliereno avatar alwxsin avatar banteg avatar bswck avatar cielquan avatar dependabot[bot] avatar pyup-bot avatar samuelcolvin avatar staticf0x avatar the-vty avatar tomhamiltonstubber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-devtools's Issues

print fails on windows

On windows I encounter the problem that the colorful output looks like this:

�[35mtester.py�[0m:�[32m3�[0m �[32;3m<module>�[0m
    'hi'�[2m (str) len=2�[0m

After some research I found the reason. Windows added support for ACSII escapes but they have to be activated.

I also found a solution I think. I will test it a bit more and would than create a pull request.

EDIT: As a workaround one can disable colors with the environment variable PY_DEVTOOLS_HIGHLIGHT=false

error with kwargs usage

tests/test_utils.py:110: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
atoolbox/utils.py:79: in parse_request_query
    debug(**data)
env/lib/python3.6/site-packages/devtools/debug.py:112: in __call__
    d_out = self._process(args, kwargs, r'debug *\(')
env/lib/python3.6/site-packages/devtools/debug.py:155: in _process
    arguments = list(self._process_args(func_ast, code_lines, args, kwargs))
env/lib/python3.6/site-packages/devtools/debug.py:178: in _process_args
    arg_offsets = list(self._get_offsets(func_ast))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

func_ast = <_ast.Call object at 0x7f3a4bb9b240>

    @staticmethod
    def _get_offsets(func_ast):
        for arg in func_ast.args:
            start_line, start_col = arg.lineno - 2, arg.col_offset - 1
    
            # horrible hack for http://bugs.python.org/issue31241
            if isinstance(arg, (ast.ListComp, ast.GeneratorExp)):
                start_col -= 1
            yield start_line, start_col
        for kw in func_ast.keywords:
>           yield kw.value.lineno - 2, kw.value.col_offset - len(kw.arg) - 2
E           TypeError: object of type 'NoneType' has no len()

env/lib/python3.6/site-packages/devtools/debug.py:272: TypeError

result of using debug(**data)

Option to include stack trace in `debug()` output?

Today I was trying to debug an issue where a function of mine was unexpectedly being called from two places. I was using debug() within the function, but it only reports the file/line number/function that it was directly invoked from, which in this case I knew. I wanted to know where this function was being called from, so I looked for a way to get a full stack trace by doing something like debug(trace=True). As far as I can tell, this option doesn't exist yet.

Is there interest in adding such an option? If so, I could probably try to make a PR in the near-ish future.

pretty print error

    def _format_tuples(self, value: tuple, value_repr: str, indent_current: int, indent_new: int):
        fields = getattr(value, '_fields', None)
        if fields:
            # named tuple
            self._stream.write(value.__class__.__name__ + '(\n')
            for field, v in zip(fields, value):
                self._stream.write(indent_new * self._c)
>               self._stream.write(field)
E               TypeError: string argument expected, got 'tuple'

../code/debug-tools/devtools/prettier.py:106: TypeError

when printing call_args from mock

error when using with django

File "/home/samuel/code/debug-tools/devtools/debug.py" in __call__
  117.         d_out = self._process(args, kwargs, r'debug *\(')

File "/home/samuel/code/debug-tools/devtools/debug.py" in _process
  155.             func_ast, code_lines, lineno = self._parse_code(call_frame, func_regex, filename)

File "/home/samuel/code/debug-tools/devtools/debug.py" in _parse_code
  236.                     func_ast = self._wrap_parse(code, filename)

File "/home/samuel/code/debug-tools/devtools/debug.py" in _wrap_parse
  262.         return ast.parse(code, filename=filename).body[0].body[0].value

Exception Type: AttributeError at /signin-with/
Exception Value: 'Import' object has no attribute 'value'

error rendering content

debug(self.max_burst_jobs, self.jobs_complete + self.jobs_retried + self.jobs_failed, len(self.tasks))

goes wrong.

Instructions for using without import fail on Python 3.10

https://python-devtools.helpmanual.io/usage/#usage-without-import

Suggests the following snippet:

    __builtins__['debug'] = debug

In python 3.10 (at least, only version tried), referencing builtins directly gives "TypeError: 'module' object is not subscriptable"

builtins.dict, however, is subscriptable.

That figuring out where sitecustomize.py (or usercustomize.py) actually can be put so it gets properly executed on startup on a multi-python and/or brew (linuxbrew) system is an absolute nightmare is a different issue and not one I'd expect python-devtools to address particularly.

how __pretty__ works

I'll describe fully how the __pretty__ unofficial dunder method is used by devtools on the remote off chance that @willmcgugan might adopt a similar thing in rich.

This approach seems to work but is completely unofficial and wasn't developed in collaboration with others, so might require changes. I'd be very happy to support a changes to this, particularly if it can be somewhat backwards compatible.

The Idea

Provide a way for objects to describe how they should be displayed in "rich" (no pun intended) contexts, without moving all the logic of pretty printing into the object.

The implementation

objects which want to be pretty printed include a __pretty__ method with takes as arguments:

  • fmt a callable which takes one argument (usage described below)
  • extra **kwargs in case we want to pass more things to the method later

It yields (or returns a list of) any of the following:

  • string, in which case that string is printed on the current line and the current indentation
  • int, in which case:
    • 1 means increase the indent by one level (4 spaces, 2 spaces, 1 tab, whatever you want it to mean)
    • -1 means decrease the indent by one level
    • 0 means new line, same indent
    • I think other numbers can be used to increase or decrease the indent more (e.g. 2 -> increase by two levels), but I can't remember if this is actually implemented, it's generally not needd
  • the result of fmt(<whatever>) in which case the application calling __pretty__ can decide how to render the argument to fmt(). This is useful for recursive pretty printing - e.g. a custom type of list doesn't need to know how to render all it's values.

An example

Here' is the __pretty__ method from the reusable Representation method in pydantic with more comments:

    def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]:
        # show the name of the class together with an opening bracket so it looks a bit like a instance definition:
        yield self.__repr_name__() + '('
        # new line and increase the indent
        yield 1
        for name, value in self.__repr_args__():
            # if this class wants to show name value pairs, show '<name>=`
            if name is not None:
                yield name + '='
            # render the value, devtools (or any tool calling this) can decide how to display the value
            yield fmt(value)
            # add a comma to the end of the line to keep it looking like an instance definition
            yield ','
            # new line without a change of indent
            yield 0
        # decrease indent, no extra new line
        yield -1
        # closing bracket to keep it looking a bit like python
        yield ')'

with self.__repr_name__() --> 'TheName' and self.__repr_args__() --> [('x', 1), ('y', 2), (None, 3)], it might display

TheName(
    x=1,
    y=2
    3,
)

[Feature Request] Can debug print output to a configured logger?

debug is a wonderful tool and saved me a lot of time for debugging. And In my projects, I used to configure my owner logger for recording some running information. And now I use both debug() and logger.debug().

However, when they are used together, the terminal looks like this:

image

But another tool I'm using called Hydra can also format some information into its configured logger, and the result looks like this:
image

So I assume if debug can offer similar option, it will be amazing.

Can your team kindly consider to support this feature?

Appreciated your maintenance. Thanks!

A failure occurred in check()

Python has been updated to 3.9.0 . I think this error is because of that, but I am not sure
OS : Arch Linux
I downloaded the AUR package and called the makepkg -si and this is what I get

==> Making package: python-devtools 0.6.1-1 (Sunday 06 December 2020 02:18:39 PM)
==> Checking runtime dependencies...
==> Checking buildtime dependencies...
==> Retrieving sources...
  -> Found python-devtools-0.6.1.tar.gz
  -> Found LICENSE
==> Validating source files with sha256sums...
    python-devtools-0.6.1.tar.gz ... Passed
    LICENSE ... Passed
==> Validating source files with b2sums...
    python-devtools-0.6.1.tar.gz ... Passed
    LICENSE ... Passed
==> Extracting sources...
  -> Extracting python-devtools-0.6.1.tar.gz with bsdtar
==> Removing existing $pkgdir/ directory...
==> Starting build()...
running build
running build_py
==> Starting check()...
running pytest
running egg_info
writing devtools.egg-info/PKG-INFO
writing dependency_links to devtools.egg-info/dependency_links.txt
writing requirements to devtools.egg-info/requires.txt
writing top-level names to devtools.egg-info/top_level.txt
reading manifest file 'devtools.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'devtools.egg-info/SOURCES.txt'
running build_ext
============================================================== test session starts ==============================================================
platform linux -- Python 3.9.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /home/joel/build/python-devtools/src/python-devtools-0.6.1, configfile: setup.cfg, testpaths: tests
plugins: mock-2.0.0
collected 96 items                                                                                                                              

tests/test_ansi.py ............                                                                                                           [ 12%]
tests/test_custom_pretty.py .....s                                                                                                        [ 18%]
tests/test_expr_render.py ................                                                                                                [ 35%]
tests/test_main.py ....................s.                                                                                                 [ 58%]
tests/test_prettier.py ..F...............s.ss.....s.                                                                                      [ 88%]
tests/test_timer.py ......                                                                                                                [ 94%]
tests/test_utils.py ....s                                                                                                                 [100%]

=================================================================== FAILURES ====================================================================
_________________________________________________________________ test_colours __________________________________________________________________

    def test_colours():
        v = pformat({1: 2, 3: 4}, highlight=True)
>       assert v.startswith('\x1b'), repr(v)
E       AssertionError: '{\n    1: 2,\n    3: 4,\n}'
E       assert False
E        +  where False = <built-in method startswith of str object at 0x7fbabf33ba80>('\x1b')
E        +    where <built-in method startswith of str object at 0x7fbabf33ba80> = '{\n    1: 2,\n    3: 4,\n}'.startswith

tests/test_prettier.py:52: AssertionError
============================================================ short test summary info ============================================================
FAILED tests/test_prettier.py::test_colours - AssertionError: '{\n    1: 2,\n    3: 4,\n}'
==================================================== 1 failed, 88 passed, 7 skipped in 0.31s ====================================================
==> ERROR: A failure occurred in check().
    Aborting...

       ```

Is it ok that input args are printed twice?

I get my "debugged" vars printed both by debug utils and as a return val, so in case of dataclasses or big dicts (for which it fits the most) it eats all screen space.

Am I missing something or it works as expected?

Python 3.10.2 (main, Jan 29 2022, 02:55:36) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from devtools import debug
>>> debug({'a': 1, 'b': 2})
<stdin>:1 <module> (no code context for debug call, code inspection impossible)
    {
        'a': 1,
        'b': 2,
    } (dict) len=2
{'a': 1, 'b': 2}

Option to disable when not developing?

I read the docs but I can’t see any options to turn off the module once development has been completed. I find all the features very useful, but now my code has a lot of these debug codes that I would prefer to keep but not output when I’m ready to put them in production. Is there a way? Thanks!

error debug printing a dict

Error wen calling debug(dict(ctx)) in a harrier extension.

    !!! error pretty printing value: TypeError("descriptor 'items' of 'dict' object needs an argument")

on v0.6a1

[Bug] import statement in method: 'error parsing code, unable to find "debug" function statement'

Bug

When the from devtools import debug statement occurs within a method (as opposed to at toplevel of the module), an "error parsing code" message is printed.

Min repro:

The following works as expected:

# tmp.py
from devtools import debug
def foo():
    debug(123)
foo()
$ python tmp.py
tmp.py:4 foo
    123 (int)

But if you put the import statement inside the method, there is an error:

# tmp2.py
def foo():
    from devtools import debug
    debug(123)
foo()
$ python tmp2.py
tmp2.py:4 foo (error parsing code, unable to find "debug" function statement)
    123 (int)

error in debug

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./run.py", line 57, in <module>
    if __name__ == '__main__':
  File "./run.py", line 52, in main
    # transport = logging_client.remote.get_transport()
  File "/home/samuel/code/nosht/env/lib/python3.6/site-packages/devtools/debug.py", line 112, in __call__
    d_out = self._process(args, kwargs, r'debug *\(')
  File "/home/samuel/code/nosht/env/lib/python3.6/site-packages/devtools/debug.py", line 153, in _process
    func_ast, code_lines, lineno = self._parse_code(call_frame, func_regex, filename)
  File "/home/samuel/code/nosht/env/lib/python3.6/site-packages/devtools/debug.py", line 223, in _parse_code
    func_ast = self._wrap_parse(code, filename)
  File "/home/samuel/code/nosht/env/lib/python3.6/site-packages/devtools/debug.py", line 260, in _wrap_parse
    return ast.parse(code, filename=filename).body[0].body[0].value
AttributeError: 'ImportFrom' object has no attribute 'value'

sqlalchemy 2.0 formatting is broken. possible fix.

Hi Samuel,

Thanks for the wonderful tool!

After upgrading to sqlalchemy 2.0 debug() stopped working. Here is my fix.

diff --git a/devtools/utils.py b/devtools/utils.py
index 994027e..2da806a 100644
--- a/devtools/utils.py
+++ b/devtools/utils.py
@@ -149,6 +149,13 @@ class DataClassType(metaclass=MetaDataClassType):

 class MetaSQLAlchemyClassType(type):
     def __instancecheck__(self, instance: 'Any') -> bool:
+        try:
+            from sqlalchemy.orm import DeclarativeBase  # type: ignore
+        except ImportError:
+            ...
+        else:
+            return isinstance(instance, DeclarativeBase)
+
         try:
             from sqlalchemy.ext.declarative import DeclarativeMeta  # type: ignore
         except ImportError:

error with python 3.7

Traceback (most recent call last):
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/aiosmtpd/smtp.py", line 315, in _handle_client
    await method(arg)
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/aiosmtpd/smtp.py", line 380, in smtp_NOOP
    status = await self._call_handler_hook('NOOP', arg)
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/aiosmtpd/smtp.py", line 122, in _call_handler_hook
    status = await hook(self, self.session, self.envelope, *args)
  File "./run.py", line 14, in handle_NOOP
    print('Message data:\n')
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/devtools/debug.py", line 112, in __call__
    d_out = self._process(args, kwargs, r'debug *\(')
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/devtools/debug.py", line 153, in _process
    func_ast, code_lines, lineno = self._parse_code(call_frame, func_regex, filename)
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/devtools/debug.py", line 223, in _parse_code
    func_ast = self._wrap_parse(code, filename)
  File "/home/samuel/code/email-forward/env/lib/python3.7/site-packages/devtools/debug.py", line 260, in _wrap_parse
    return ast.parse(code, filename=filename).body[0].body[0].value
AttributeError: 'Import' object has no attribute 'value'

return the arguments to debug()

so i can do

foobar = ...
next_thing = debug(a_method(foobar))

and display the output of a_method(foobar) while also assigning it to next_thing

Truncate long strings etc?

I noticed in #16 that you intended to strip the middle of massive outputs, but I can't seem to get this to happen (for very long strings). Is it possible to configure this somehow?

debug reports indentation error inside of if-else blocks within __init__

I noticed this strange behavior when using debug to debug some if-else blocks in my code. Here is a small script to recreate the bug.

# script.py
from devtools import debug

class Test:
    def __init__(self, flag: bool):
        if flag:
            debug('Flag was true')
        else:
            debug('Flag was false')

# instantiate this class with True flag
test = Test(flag=True)

Output of python script.py:

script.py:7 __init__ (error parsing code, IndentationError: unindent does not match any outer indentation level (script.py, line 3))
    'Flag was true' (str) len=13

There is no indentation error in the code, and furthermore, I notice that adding something benign after the debug statement makes this go away. For example, if instead script.py looks like this, there is no issue:

# script2.py
from devtools import debug


class Test:
    def __init__(self, flag):
        if flag:
            debug('Flag was true')
            print('This helps debug out for some reason.')
        else:
            debug('Flag was false')

test = Test(True)

Output of python script2.py (which I expect to happen normally):

script2.py:7 __init__
    'Flag was true' (str) len=13
This helps debug out for some reason.

Environment info: I tested this by creating a new conda environment with python 3.7.

$ conda --version
conda 4.10.1

$ python --version
python 3.7.10

$ pip list
Package    Version
---------- -------------------
certifi    2021.5.30
devtools   0.6.1
pip        21.1.3
setuptools 52.0.0.post20210125
wheel      0.36.2

`inspect` / `vars` function

(I thought this was already an issue, but I checked and it seems it only existed in my head)

I want a way to pretty-print pertinent attributes of an object, like vars(...) but more intelligent - in particular it should try to inspect __slots__ if __dict__ is not available.

This might be as simple as sugar around the following POC, but there are probably edge cases I haven't thought about.

I guess we should expose this via debug.inspect().

Rough demo code of what I mean
from __future__ import annotations
from devtools import debug

ignored_attributes = {
    '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
    '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
    '__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
    '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
    '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '__slots__',
}


class DebugInspect:
    __slots__ = ('obj',)

    def __init__(self, obj: Any):
        self.obj = obj

    def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]:
        yield self.obj.__class__.__name__ + '('
        yield 1
        # use __dict__ if possible to maintain order, also should be slightly faster
        obj_dict = getattr(self.obj, '__dict__', None)
        if obj_dict is not None:
            for name, value in obj_dict.items():
                yield name + '='
                yield fmt(value)
                yield ','
                yield 0
        else:
            for name in dir(self.obj):
                if name not in ignored_attributes:
                    yield name + '='
                    yield fmt(getattr(self.obj, name))
                    yield ','
                    yield 0
        yield -1
        # closing bracket to keep it looking a bit like python
        yield ')'


class Foo:
    def __init__(self):
        self.x = 1
        self.y = 2
        self._private = 3
        self.__custom_dunder__ = 4


class Bar:
    __slots__ = 'x', 'y', '_private', '__custom_dunder__'

    def __init__(self):
        self.x = 1
        self.y = 2
        self._private = 3
        self.__custom_dunder__ = 4


f = Foo()
debug(DebugInspect(f))

b = Bar()
debug(DebugInspect(b))

prints:

foobar.py:61 <module>
    DebugInspect(f): Foo(
        x=1,
        y=2,
        _private=3,
        __custom_dunder__=4,
    ) (DebugInspect)
foobar.py:64 <module>
    DebugInspect(b): Bar(
        __custom_dunder__=4,
        _private=3,
        x=1,
        y=2,
    ) (DebugInspect)

FEATURE: Add 'when: bool' argument to debug

IGNORE: I guess using a conditional would be just as good.

Oftentimes one would want to use debug inside a loop but only when a particular condition is true. Maybe you could add a 'when: bool' argument that limits the debug output to only if 'when' is True. That would make it much more powerful.

0.11.0: Issue with several tests due to string matching

Hi! I'm packaging this project for Arch Linux and ran into a few problems when trying to run tests for 0.11.0.

============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.4.0, pluggy-1.0.0 -- /usr/bin/python
cachedir: .pytest_cache
rootdir: /build/python-devtools/src/devtools-0.11.0
configfile: pyproject.toml
testpaths: tests
plugins: mock-3.10.0, pretty-1.2.0
collecting ... collected 117 items

tests/test_ansi.py::test_colours PASSED                                  [  0%]
tests/test_ansi.py::test_no_reset PASSED                                 [  1%]
tests/test_ansi.py::test_combine_styles PASSED                           [  2%]
tests/test_ansi.py::test_no_styles PASSED                                [  3%]
tests/test_ansi.py::test_style_str PASSED                                [  4%]
tests/test_ansi.py::test_non_str_input PASSED                            [  5%]
tests/test_ansi.py::test_invalid_style_str PASSED                        [  5%]
tests/test_ansi.py::test_print_not_tty PASSED                            [  6%]
tests/test_ansi.py::test_print_is_tty PASSED                             [  7%]
tests/test_ansi.py::test_print_tty_error PASSED                          [  8%]
tests/test_ansi.py::test_get_styles PASSED                               [  9%]
tests/test_ansi.py::test_repr_str PASSED                                 [ 10%]
tests/test_custom_pretty.py::test_simple PASSED                          [ 11%]
tests/test_custom_pretty.py::test_skip PASSED                            [ 11%]
tests/test_custom_pretty.py::test_yield_other PASSED                     [ 12%]
tests/test_custom_pretty.py::test_pretty_not_func PASSED                 [ 13%]
tests/test_custom_pretty.py::test_pretty_class PASSED                    [ 14%]
tests/test_custom_pretty.py::test_pydantic_pretty SKIPPED (numpy not
installed)                                                               [ 15%]
tests/test_expr_render.py::test_simple PASSED                            [ 16%]
tests/test_expr_render.py::test_subscription PASSED                      [ 17%]
tests/test_expr_render.py::test_exotic_types PASSED                      [ 17%]
tests/test_expr_render.py::test_newline PASSED                           [ 18%]
tests/test_expr_render.py::test_trailing_bracket PASSED                  [ 19%]
tests/test_expr_render.py::test_multiline PASSED                         [ 20%]
tests/test_expr_render.py::test_multiline_trailing_bracket PASSED        [ 21%]
tests/test_expr_render.py::test_kwargs PASSED                            [ 22%]
tests/test_expr_render.py::test_kwargs_multiline PASSED                  [ 23%]
tests/test_expr_render.py::test_multiple_trailing_lines PASSED           [ 23%]
tests/test_expr_render.py::test_very_nested_last_statement PASSED        [ 24%]
tests/test_expr_render.py::test_syntax_warning PASSED                    [ 25%]
tests/test_expr_render.py::test_no_syntax_warning PASSED                 [ 26%]
tests/test_expr_render.py::test_await PASSED                             [ 27%]
tests/test_expr_render.py::test_other_debug_arg PASSED                   [ 28%]
tests/test_expr_render.py::test_other_debug_arg_not_literal PASSED       [ 29%]
tests/test_expr_render.py::test_executing_failure PASSED                 [ 29%]
tests/test_expr_render.py::test_format_inside_error PASSED               [ 30%]
tests/test_insert_assert.py::test_insert_assert FAILED                   [ 31%] [ 31%]
tests/test_insert_assert.py:22 test_insert_assert - assert "def test…
tests/test_insert_assert.py::test_insert_assert_no_pretty FAILED         [ 32%] [ 32%]
tests/test_insert_assert.py:40 test_insert_assert_no_pretty - assert…
tests/test_insert_assert.py::test_insert_assert_print PASSED             [ 33%]
tests/test_insert_assert.py::test_insert_assert_fail PASSED              [ 34%]
tests/test_insert_assert.py::test_deep FAILED                            [ 35%] [ 35%]
tests/test_insert_assert.py:80 test_deep - assert "def test_deep(ins…
tests/test_insert_assert.py::test_enum FAILED                            [ 35%] [ 35%]
tests/test_insert_assert.py:103 test_enum - AssertionError: assert '…
tests/test_insert_assert.py::test_insert_assert_black FAILED             [ 36%] [ 36%]
tests/test_insert_assert.py:126 test_insert_assert_black - assert "'…
tests/test_insert_assert.py::test_insert_assert_repeat PASSED            [ 37%]
tests/test_main.py::test_print PASSED                                    [ 38%]
tests/test_main.py::test_print_kwargs PASSED                             [ 39%]
tests/test_main.py::test_print_generator PASSED                          [ 40%]
tests/test_main.py::test_format PASSED                                   [ 41%]
tests/test_main.py::test_print_subprocess PASSED                         [ 41%]
tests/test_main.py::test_odd_path PASSED                                 [ 42%]
tests/test_main.py::test_small_call_frame PASSED                         [ 43%]
tests/test_main.py::test_small_call_frame_warning PASSED                 [ 44%]
tests/test_main.py::test_kwargs PASSED                                   [ 45%]
tests/test_main.py::test_kwargs_orderless PASSED                         [ 46%]
tests/test_main.py::test_simple_vars PASSED                              [ 47%]
tests/test_main.py::test_attributes PASSED                               [ 47%]
tests/test_main.py::test_eval PASSED                                     [ 48%]
tests/test_main.py::test_warnings_disabled PASSED                        [ 49%]
tests/test_main.py::test_eval_kwargs PASSED                              [ 50%]
tests/test_main.py::test_exec PASSED                                     [ 51%]
tests/test_main.py::test_colours PASSED                                  [ 52%]
tests/test_main.py::test_colours_warnings PASSED                         [ 52%]
tests/test_main.py::test_inspect_error PASSED                            [ 53%]
tests/test_main.py::test_breakpoint PASSED                               [ 54%]
tests/test_main.py::test_starred_kwargs PASSED                           [ 55%]
tests/test_main.py::test_pretty_error PASSED                             [ 56%]
tests/test_main.py::test_multiple_debugs PASSED                          [ 57%]
tests/test_main.py::test_return_args PASSED                              [ 58%]
tests/test_prettier.py::test_dict PASSED                                 [ 58%]
tests/test_prettier.py::test_print PASSED                                [ 59%]
tests/test_prettier.py::test_colours PASSED                              [ 60%]
tests/test_prettier.py::test_list PASSED                                 [ 61%]
tests/test_prettier.py::test_set PASSED                                  [ 62%]
tests/test_prettier.py::test_tuple PASSED                                [ 63%]
tests/test_prettier.py::test_generator PASSED                            [ 64%]
tests/test_prettier.py::test_named_tuple PASSED                          [ 64%]
tests/test_prettier.py::test_generator_no_yield PASSED                   [ 65%]
tests/test_prettier.py::test_str PASSED                                  [ 66%]
tests/test_prettier.py::test_str_repr PASSED                             [ 67%]
tests/test_prettier.py::test_bytes PASSED                                [ 68%]
tests/test_prettier.py::test_short_bytes PASSED                          [ 69%]
tests/test_prettier.py::test_bytearray PASSED                            [ 70%]
tests/test_prettier.py::test_bytearray_short PASSED                      [ 70%]
tests/test_prettier.py::test_map PASSED                                  [ 71%]
tests/test_prettier.py::test_filter PASSED                               [ 72%]
tests/test_prettier.py::test_counter PASSED                              [ 73%]
tests/test_prettier.py::test_dataclass PASSED                            [ 74%]
tests/test_prettier.py::test_nested_dataclasses PASSED                   [ 75%]
tests/test_prettier.py::test_indent_numpy PASSED                         [ 76%]
tests/test_prettier.py::test_indent_numpy_short PASSED                   [ 76%]
tests/test_prettier.py::test_ordered_dict PASSED                         [ 77%]
tests/test_prettier.py::test_frozenset PASSED                            [ 78%]
tests/test_prettier.py::test_deep_objects PASSED                         [ 79%]
tests/test_prettier.py::test_call_args PASSED                            [ 80%]
tests/test_prettier.py::test_multidict PASSED                            [ 81%]
tests/test_prettier.py::test_cimultidict PASSED                          [ 82%]
tests/test_prettier.py::test_os_environ PASSED                           [ 82%]
tests/test_prettier.py::test_dir PASSED                                  [ 83%]
tests/test_prettier.py::test_instance_dict PASSED                        [ 84%]
tests/test_prettier.py::test_class_dict PASSED                           [ 85%]
tests/test_prettier.py::test_dictlike PASSED                             [ 86%]
tests/test_prettier.py::test_asyncpg_record PASSED                       [ 87%]
tests/test_prettier.py::test_dict_type PASSED                            [ 88%]
tests/test_prettier.py::test_sqlalchemy_object PASSED                    [ 88%]
tests/test_prettier.py::test_ast_expr PASSED                             [ 89%]
tests/test_prettier.py::test_ast_module PASSED                           [ 90%]
tests/test_timer.py::test_simple PASSED                                  [ 91%]
tests/test_timer.py::test_multiple PASSED                                [ 92%]
tests/test_timer.py::test_unfinished PASSED                              [ 93%]
tests/test_timer.py::test_multiple_not_verbose PASSED                    [ 94%]
tests/test_timer.py::test_unfinished_summary PASSED                      [ 94%]
tests/test_timer.py::test_summary_not_started PASSED                     [ 95%]
tests/test_utils.py::test_env_true PASSED                                [ 96%]
tests/test_utils.py::test_env_bool PASSED                                [ 97%]
tests/test_utils.py::test_use_highlight_manually_set PASSED              [ 98%]
tests/test_utils.py::test_use_highlight_auto_not_win PASSED              [ 99%]
tests/test_utils.py::test_use_highlight_auto_win SKIPPED (not windows
os)                                                                      [100%]

=================================== FAILURES ===================================
______________________________ test_insert_assert ______________________________

pytester_pretty = <pytest_pretty.PytesterWrapper object at 0x7f73cc6ab410>

    def test_insert_assert(pytester_pretty):
        os.environ.pop('CI', None)
        pytester_pretty.makeconftest(config)
        test_file = pytester_pretty.makepyfile(default_test)
        result = pytester_pretty.runpytest()
        result.assert_outcomes(passed=2)
        # print(result.outlines)
>       assert test_file.read_text() == (
            'def test_ok():\n'
            '    assert 1 + 2 == 3\n'
            '\n'
            'def test_string_assert(insert_assert):\n'
            "    thing = 'foobar'\n"
            '    # insert_assert(thing)\n'
            '    assert thing == "foobar"'
        )
E       assert "def test_ok():\n    assert 1 + 2 == 3\n\ndef test_string_assert(insert_assert):\n    thing = 'foobar'\n    # insert_assert(thing)\n    assert thing == 'foobar'" == 'def test_ok():\n    assert 1 + 2 == 3\n\ndef test_string_assert(insert_assert):\n    thing = \'foobar\'\n    # insert_assert(thing)\n    a
ssert thing == "foobar"'
E           def test_ok():
E               assert 1 + 2 == 3
E
E           def test_string_assert(insert_assert):
E               thing = 'foobar'
E               # insert_assert(thing)
E         -     assert thing == "foobar"
E         ?                     ^      ^
E         +     assert thing == 'foobar'
E         ?                     ^      ^

/build/python-devtools/src/devtools-0.11.0/tests/test_insert_assert.py:30: AssertionError
----------------------------- Captured stdout call -----------------------------
============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.4.0, pluggy-1.0.0
rootdir: /tmp/pytest-of-builduser/pytest-0/test_insert_assert0
plugins: mock-3.10.0, pretty-1.2.0
collected 2 items

test_insert_assert.py ..i                                                [100%]

=============================== warnings summary ===============================
../../../../usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755
  /usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755: PytestAssertRewriteWarning: Module already imported so cannot be rewritten: devtools.pytest_plugin
    self.import_plugin(import_spec)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
Replaced 1 insert_assert() call in 1 file
Results (0.01s):
         2 passed
         1 warning
         1 insert assert
_________________________ test_insert_assert_no_pretty _________________________

pytester = <Pytester PosixPath('/tmp/pytest-of-builduser/pytest-0/test_insert_assert_no_pretty0')>

    def test_insert_assert_no_pretty(pytester):
        os.environ.pop('CI', None)
        pytester.makeconftest(config)
        test_file = pytester.makepyfile(default_test)
        result = pytester.runpytest('-p', 'no:pretty')
        result.assert_outcomes(passed=2)
>       assert test_file.read_text() == (
            'def test_ok():\n'
            '    assert 1 + 2 == 3\n'
            '\n'
            'def test_string_assert(insert_assert):\n'
            "    thing = 'foobar'\n"
            '    # insert_assert(thing)\n'
            '    assert thing == "foobar"'
        )
E       assert "def test_ok():\n    assert 1 + 2 == 3\n\ndef test_string_assert(insert_assert):\n    thing = 'foobar'\n    # insert_assert(thing)\n    assert thing == 'foobar'" == 'def test_ok():\n    assert 1 + 2 == 3\n\ndef test_string_assert(insert_assert):\n    thing = \'foobar\'\n    # insert_assert(thing)\n    a
ssert thing == "foobar"'
E           def test_ok():
E               assert 1 + 2 == 3
E
E           def test_string_assert(insert_assert):
E               thing = 'foobar'
E               # insert_assert(thing)
E         -     assert thing == "foobar"
E         ?                     ^      ^
E         +     assert thing == 'foobar'
E         ?                     ^      ^

/build/python-devtools/src/devtools-0.11.0/tests/test_insert_assert.py:47: AssertionError
----------------------------- Captured stdout call -----------------------------
============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.4.0, pluggy-1.0.0
rootdir: /tmp/pytest-of-builduser/pytest-0/test_insert_assert_no_pretty0
plugins: mock-3.10.0
collected 2 items

test_insert_assert_no_pretty.py ..i                                      [100%]

=============================== warnings summary ===============================
../../../../usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755
  /usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755: PytestAssertRewriteWarning: Module already imported so cannot be rewritten: devtools.pytest_plugin
    self.import_plugin(import_spec)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
Replaced 1 insert_assert() call in 1 file
================ 2 passed, 1 warning, 1 insert assert in 0.00s =================
__________________________________ test_deep ___________________________________

pytester_pretty = <pytest_pretty.PytesterWrapper object at 0x7f73cc6a9510>

    def test_deep(pytester_pretty):
        os.environ.pop('CI', None)
        pytester_pretty.makeconftest(config)
        # language=Python
        test_file = pytester_pretty.makepyfile(
            """
        def test_deep(insert_assert):
            insert_assert([{'a': i, 'b': 2 * 2} for i in range(3)])
        """
        )
        result = pytester_pretty.runpytest()
        result.assert_outcomes(passed=1)
>       assert test_file.read_text() == (
            'def test_deep(insert_assert):\n'
            "    # insert_assert([{'a': i, 'b': 2 * 2} for i in range(3)])\n"
            '    assert [{"a": i, "b": 2 * 2} for i in range(3)] == [\n'
            '        {"a": 0, "b": 4},\n'
            '        {"a": 1, "b": 4},\n'
            '        {"a": 2, "b": 4},\n'
            '    ]'
        )
E       assert "def test_deep(insert_assert):\n    # insert_assert([{'a': i, 'b': 2 * 2} for i in range(3)])\n    assert [{'a': i, 'b': 2 * 2} for i in range(3)] == [{'a': 0, 'b': 4}, {'a': 1, 'b': 4}, {'a': 2, 'b': 4}]" == 'def test_deep(insert_assert):\n    # insert_assert([{\'a\': i, \'b\': 2 * 2} for i in range(3)
])\n    assert [{"a": i, "b": 2 * 2} for i in range(3)] == [\n        {"a": 0, "b": 4},\n        {"a": 1, "b": 4},\n        {"a": 2, "b": 4},\n    ]'
E           def test_deep(insert_assert):
E               # insert_assert([{'a': i, 'b': 2 * 2} for i in range(3)])
E         +     assert [{'a': i, 'b': 2 * 2} for i in range(3)] == [{'a': 0, 'b': 4}, {'a': 1, 'b': 4}, {'a': 2, 'b': 4}]
E         -     assert [{"a": i, "b": 2 * 2} for i in range(3)] == [
E         -         {"a": 0, "b": 4},
E         -         {"a": 1, "b": 4},
E         -         {"a": 2, "b": 4},
E         -     ]

/build/python-devtools/src/devtools-0.11.0/tests/test_insert_assert.py:93: AssertionError
----------------------------- Captured stdout call -----------------------------
============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.4.0, pluggy-1.0.0
rootdir: /tmp/pytest-of-builduser/pytest-0/test_deep0
plugins: mock-3.10.0, pretty-1.2.0
collected 1 item

test_deep.py .i                                                          [100%]

=============================== warnings summary ===============================
../../../../usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755
  /usr/lib/python3.11/site-packages/_pytest/config/__init__.py:755: PytestAssertRewriteWarning: Module already imported so cannot be rewritten: devtools.pytest_plugin
    self.import_plugin(import_spec)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
Replaced 1 insert_assert() call in 1 file
Results (0.01s):
         1 passed
         1 warning
         1 insert assert
__________________________________ test_enum ___________________________________

pytester_pretty = <pytest_pretty.PytesterWrapper object at 0x7f73cc488290>
capsys = <_pytest.capture.CaptureFixture object at 0x7f73cc4885d0>

    def test_enum(pytester_pretty, capsys):
        os.environ.pop('CI', None)
        pytester_pretty.makeconftest(config)
        # language=Python
        pytester_pretty.makepyfile(
            """
    from enum import Enum

    class Foo(Enum):
        A = 1
        B = 2

    def test_deep(insert_assert):
        x = Foo.A
        insert_assert(x)
        """
        )
        result = pytester_pretty.runpytest('--insert-assert-print')
        result.assert_outcomes(passed=1)
        captured = capsys.readouterr()
>       assert '    assert x == Foo.A\n' in captured.out
E       AssertionError: assert '    assert x == Foo.A\n' in '============================= test session starts ==============================\nplatform linux -- Python 3.11.3, py...---------------\n\nPrinted 1 insert_assert() call in 1 file\nResults (0.01s):\n         1 passed\n         1 warning\n'
E        +  where '============================= test session starts ==============================\nplatform linux -- Python 3.11.3, py...---------------\n\nPrinted 1 insert_assert() call in 1 file\nResults (0.01s):\n         1 passed\n         1 warning\n' = CaptureResult(out='============================= test sess
ion starts ==============================\nplatform linux --...------\n\nPrinted 1 insert_assert() call in 1 file\nResults (0.01s):\n         1 passed\n         1 warning\n', err='').out

/build/python-devtools/src/devtools-0.11.0/tests/test_insert_assert.py:124: AssertionError
___________________________ test_insert_assert_black ___________________________

tmp_path = PosixPath('/tmp/pytest-of-builduser/pytest-0/test_insert_assert_black0')

    def test_insert_assert_black(tmp_path):
        old_wd = os.getcwd()
        try:
            os.chdir(tmp_path)
            (tmp_path / 'pyproject.toml').write_text(
                """\
    [tool.black]
    target-version = ["py39"]
    skip-string-normalization = true"""
            )
            load_black.cache_clear()
        finally:
            os.chdir(old_wd)

        f = load_black()
        # no string normalization
>       assert f("'foobar'") == "'foobar'\n"
E       assert "'foobar'" == "'foobar'\n"
E         - 'foobar'
E         ?         -
E         + 'foobar'

tests/test_insert_assert.py:143: AssertionError
                              Summary of Failures
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃               ┃               ┃  Function     ┃              ┃               ┃
┃  File         ┃  Function     ┃  Line         ┃  Error Line  ┃  Error        ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│  tests/test…  │  test_inser…  │  23           │  30          │  AssertionE…  │
│  tests/test…  │  test_inser…  │  41           │  47          │  AssertionE…  │
│  tests/test…  │  test_deep    │  81           │  93          │  AssertionE…  │
│  tests/test…  │  test_enum    │  104          │  124         │  AssertionE…  │
│  tests/test…  │  test_inser…  │  127          │  143         │  AssertionE…  │
└───────────────┴───────────────┴───────────────┴──────────────┴───────────────┘
Results (0.33s):
         5 failed
       110 passed
         2 skipped

I noticed that I had to package pytest-pretty as well to run the tests. I would like to mention, that while I guess the output is nice for debugging to some degree, it is problematic in environments with limited line length and it hides the full name of the tests, which makes it hard to see what's going on.

I need the full test name for being able to disable tests via pytest --deselect.

debug and pprint not working with pydantic classes

Hi,

thanks for devtools I use it quite a lot.
Now I stumbled over the following:

>>> from devtools import debug

>>> class W:
...     pass

>>> debug(W)
<stdin>:1 <module> (no code context for debug call, code inspection impossible)
    <class '__main__.W'> (type)

>>> class W(:
...     pass
>>> from pydantic import BaseModel

>>> debug(BaseModel)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 122, in __call__
    s = d_out.str(highlight)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 85, in str
    return prefix + '\n    ' + '\n    '.join(a.str(highlight) for a in self.arguments)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 85, in <genexpr>
    return prefix + '\n    ' + '\n    '.join(a.str(highlight) for a in self.arguments)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 45, in str
    s += pformat(self.value, indent=4, highlight=highlight)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/prettier.py", line 80, in __call__
    self._format(value, indent_current=indent, indent_first=indent_first)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/prettier.py", line 94, in _format
    gen = pretty_func(fmt=fmt, skip_exc=SkipPretty)
  File "pydantic/utils.py", line 263, in pydantic.utils.Representation.__pretty__
TypeError: __pretty__() takes exactly 2 positional arguments (0 given)

__pretty__() takes exactly 2 positional arguments (0 given)

>>> class W(BaseModel):
...     pass

>>> debug(W)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 122, in __call__
    s = d_out.str(highlight)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 85, in str
    return prefix + '\n    ' + '\n    '.join(a.str(highlight) for a in self.arguments)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 85, in <genexpr>
    return prefix + '\n    ' + '\n    '.join(a.str(highlight) for a in self.arguments)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/debug.py", line 45, in str
    s += pformat(self.value, indent=4, highlight=highlight)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/prettier.py", line 80, in __call__
    self._format(value, indent_current=indent, indent_first=indent_first)
  File "/Users/weissl/.pyenv/versions/3.8.2/envs/cauldron/lib/python3.8/site-packages/devtools/prettier.py", line 94, in _format
    gen = pretty_func(fmt=fmt, skip_exc=SkipPretty)
  File "pydantic/utils.py", line 263, in pydantic.utils.Representation.__pretty__
TypeError: __pretty__() takes exactly 2 positional arguments (0 given)

__pretty__() takes exactly 2 positional arguments (0 given)

>>>

Could you please give me a hint what I am doing wrong here?

Suggestion: adding parameter to configure the frame depth

First of all: I just discover devtools and I think it is a fantastic tool. I definitely plan to use it instead of my simple minded debugging helper.

Currently, debug() is assumed to be called explicitly in a given module, with a hard-coded value for the frame in which it looks for information:

    def _process(self, args, kwargs) -> DebugOutput:
        """
        BEWARE: this must be called from a function exactly 2 levels below the top of the stack.
        """
        # HELP: any errors other than ValueError from _getframe? If so please submit an issue
        try:
            call_frame: 'FrameType' = sys._getframe(2)
        except ValueError:
            ...

I would find it useful if the constant 2 could be a configurable parameter. For example, I could then define custom functions like the following:

def handle_problem(*args, **kwargs):
    if dev_version:
        debug = Debug(additional_frame_depth=1)
        debug(*args, **kwargs)
    else:
        print("Internal problem: please report this case.")

Obviously, I could currently do this by subclassing Debug, but I think that this might be a useful feature to have. I understand that the idea is to use devtools.debug during development and one would normally remove all traces of it for releases. However, I find it useful in one of my projects (friendly-traceback) to leave in place such debugging functions such that end-users are never exposed to tracebacks generated by my own project while still being able to cope with the unexpected.

dataclasses

for some reason, not currently supported.

Error building wheel

Trying to make a multi-stage build with python:3.7.2-alpine docker image. First I make a wheel for devtools with pip wheel devtools --wheel-dir=wheels/. Then, in the next stage, install wheel pip install wheels/*. But I got an error that devtools-0.5-py35,py36-none-any.whl is not a supported wheel on this platform.

The problem is in this piece - py35,py36. There should be a period, not a comma.

Nested dataclasses fail to get formatted properly

Reproducible code

import dataclasses
import devtools


@dataclasses.dataclass
class Foo:
    x: int


@dataclasses.dataclass
class Bar:
    a: float
    b: Foo


devtools.debug(foo=Foo(1), bar=Bar(1.0, b=Foo(1)))

Output

test.py:16 <module>
    foo: Foo(x=1) (Foo)
    bar: Bar(
        a=1.0,
        b={'x': 1},
    ) (Bar)

Expected output

test.py:16 <module>
    foo: Foo(x=1) (Foo)
    bar: Bar(
        a=1.0,
        b=Foo(x=1),
    ) (Bar)

improvements

  • length of anything with length
  • multidict rendered properly
  • strip middle of massive outputs.

error when code has changed, then debug runs.

AttributeError: 'FunctionDef' object has no attribute 'value'
Traceback (most recent call last):
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/atoolbox/middleware.py", line 86, in error_middleware
    r = await handler(request)
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/atoolbox/middleware.py", line 114, in pg_middleware
    return await handler(request)
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/atoolbox/middleware.py", line 167, in csrf_middleware
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/devtools/debug.py", line 112, in __call__
    d_out = self._process(args, kwargs, r'debug *\(')
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/devtools/debug.py", line 153, in _process
    func_ast, code_lines, lineno = self._parse_code(call_frame, func_regex, filename)
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/devtools/debug.py", line 223, in _parse_code
    func_ast = self._wrap_parse(code, filename)
  File "/home/samuel/code/steamdonkey/env/lib/python3.6/site-packages/devtools/debug.py", line 260, in _wrap_parse
    return ast.parse(code, filename=filename).body[0].body[0].value
AttributeError: 'FunctionDef' object has no attribute 'value'

debug() output suddenly styled weirdly

Hi,

I've been using the debug() function of devtools to print out some data structures for about 1-2 months now and I've been loving it!

After taking a break from a Python program for about 2 weeks I've gone back to start improving it again and I've noticed that now, when I use debug(), the colorized output looks very weird. Since I haven't seen any other GitHub issues on the subject I'm sure the problem is on my end, but I can't figure out what it is.

To simplify things, I've created a new venv, installed devtools and Pygments using the suggested pip install devtools[pygments]:

❯ pip list
Package    Version
---------- -------
devtools   0.6
pip        20.2.4
Pygments   2.7.1
setuptools 49.2.1

❯ python -V
Python 3.8.6

and created the simplest program I could think of:

#!/usr/bin/env python

from devtools import debug

a = 5

debug(a)

Still, the output is like this:

image

I normally use Bash on ArchLinux, but I've tried running this in Fish with the same result. I have installed a fresh Fedora 32 Workstation in a VM, created a new venv, and tried the same program, and get the same result.

I really don't understand why I am suddenly getting this results and what I am doing wrong.

Any tips/hints on where I should look to troubleshoot further? Thanks.

"error passing code, SyntaxError" - fails with 4 lines or more?

So, apparently this fails:

a = 1
debug(
    a,
    a,
    a,
    a,
)

yielding

<ipython-input-540-3818b4c07d3a>:2 <module> (error passing code, SyntaxError: unexpected EOF while parsing (<ipython-input-540-3818b4c07d3a>, line 2))
    1 (int)
    1 (int)
    1 (int)
    1 (int)

However if you join any two of the four lines inside parentheses, it then works just fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.