Skip to content
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

Add pformat to config object #33

Merged
merged 6 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Here we specify `depth=2` to mean we should also step one level down into inner

![cache output](https://i.imgur.com/Enu7k0h.png)

At a glance we can see that in the first call the cache lookup failed with a `KeyError` so the original `add` function was called, while in the second call the previously cached result was returned immediately.
At a glance we can see that in the first call the cache lookup failed with a `KeyError` so the original `add` function was called, while in the second call the previously cached result was returned immediately.

If you don't want to trace an entire function, you can wrap the relevant part in a `with` block:

Expand Down Expand Up @@ -122,7 +122,7 @@ which outputs something like:

- `depth`: as seen above, snoops deeper calls made by the function/block you trace. The default is 1, meaning no inner calls, so pass something bigger.
- `watch`: show values of arbitrary expressions by specifying them as a string, e.g:

```python
@snoop(watch=('foo.bar', 'self.x["whatever"]'))
```
Expand All @@ -148,9 +148,9 @@ See [`watch_extras`](#watch_extras) to show additional information about any val

While `snoop` is meant to save you from writing `print` calls, sometimes that's still exactly the kind of thing you need. `pp` aims to be the best possible version of this. It can be used alone or in combination with `snoop`.

`pp(x)` will output `x = <pretty printed value of x>`, i.e. it will show the source code of its argument(s) so you know what's being printed, and format the value with `pprint.pformat` so that you can easily see the layout of complicated data structures.
`pp(x)` will output `x = <pretty printed value of x>`, i.e. it will show the source code of its argument(s) so you know what's being printed, and format the value with `pprint.pformat` so that you can easily see the layout of complicated data structures. If [`prettyprinter`](https://github.com/tommikaikkonen/prettyprinter) or [`pprintpp`](https://github.com/wolever/pprintpp) is installed their `pformat` will be used instead of `pprint.pformat`.

`pp` will return its argument directly so you can easily insert it in code without rearranging. If given multiple arguments, it will return them as a tuple, so you can replace `foo(x, y)` with `foo(*pp(x, y))` to leave the behaviour of the code intact.
`pp` will return its argument directly so you can easily insert it in code without rearranging. If given multiple arguments, it will return them as a tuple, so you can replace `foo(x, y)` with `foo(*pp(x, y))` to leave the behaviour of the code intact.

Here's an example:

Expand Down Expand Up @@ -181,7 +181,7 @@ There are a few situations where `pp` can't find the source code of its argument
- In Python 3.4 and PyPy.
- In the presence of magic which transforms source code under the hood, such as `pytest` or `birdseye` (and thus the [`@spy`](#spy) decorator).
- When the source file has been modified before the first call to `pp` or `snoop`.

Under the hood, `pp` uses the library [`executing`](https://github.com/alexmojaki/executing) to locate the AST node of the function call - check it out if you'd like to write some cool utilities of your own.

`pp` is inspired by [icecream](https://github.com/gruns/icecream) and offers the same basic API for printing, but `pp` integrates seamlessly with `snoop` and offers `pp.deep`, which is unique.
Expand Down Expand Up @@ -253,7 +253,7 @@ The only big disadvantage of `@spy` is that it significantly reduces performance

[Read more about `birdseye` in the documentation here.](https://birdseye.readthedocs.io/en/latest/)

('`spy`' is so named because it's a combination of the decorator names '`snoop`' and '`eye`')
('`spy`' is so named because it's a combination of the decorator names '`snoop`' and '`eye`')

## `install()`

Expand All @@ -277,7 +277,7 @@ will let you decorate functions with `@ss`.

If you dislike this feature and would prefer to just import normally, but you want to use `install()` for other configuration, pass `builtins=False`.

As an alternative, in Python 3.7+ you can use the new `breakpoint` function in place of `snoop` if you set the environment variable `PYTHONBREAKPOINT=snoop.snoop`.
As an alternative, in Python 3.7+ you can use the new `breakpoint` function in place of `snoop` if you set the environment variable `PYTHONBREAKPOINT=snoop.snoop`.

### Disabling

Expand All @@ -294,30 +294,31 @@ You can also dynamically re-enable the functions at any point by calling `snoop.
- Anything with a `write` method, e.g. `sys.stdout` or a file object.
- Any callable with a single string argument, e.g. `logger.info`.
- `color`: determines whether the output includes escape characters to display colored text in the console. If you see weird characters in your output, your console doesn't support colors, so pass `color=False`.
- Code is syntax highlighted using [Pygments](http://pygments.org/), and this argument is passed as the style. You can choose a different color scheme by passing a string naming a style (see [this gallery](https://help.farbox.com/pygments.html)) or a style class. The default style is monokai.
- Code is syntax highlighted using [Pygments](http://pygments.org/), and this argument is passed as the style. You can choose a different color scheme by passing a string naming a style (see [this gallery](https://help.farbox.com/pygments.html)) or a style class. The default style is monokai.
- By default this parameter is set to `out.isatty()`, which is usually true for stdout and stderr but will be false if they are redirected or piped. Pass `True` or a style if you want to force coloring.
- To see colors in the PyCharm Run window, edit the Run Configuration and tick "Emulate terminal in output console".
- `prefix`: Pass a string to start all snoop lines with that string so you can grep for them easily.
- `columns`: This specifies the columns at the start of each output line. You can pass a string with the names of built in columns separated by spaces or commas. These are the available columns:
- `time`: The current time. This is the only column by default.
- `thread`: The name of the current thread.
- `thread`: The name of the current thread.
- `thread_ident`: The [identifier](https://docs.python.org/3/library/threading.html#threading.Thread.ident) of the current thread, in case thread names are not unique.
- `file`: The filename (not the full path) of the current function.
- `full_file`: The full path to the file (also shown anyway when the function is called).
- `function`: The name of the current function.
- `function_qualname`: The qualified name of the current function.
- `watch_extras` and `replace_watch_extras`: read about these under [Advanced usage](#watch_extras)

If you want a custom column, please open an issue to tell me what you're interested in! In the meantime, you can pass a list, where the elements are either strings or callables. The callables should take one argument, which will be an `Event` object. It has attributes `frame`, `event`, and `arg`, as specified in [`sys.settrace()`](https://docs.python.org/3/library/sys.html#sys.settrace), and other attributes which may change.
- `pformat`: set the pretty formatting function `pp` uses. Default is to use the first of `prettyprinter.pformat`, `pprintpp.pformat` and `pprint.pformat` that can be imported.

## API differences from `PySnooper`

If you're familiar with `PySnooper` and want to use `snoop`, there are a few things you should be aware of that you have to do differently:
If you're familiar with `PySnooper` and want to use `snoop`, there are a few things you should be aware of that you have to do differently:

- Pass `prefix` and `overwrite` to `install()`, not `snoop()`.
- The first argument to `pysnooper.snoop`, called `output`, should be passed to `install` with the keyword `out`.
- Instead of `snoop(thread_info=True)`, write `install(columns='time thread thread_ident')`.
- Instead of the environment variable `PYSNOOPER_DISABLED`, use `install(enabled=False)`.
- Instead of the environment variable `PYSNOOPER_DISABLED`, use `install(enabled=False)`.
- Instead of using `custom_repr`, see [`watch_extras`](#watch_extras) and [Customising the display of variables](#customising-the-display-of-variables).

If you're not sure if it's worth using `snoop` instead of `PySnooper`, [read the comparison here](https://github.com/alexmojaki/snoop/wiki/Comparison-to-PySnooper).
Expand Down Expand Up @@ -391,7 +392,7 @@ from cheap_repr import register_repr, cheap_repr
@register_repr(MyClass)
def repr_my_class(x, helper):
return '{}(items={})'.format(
x.__class__.__name__,
x.__class__.__name__,
cheap_repr(x.items, helper.level - 1),
)
```
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def file_to_string(*path):
tests_require = [
'pytest',
'littleutils',
'prettyprinter',
'pprintpp',
]

if 'pypy' not in sys.version.lower() and sys.version_info[:2] not in [(3, 4)]:
Expand Down
21 changes: 19 additions & 2 deletions snoop/configuration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
import os
import pprint
import sys
import threading
from io import open
Expand Down Expand Up @@ -37,6 +38,7 @@ def install(
watch_extras=(),
replace_watch_extras=None,
formatter_class=DefaultFormatter,
pformat=None,
):
"""
Configure output, enable or disable, and add names to builtins. Parameters:
Expand All @@ -62,8 +64,9 @@ def install(
- `full_file`: The full path to the file (also shown anyway when the function is called).
- `function`: The name of the current function.
- `function_qualname`: The qualified name of the current function.

If you want a custom column, please open an issue to tell me what you're interested in! In the meantime, you can pass a list, where the elements are either strings or callables. The callables should take one argument, which will be an `Event` object. It has attributes `frame`, `event`, and `arg`, as specified in [`sys.settrace()`](https://docs.python.org/3/library/sys.html#sys.settrace), and other attributes which may change.

If you want a custom column, please open an issue to tell me what you're interested in! In the meantime, you can pass a list, where the elements are either strings or callables. The callables should take one argument, which will be an `Event` object. It has attributes `frame`, `event`, and `arg`, as specified in [`sys.settrace()`](https://docs.python.org/3/library/sys.html#sys.settrace), and other attributes which may change.
- `pformat`: set the pretty formatting function `pp` uses. Default is to use the first of `prettyprinter.pformat`, `pprintpp.pformat` and `pprint.pformat` that can be imported.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also mention the pformat argument here and under Output configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is mentioned there (?) I pushed a new commit which adds it to the README's Output configuration section.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol my brain only saw mention of the libraries and not the argument.

"""

if builtins:
Expand All @@ -80,6 +83,7 @@ def install(
watch_extras=watch_extras,
replace_watch_extras=replace_watch_extras,
formatter_class=formatter_class,
pformat=pformat,
)
package.snoop.config = config
package.pp.config = config
Expand All @@ -104,6 +108,7 @@ def __init__(
watch_extras=(),
replace_watch_extras=None,
formatter_class=DefaultFormatter,
pformat=None,
):
if can_color:
if color is None:
Expand All @@ -115,6 +120,18 @@ def __init__(
self.write = get_write_function(out, overwrite)
self.formatter = formatter_class(prefix, columns, color)
self.enabled = enabled

if pformat is None:
try:
from prettyprinter import pformat
except Exception:
try:
from pprintpp import pformat
except Exception:
from pprint import pformat

self.pformat = pformat

self.pp = PP(self)

class ConfiguredTracer(Tracer):
Expand Down
3 changes: 1 addition & 2 deletions snoop/pp_module.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ast
import inspect
import pprint
import traceback
import warnings
from copy import deepcopy
Expand Down Expand Up @@ -68,7 +67,7 @@ def __init__(self, pp_object, args, deep):

def write(self, source, value, depth=0):
if depth == 0:
value_string = pprint.pformat(value)
value_string = self.config.pformat(value)
else:
try:
value_string = repr(value)
Expand Down
51 changes: 51 additions & 0 deletions tests/sample_results/2.7/pp_custom_pformat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Test with custom pformat
12:34:56.78 LOG:
12:34:56.78 .... x = custom(1)
12:34:56.78 LOG:
12:34:56.78 .... pp(x) + y = custom(3)
12:34:56.78 LOG:
12:34:56.78 ............ x = 1
12:34:56.78 ............ y = 2
12:34:56.78 ........ x + y = 3
12:34:56.78 ........ y = 2
12:34:56.78 .... (x + y) + y = custom(5)
12:34:56.78 >>> Enter with block in test_misc in File "/path/to_file.py", line 11
12:34:56.78 .......... x = 1
12:34:56.78 .......... y = 2
12:34:56.78 12 | y = 3 # custom pformat is not used here
12:34:56.78 .............. y = 3
12:34:56.78 <<< Exit with block in test_misc
12:34:56.78 LOG:
12:34:56.78 .... d = custom(['a long key to be pretty printed prettily', ['prettyprinter prints datetime with keyword arguments:', datetime.datetime(1970, 1, 1, 0, 0, 42)]])

Test without prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = ['a long key to be pretty printed prettily',
12:34:56.78 ['prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42)]]

Test with prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = ['a long key to be pretty printed prettily',
12:34:56.78 ['prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42)]]

Test without prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42),
12:34:56.78 ],
12:34:56.78 ]

Test with prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42),
12:34:56.78 ],
12:34:56.78 ]
47 changes: 47 additions & 0 deletions tests/sample_results/3.4/pp_custom_pformat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Test with custom pformat
12:34:56.78 LOG:
12:34:56.78 .... <argument> = custom(1)
12:34:56.78 LOG:
12:34:56.78 .... <argument> = custom(3)
12:34:56.78 LOG:
12:34:56.78 .... <argument> = custom(5)
12:34:56.78 >>> Enter with block in test_misc in File "/path/to_file.py", line 11
12:34:56.78 .......... x = 1
12:34:56.78 .......... y = 2
12:34:56.78 12 | y = 3 # custom pformat is not used here
12:34:56.78 .............. y = 3
12:34:56.78 <<< Exit with block in test_misc
12:34:56.78 LOG:
12:34:56.78 .... <argument> = custom(['a long key to be pretty printed prettily', ['prettyprinter prints datetime with keyword arguments:', datetime.datetime(1970, 1, 1, 0, 0, 42)]])

Test without prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... <argument> = ['a long key to be pretty printed prettily',
12:34:56.78 ['prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42)]]

Test with prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... <argument> = ['a long key to be pretty printed prettily',
12:34:56.78 ['prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42)]]

Test without prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... <argument> = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42),
12:34:56.78 ],
12:34:56.78 ]

Test with prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... <argument> = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42),
12:34:56.78 ],
12:34:56.78 ]
69 changes: 69 additions & 0 deletions tests/sample_results/3.5/pp_custom_pformat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Test with custom pformat
12:34:56.78 LOG:
12:34:56.78 .... x = custom(1)
12:34:56.78 LOG:
12:34:56.78 .... pp(x) + y = custom(3)
12:34:56.78 LOG:
12:34:56.78 ............ x = 1
12:34:56.78 ............ y = 2
12:34:56.78 ........ x + y = 3
12:34:56.78 ........ y = 2
12:34:56.78 .... (x + y) + y = custom(5)
12:34:56.78 >>> Enter with block in test_misc in File "/path/to_file.py", line 11
12:34:56.78 .......... x = 1
12:34:56.78 .......... y = 2
12:34:56.78 12 | y = 3 # custom pformat is not used here
12:34:56.78 .............. y = 3
12:34:56.78 <<< Exit with block in test_misc
12:34:56.78 LOG:
12:34:56.78 .... d = custom(['a long key to be pretty printed prettily', ['prettyprinter prints datetime with keyword arguments:', datetime.datetime(1970, 1, 1, 0, 0, 42)]])

Test without prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = ['a long key to be pretty printed prettily',
12:34:56.78 ['prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42)]]

Test with prettyprinter and without pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(
12:34:56.78 year=1970,
12:34:56.78 month=1,
12:34:56.78 day=1,
12:34:56.78 hour=0,
12:34:56.78 minute=0,
12:34:56.78 second=42
12:34:56.78 )
12:34:56.78 ]
12:34:56.78 ]

Test without prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(1970, 1, 1, 0, 0, 42),
12:34:56.78 ],
12:34:56.78 ]

Test with prettyprinter and with pprintpp
12:34:56.78 LOG:
12:34:56.78 .... d = [
12:34:56.78 'a long key to be pretty printed prettily',
12:34:56.78 [
12:34:56.78 'prettyprinter prints datetime with keyword arguments:',
12:34:56.78 datetime.datetime(
12:34:56.78 year=1970,
12:34:56.78 month=1,
12:34:56.78 day=1,
12:34:56.78 hour=0,
12:34:56.78 minute=0,
12:34:56.78 second=42
12:34:56.78 )
12:34:56.78 ]
12:34:56.78 ]
Loading